Compare commits
368 Commits
2.0.46-bet
...
master
Author | SHA1 | Date |
---|---|---|
Frank Denis | 0059194a9e | |
Frank Denis | 35d7aa0603 | |
Frank Denis | 8dadd61730 | |
dependabot[bot] | f7da81cf29 | |
Frank Denis | 0efce55895 | |
Frank Denis | 5a1d94d506 | |
Frank Denis | 271943c158 | |
Frank Denis | 34a1f2ebf5 | |
Frank Denis | f8ce22d9b9 | |
Frank Denis | 249dba391d | |
Frank Denis | 987ae216e3 | |
Frank Denis | 7fba32651b | |
Frank Denis | 6ae388e646 | |
Frank Denis | 0af88bc875 | |
Frank Denis | d36edeb612 | |
Frank Denis | 041a6c7d7f | |
cuibuwei | 2c6416d5ae | |
Frank Denis | 4d1cd67d4d | |
Frank Denis | 363d44919f | |
Frank Denis | a88076d06f | |
Frank Denis | 119bc0b660 | |
Robert Edmonds | 49000cd4f4 | |
Frank Denis | ec46e09360 | |
dependabot[bot] | ea5808e024 | |
Frank Denis | 79a1aa8325 | |
Alison Winters | 4c442f5dbb | |
Alison Winters | f7e13502c0 | |
YX Hao | 8d43ce9b56 | |
YX Hao | ac5087315c | |
Frank Denis | ad80d81d43 | |
dependabot[bot] | a7fb13ba4e | |
Frank Denis | 006619837f | |
Frank Denis | 093936f7ab | |
Frank Denis | 7462961980 | |
Frank Denis | 0b559bb54f | |
Frank Denis | 658835b4ff | |
Frank Denis | 90c3017793 | |
dependabot[bot] | e371138b86 | |
Frank Denis | bcbf2db4ff | |
YX Hao | 64fa90839c | |
Frank Denis | f2484f5bd5 | |
Frank Denis | 63f8d9b30d | |
Xiaotong Liu | 49e3570c2c | |
lifenjoiner | 3be53642fe | |
YX Hao | 13e7077200 | |
Frank Denis | f5912d7ca9 | |
dependabot[bot] | 0196d7d2ab | |
Frank Denis | 898ded9c52 | |
Frank Denis | e782207911 | |
Frank Denis | 0f1f635ec1 | |
keatonLiu | 956a14ee21 | |
dependabot[bot] | 22731786a2 | |
Frank Denis | 42b6ae9052 | |
YX Hao | 0d5e52bb16 | |
Frank Denis | 0ba728b6ce | |
Frank Denis | cb80bf33e8 | |
Frank Denis | 88207560a7 | |
Jeffrey Damick | 4a361dbb05 | |
Frank Denis | b37a5c991a | |
Frank Denis | 0232870870 | |
Frank Denis | 1a9bf8a286 | |
Frank Denis | 7fb58720fb | |
Frank Denis | f85b3e81ec | |
Frank Denis | 79779cf744 | |
Frank Denis | 8bea679e7b | |
Frank Denis | 96f21f1bff | |
dependabot[bot] | 21097686c1 | |
Frank Denis | 87571d4a7f | |
Frank Denis | f531c8fffb | |
Frank Denis | 5ae83c1592 | |
Frank Denis | c86e9a90cc | |
Frank Denis | d48c811ea9 | |
Frank Denis | f2b1edcec2 | |
Frank Denis | 1b65fe62b0 | |
Frank Denis | 194752e829 | |
Frank Denis | 808f2dfa0e | |
Frank Denis | 7dd79d5f96 | |
Frank Denis | 5088d8fae1 | |
Frank Denis | aff09648bb | |
Frank Denis | 7bca9a6c0a | |
Frank Denis | 98d0938815 | |
Frank Denis | 50780421a8 | |
RadhaKrishna | be7d5d1277 | |
Frank Denis | c3dd761b81 | |
Frank Denis | d8aec47a72 | |
Frank Denis | cfd6ced134 | |
Frank Denis | bdf27330c9 | |
Frank Denis | a108d048d8 | |
Frank Denis | afcfd566c9 | |
Frank Denis | ce55d1c5bb | |
Frank Denis | 2481fbebd7 | |
Frank Denis | 32aad7bb34 | |
Frank Denis | 7033f242c0 | |
Frank Denis | 2675d73b13 | |
Frank Denis | 5085a22903 | |
Frank Denis | 7cc5a051c7 | |
Frank Denis | 894d20191f | |
Frank Denis | 0a98be94a7 | |
Frank Denis | 1792c06bc7 | |
Expertcoderz | 63e414021b | |
Frank Denis | d659a801c2 | |
Frank Denis | a4eda39563 | |
Expertcoderz | 4114f032c3 | |
Frank Denis | a352a3035c | |
Frank Denis | 60684f8ee4 | |
YX Hao | be369a1f7a | |
YX Hao | 89ccc59f0e | |
Frank Denis | 16b2c84147 | |
Carlo Teubner | b46775ae0c | |
Frank Denis | cef4b041d7 | |
Carlo Teubner | d8b1f4e7cd | |
Frank Denis | 23a6cd7504 | |
Frank Denis | f42b7dad17 | |
Frank Denis | 4f3ce0cbae | |
Frank Denis | 0f1e3b4ba8 | |
Frank Denis | 62ef5c9d02 | |
Frank Denis | f9f68cf0a3 | |
Frank Denis | 0c26d1637a | |
Frank Denis | 9f86ffdd1e | |
lifenjoiner | 9b2c674744 | |
Frank Denis | d381af5510 | |
Frank Denis | c66023c7d7 | |
Frank Denis | 5b8e7d4114 | |
KOLANICH | f4007f709d | |
lifenjoiner | dd1c066724 | |
lifenjoiner | 5d551e54ce | |
Thad Guidry | fbc7817366 | |
Frank Denis | 9b61b73852 | |
Frank Denis | af6340df09 | |
Frank Denis | 9c73ab3070 | |
Frank Denis | ea3625bcfd | |
Frank Denis | f567f57150 | |
Frank Denis | c03f1a31eb | |
Frank Denis | c3c51bb435 | |
Frank Denis | 0f30b3b028 | |
lifenjoiner | 6d826afac5 | |
Frank Denis | b341c21dbd | |
Frank Denis | 92ed5b95e0 | |
Frank Denis | b898e07066 | |
dependabot[bot] | 92063aa76d | |
dependabot[bot] | 4be5264529 | |
Frank Denis | 13d78c042b | |
Frank Denis | 36c17eb59a | |
Frank Denis | b9f8f78c6e | |
Frank Denis | fc16e3c31c | |
lifenjoiner | b3318a94b7 | |
Frank Denis | ca0f353087 | |
Frank Denis | cf7d60a704 | |
Frank Denis | a47f7fe750 | |
Frank Denis | beb002335f | |
Frank Denis | 15c87a68a1 | |
Frank Denis | 47e6a56b16 | |
Frank Denis | 03c6f92a5f | |
lifenjoiner | 24a301b1af | |
lifenjoiner | a8d1c2fd24 | |
Frank Denis | 96ffb21228 | |
Frank Denis | acc25fcefb | |
Frank Denis | 07b4ec33c5 | |
Frank Denis | 9f3ef735f2 | |
Frank Denis | d568e43937 | |
Frank Denis | 68f3ab249c | |
dependabot[bot] | 2edfdc48b8 | |
Frank Denis | 3f31c4d3e2 | |
Frank Denis | 84184bbad8 | |
Frank Denis | f630094e8d | |
lifenjoiner | 3517dec376 | |
lifenjoiner | 683aad75da | |
lifenjoiner | e1c7ea1770 | |
Frank Denis | 470460f069 | |
Frank Denis | 8694753866 | |
Frank Denis | b4b58366cc | |
Frank Denis | f7df72eafa | |
Frank Denis | fb15535282 | |
Frank Denis | c32aad3dfd | |
Frank Denis | 9e208e6090 | |
Frank Denis | 5f88a9146c | |
Frank Denis | 3f23ff5c08 | |
Frank Denis | 33c8027e0a | |
Frank Denis | 11e824bd13 | |
Deltadroid | c3fd855831 | |
Frank Denis | 5438eed2f4 | |
Frank Denis | a868e2b306 | |
Frank Denis | f21eca0764 | |
Frank Denis | c883949a97 | |
Frank Denis | e13b4842e8 | |
dependabot[bot] | 5705b60936 | |
Frank Denis | 5f4dfc5d6e | |
Frank Denis | a89d96144f | |
Frank Denis | 4aa415de6e | |
Frank Denis | f4389559ef | |
Frank Denis | 361455cd58 | |
cobratbq | 77059ce450 | |
Frank Denis | 09a6918226 | |
Frank Denis | c748630691 | |
Frank Denis | 94cba8cf78 | |
Maurizio Pasquinelli | ca253923eb | |
Frank Denis | 08d44241b9 | |
lifenjoiner | 4881186dcf | |
Frank Denis | 41f192a907 | |
Frank Denis | 937c1e63e2 | |
Frank Denis | e124623ffc | |
lifenjoiner | 55fc4c207b | |
Ian Bashford | baee50f1dc | |
Frank Denis | 6e1bc06477 | |
Frank Denis | 8523a92437 | |
Frank Denis | 442f2e15cb | |
Frank Denis | 0c88e2a1a0 | |
Frank Denis | 35063a1eec | |
Frank Denis | 07266e4d4f | |
Frank Denis | 5977de660b | |
lifenjoiner | 91388b148c | |
lifenjoiner | 8e46f44799 | |
Frank Denis | 3d641b758a | |
Frank Denis | 49ea894ce8 | |
lifenjoiner | 568f54fabb | |
pc-v2 | dc2fff05be | |
Frank Denis | 38e87f9a7b | |
lifenjoiner | 0e2bb13254 | |
Frank Denis | 59ce17e0ab | |
Frank Denis | ee5c9d67a4 | |
Frank Denis | 8c43118b03 | |
ignoramous | 7177a0ec74 | |
lifenjoiner | 72a602577a | |
lifenjoiner | 0a0b69d93d | |
lifenjoiner | 6916c047e1 | |
ignoramous | 8d737a69f5 | |
Frank Denis | 866954fbad | |
Frank Denis | e477d0e126 | |
Frank Denis | e24fdd2235 | |
livingentity | 74fb5dabb9 | |
Frank Denis | 1afd573b0d | |
Frank Denis | c367a82ac0 | |
dependabot[bot] | 9c8c327703 | |
livingentity | 207d44323d | |
Frank Denis | 3eac156789 | |
Frank Denis | 5fca7ea49e | |
Frank Denis | 77dc3b1e85 | |
Frank Denis | 66f019d886 | |
livingentity | f67e9cab32 | |
Frank Denis | 5d023d2a7c | |
Frank Denis | e931b234b7 | |
quindecim | ed2c880648 | |
Frank Denis | c467e20311 | |
Frank Denis | df3fb0c9f8 | |
Frank Denis | c0435772d4 | |
Frank Denis | 49c17f8e98 | |
Frank Denis | 0465cd35ef | |
dependabot[bot] | 08420917a5 | |
livingentity | 87d9653ec2 | |
BigDargon | d30c44a6a8 | |
dependabot[bot] | 911108149b | |
dependabot[bot] | a8aa4cb8e6 | |
Frank Denis | ca076ce133 | |
Frank Denis | 034d3bd424 | |
Frank Denis | c08852feb1 | |
Frank Denis | 9373cc7162 | |
Frank Denis | cb140673fa | |
Frank Denis | 7956ba5b10 | |
livingentity | 9ec8a35468 | |
livingentity | ac6abfb985 | |
quindecim | a20d1685b2 | |
livingentity | 62092726ec | |
Frank Denis | f38a5463b0 | |
quindecim | 7a54406415 | |
quindecim | bce0405c0a | |
quindecim | 29a3442306 | |
Frank Denis | 8ed98cacae | |
Peter Dave Hello | ef1c70e87d | |
Frank Denis | 4c67e790f6 | |
Frank Denis | 4eeed5816f | |
Frank Denis | c10e6e0635 | |
Frank Denis | e6089449b6 | |
mibere | 706c1ab286 | |
Frank Denis | f7e3381650 | |
cobratbq | 7a8bd35009 | |
Frank Denis | 06733f57ed | |
Frank Denis | 4fd26029c7 | |
Frank Denis | 351bced7c5 | |
Frank Denis | 916e84e798 | |
ValdikSS | 53f3a0e63d | |
Frank Denis | 4e9f0382ee | |
dependabot[bot] | 9121f4f359 | |
Frank Denis | e73459f558 | |
Frank Denis | fbfc2d57a7 | |
Frank Denis | b9d6b22ce1 | |
Ian Bashford | 1b6caba307 | |
CNMan | 27e93a53cf | |
Frank Denis | 9e7221c31c | |
Frank Denis | f6f63743ce | |
Frank Denis | 561e849889 | |
a1346054 | 766e149699 | |
Frank Denis | e2ada45598 | |
Frank Denis | fc0ff3b26a | |
Frank Denis | 88b174e5eb | |
Frank Denis | e1f3f58eed | |
Frank Denis | efcd392279 | |
Frank Denis | 8da1b698ad | |
Frank Denis | 180e75bfdb | |
Frank Denis | 77b27d9293 | |
Frank Denis | 4c29840040 | |
Frank Denis | b7704a05c5 | |
Frank Denis | d82021b545 | |
Frank Denis | e5608e08cf | |
livingentity | 2a3e59c4bf | |
Frank Denis | 69019b7a80 | |
Frank Denis | 8e913d8bf9 | |
Frank Denis | 3bae61dbe1 | |
Frank Denis | 5fedbe4c6e | |
Frank Denis | b2f26192e1 | |
Frank Denis | a4684d3bf5 | |
Frank Denis | 34f0caaa34 | |
Frank Denis | 75e917ae49 | |
Frank Denis | 8fc0ffc35f | |
Frank Denis | 97a983c6b3 | |
Frank Denis | 0f00cd27f9 | |
dependabot[bot] | 8178f3419f | |
Alison Winters | d8358b795f | |
Aaron | b8c5790716 | |
a1346054 | f6a2d2ea44 | |
Frank Denis | 68b0d87522 | |
Frank Denis | bb5dc3b1fc | |
Frank Denis | 59b6b8359c | |
Frank Denis | 785f86f50f | |
Frank Denis | d8c4a90501 | |
Frank Denis | 9cb89ae410 | |
Frank Denis | e83cb28ef5 | |
Frank Denis | 73dc3dd1d8 | |
Frank Denis | 35c82e3dcf | |
Frank Denis | 9f0b409375 | |
Frank Denis | 49fdb461d8 | |
Frank Denis | c9d5d81e6a | |
Frank Denis | 1052fa6323 | |
Frank Denis | c8a61abb79 | |
Frank Denis | e64425b5e7 | |
Frank Denis | da69583bd2 | |
Frank Denis | d996e3424d | |
Frank Denis | b4a073f54f | |
Frank Denis | 0ca90dd8cc | |
Frank Denis | 026c42424f | |
Frank Denis | cedd4f3b54 | |
dependabot[bot] | d3b1976208 | |
Frank Denis | bc529f0076 | |
Frank Denis | 796a7f6d31 | |
Frank Denis | 03e1916527 | |
Frank Denis | d35c1c3cb2 | |
Frank Denis | 8b3b7d38ac | |
Frank Denis | 9acf257fe5 | |
Frank Denis | 5a091c6da4 | |
Jauder Ho | 59690da355 | |
Frank Denis | f033bb3034 | |
Frank Denis | 4caa7b6d64 | |
Frank Denis | 6117a1111c | |
Frank Denis | 9bea0e8f20 | |
Frank Denis | da9c95ec5c | |
Frank Denis | b472fb3b21 | |
Frank Denis | 5fb2901dbc | |
Frank Denis | ccddb18424 | |
Frank Denis | 1b03ac817e | |
Frank Denis | a34258c3aa | |
Frank Denis | 95c9fa75f8 | |
Frank Denis | d6be6f97ea | |
Frank Denis | a85a003d2b | |
Frank Denis | 5a9a6467df | |
Frank Denis | ec581597a2 | |
Frank Denis | 33ed882efe | |
Frank Denis | b39232e996 | |
Frank Denis | 9ebb90b22e | |
Frank Denis | b1dd11be72 |
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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,8 +122,8 @@ 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
|
||||
|
@ -142,17 +147,6 @@ t || dig -p${DNS_PORT} A MICROSOFT.COM @127.0.0.1 | grep -Fq "NOERROR" || fail
|
|||
kill $(cat /tmp/dnscrypt-proxy.pidfile)
|
||||
sleep 5
|
||||
|
||||
section
|
||||
../dnscrypt-proxy/dnscrypt-proxy -loglevel 4 -config test-odoh-direct.toml -pidfile /tmp/odoh-direct.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-direct.pidfile)
|
||||
sleep 5
|
||||
|
||||
section
|
||||
../dnscrypt-proxy/dnscrypt-proxy -loglevel 3 -config test-odoh-proxied.toml -pidfile /tmp/odoh-proxied.pidfile &
|
||||
sleep 5
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
server_names = ['odohtarget']
|
||||
listen_addresses = ['127.0.0.1:5300']
|
||||
|
||||
[query_log]
|
||||
file = 'query.log'
|
||||
|
||||
[static]
|
||||
[static.'odohtarget']
|
||||
stamp = 'sdns://BQcAAAAAAAAAF29kb2guY2xvdWRmbGFyZS1kbnMuY29tCi9kbnMtcXVlcnk'
|
|
@ -6,10 +6,10 @@ file = 'query.log'
|
|||
|
||||
[static]
|
||||
[static.'odohtarget']
|
||||
stamp = 'sdns://BQcAAAAAAAAAEmNsb3VkZmxhcmUtZG5zLmNvbQovZG5zLXF1ZXJ5'
|
||||
stamp = 'sdns://BQcAAAAAAAAADm9kb2guY3J5cHRvLnN4Ci9kbnMtcXVlcnk'
|
||||
|
||||
[static.'odohrelay']
|
||||
stamp = 'sdns://hQcAAAAAAAAAACCi3jNJDEdtNW4tvHN8J3lpIklSa2Wrj7qaNCgEgci9_BpvZG9oLXJlbGF5LmVkZ2Vjb21wdXRlLmFwcAEv'
|
||||
stamp = 'sdns://hQcAAAAAAAAADDg5LjM4LjEzMS4zOAAYb2RvaC1ubC5hbGVrYmVyZy5uZXQ6NDQzBi9wcm94eQ'
|
||||
|
||||
[anonymized_dns]
|
||||
routes = [
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -13,9 +13,7 @@ cache = true
|
|||
[query_log]
|
||||
file = 'query.log'
|
||||
|
||||
|
||||
|
||||
[static]
|
||||
|
||||
[static.'myserver']
|
||||
stamp = 'sdns://AQcAAAAAAAAADjIxMi40Ny4yMjguMTM2IOgBuE6mBr-wusDOQ0RbsV66ZLAvo8SqMa4QY2oHkDJNHzIuZG5zY3J5cHQtY2VydC5mci5kbnNjcnlwdC5vcmc'
|
||||
stamp = 'sdns://AQcAAAAAAAAADjIxMi40Ny4yMjguMTM2IOgBuE6mBr-wusDOQ0RbsV66ZLAvo8SqMa4QY2oHkDJNHzIuZG5zY3J5cHQtY2VydC5mci5kbnNjcnlwdC5vcmc'
|
||||
|
|
|
@ -7,25 +7,23 @@ assignees: ''
|
|||
|
||||
---
|
||||
|
||||
## Please read before filling a bug report
|
||||
THE TRACKER IS DEDICATED TO KEEPING TRACK OF *BUGS*,
|
||||
preferably after they have been already discussed and confirmed to be reproducible.
|
||||
|
||||
This tracker is dedicated to tracking bugs, reproducible in the context described in the
|
||||
"Context" section.
|
||||
|
||||
Installation and configuration issues are not bugs, but individual assistance request.
|
||||
In order to ask for help, please use the discussions (Q&A) section instead:
|
||||
FOR ASSISTANCE, PLEASE CLOSE THIS FORM AND USE THE DISCUSSIONS SECTION INSTEAD:
|
||||
https://github.com/DNSCrypt/dnscrypt-proxy/discussions/categories/q-a
|
||||
|
||||
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.
|
||||
Reported bugs must reproducible in the context described in the "Context" section.
|
||||
|
||||
If you don't have any clear understanding of the issue or can't enumerate the steps to
|
||||
reproduce it, open a discussion instead:
|
||||
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
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
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:
|
||||
|
|
|
@ -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).*"
|
|
@ -13,15 +13,20 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
|
||||
- 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
|
||||
|
|
|
@ -25,21 +25,21 @@ 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: Check out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
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
|
||||
go mod vendor
|
||||
cd .ci
|
||||
./ci-test.sh
|
||||
cd -
|
||||
|
@ -49,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: |
|
||||
|
@ -78,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 }}
|
||||
|
@ -87,3 +92,4 @@ jobs:
|
|||
dnscrypt-proxy/*.zip
|
||||
dnscrypt-proxy/*.tar.gz
|
||||
dnscrypt-proxy/*.minisig
|
||||
dnscrypt-proxy/*.msi
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
78
ChangeLog
78
ChangeLog
|
@ -1,11 +1,83 @@
|
|||
# Version 2.0.46beta1
|
||||
# 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.
|
||||
- Preliminary support for ODoH (Oblivious DoH) was added. Thanks to
|
||||
Chris Wood for his help on this!
|
||||
|
||||
# Version 2.0.45
|
||||
- Configuration changes (to be required in versions 2.1.x):
|
||||
|
|
33
LICENSE
33
LICENSE
|
@ -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.
|
||||
|
|
13
README.md
13
README.md
|
@ -2,14 +2,14 @@
|
|||
|
||||
[![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), [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.md).
|
||||
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/)
|
||||
|
@ -25,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, Anonymized DNS and ODoH
|
||||
* 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
|
||||
|
@ -38,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
|
||||
|
||||
|
@ -60,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
|
||||
|
@ -74,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
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
||||
```
|
|
@ -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
|
|
@ -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>
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jedisct1/dlog"
|
||||
|
@ -15,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) {
|
||||
|
@ -116,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
|
||||
}
|
||||
|
@ -138,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
|
||||
|
@ -175,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
|
||||
}
|
||||
|
|
|
@ -4,12 +4,14 @@ import (
|
|||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/jedisct1/dlog"
|
||||
)
|
||||
|
||||
type CryptoConstruction uint16
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
|
@ -92,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"`
|
||||
|
@ -99,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"`
|
||||
|
@ -114,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,
|
||||
|
@ -141,6 +146,7 @@ func newConfig() Config {
|
|||
LogMaxBackups: 1,
|
||||
TLSDisableSessionTickets: false,
|
||||
TLSCipherSuite: nil,
|
||||
TLSKeyLogFile: "",
|
||||
NetprobeTimeout: 60,
|
||||
OfflineMode: false,
|
||||
RefusedCodeInResponses: false,
|
||||
|
@ -155,6 +161,7 @@ func newConfig() Config {
|
|||
AnonymizedDNS: AnonymizedDNSConfig{
|
||||
DirectCertFallback: true,
|
||||
},
|
||||
CloakedPTR: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -254,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"`
|
||||
|
@ -285,6 +292,7 @@ type ConfigFlags struct {
|
|||
Resolve *string
|
||||
List *bool
|
||||
ListAll *bool
|
||||
IncludeRelays *bool
|
||||
JSONOutput *bool
|
||||
Check *bool
|
||||
ConfigFile *string
|
||||
|
@ -313,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 {
|
||||
|
@ -340,7 +352,10 @@ 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)
|
||||
|
@ -370,7 +385,9 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
|
|||
proxy.xTransport.tlsDisableSessionTickets = config.TLSDisableSessionTickets
|
||||
proxy.xTransport.tlsCipherSuite = config.TLSCipherSuite
|
||||
proxy.xTransport.mainProto = proxy.mainProto
|
||||
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.BootstrapResolvers) > 0 {
|
||||
|
@ -423,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
|
||||
|
@ -466,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
|
||||
|
@ -485,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
|
||||
|
||||
|
@ -617,6 +635,16 @@ 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")
|
||||
}
|
||||
|
@ -636,7 +664,9 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
|
|||
}
|
||||
|
||||
// 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
|
||||
|
||||
|
@ -687,8 +717,7 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
|
|||
} 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
|
||||
}
|
||||
|
@ -705,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 {
|
||||
|
@ -716,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)
|
||||
|
@ -725,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 && 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)
|
||||
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)
|
||||
}
|
||||
|
@ -748,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)
|
||||
|
@ -764,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,
|
||||
|
@ -830,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) {
|
||||
|
@ -861,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
|
||||
}
|
||||
|
@ -892,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)
|
||||
}
|
||||
|
|
|
@ -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) ||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
@ -19,4 +19,9 @@ connectivitycheck.android.com 64.233.162.100, 64.233.162.101, 64.233.162.102,
|
|||
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::
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -97,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.
|
||||
|
@ -118,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
|
||||
|
||||
|
@ -128,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
|
||||
|
@ -176,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
|
||||
|
@ -193,28 +213,34 @@ 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]
|
||||
|
||||
|
||||
## 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 the
|
||||
## for one-shot queries when retrieving the initial resolvers list and if
|
||||
## the system DNS configuration doesn't work.
|
||||
##
|
||||
## No user queries will ever be leaked through these resolvers, and they will
|
||||
|
@ -241,10 +267,20 @@ cert_refresh_delay = 240
|
|||
## 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.
|
||||
|
||||
bootstrap_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
|
||||
|
||||
|
@ -318,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
|
||||
|
||||
|
@ -331,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
|
||||
|
||||
|
||||
|
||||
|
@ -352,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
|
||||
|
||||
|
@ -360,6 +399,7 @@ reject_ttl = 600
|
|||
## TTL used when serving entries in cloaking-rules.txt
|
||||
|
||||
# cloak_ttl = 600
|
||||
# cloak_ptr = false
|
||||
|
||||
|
||||
|
||||
|
@ -436,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'
|
||||
|
@ -451,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']
|
||||
|
||||
|
||||
|
||||
|
@ -478,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:
|
||||
|
@ -508,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'
|
||||
|
||||
|
||||
|
||||
|
@ -536,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
|
||||
|
@ -564,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'
|
||||
|
||||
|
||||
|
||||
|
@ -585,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'
|
||||
|
||||
|
||||
|
||||
|
@ -624,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'}]
|
||||
|
||||
|
||||
|
||||
|
@ -660,55 +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 = ''
|
||||
|
||||
## ODoH (Oblivious DoH) servers and relays
|
||||
### ODoH (Oblivious DoH) servers and relays
|
||||
|
||||
# [sources.'odoh']
|
||||
# urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/odoh.md', 'https://download.dnscrypt.info/resolvers-list/v3/odoh.md', 'https://ipv6.download.dnscrypt.info/resolvers-list/v3/odoh.md', 'https://download.dnscrypt.net/resolvers-list/v3/odoh.md']
|
||||
# cache_file = 'odoh.md'
|
||||
# minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
|
||||
# refresh_delay = 24
|
||||
# prefix = ''
|
||||
# [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 over DNSCrypt - https://quad9.net/
|
||||
### 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-"
|
||||
|
||||
|
||||
#########################################
|
||||
|
@ -717,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']
|
||||
|
||||
|
@ -736,15 +792,14 @@ 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='*', client_cert='client.crt', client_key='client.key' }
|
||||
# ]
|
||||
|
@ -792,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
|
||||
|
||||
|
@ -827,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']
|
||||
|
||||
|
||||
|
@ -847,5 +904,5 @@ skip_incompatible = false
|
|||
|
||||
[static]
|
||||
|
||||
# [static.'myserver']
|
||||
# stamp = 'sdns://AQcAAAAAAAAAAAAQMi5kbnNjcnlwdC1jZXJ0Lg'
|
||||
# [static.myserver]
|
||||
# stamp = 'sdns://AQcAAAAAAAAAAAAQMi5kbnNjcnlwdC1jZXJ0Lg'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
AppVersion = "2.0.46-beta1"
|
||||
AppVersion = "2.1.5"
|
||||
DefaultConfigFileName = "dnscrypt-proxy.toml"
|
||||
)
|
||||
|
||||
|
@ -27,13 +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 {
|
||||
|
@ -46,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")
|
||||
|
@ -60,6 +66,10 @@ func main() {
|
|||
os.Exit(0)
|
||||
}
|
||||
|
||||
if fullexecpath, err := os.Executable(); err == nil {
|
||||
WarnIfMaybeWritableByOtherUsers(fullexecpath)
|
||||
}
|
||||
|
||||
app := &App{
|
||||
flags: &flags,
|
||||
}
|
||||
|
@ -124,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)
|
||||
|
@ -139,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
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package main
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -5,12 +5,14 @@ import (
|
|||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/jedisct1/dlog"
|
||||
hpkecompact "github.com/jedisct1/go-hpke-compact"
|
||||
)
|
||||
|
||||
const (
|
||||
odohVersion = uint16(0xff06)
|
||||
maxODoHConfigs = 10
|
||||
odohVersion = uint16(0x0001)
|
||||
odohTestVersion = uint16(0xff06)
|
||||
maxODoHConfigs = 10
|
||||
)
|
||||
|
||||
type ODoHTargetConfig struct {
|
||||
|
@ -62,7 +64,7 @@ func parseODoHTargetConfig(config []byte) (ODoHTargetConfig, error) {
|
|||
|
||||
func parseODoHTargetConfigs(configs []byte) ([]ODoHTargetConfig, error) {
|
||||
if len(configs) <= 2 {
|
||||
return nil, fmt.Errorf("No configs")
|
||||
return nil, fmt.Errorf("Server didn't return any ODoH configurations")
|
||||
}
|
||||
length := binary.BigEndian.Uint16(configs)
|
||||
if len(configs) != int(length)+2 {
|
||||
|
@ -77,7 +79,10 @@ func parseODoHTargetConfigs(configs []byte) ([]ODoHTargetConfig, error) {
|
|||
}
|
||||
configVersion := binary.BigEndian.Uint16(configs[offset : offset+2])
|
||||
configLength := binary.BigEndian.Uint16(configs[offset+2 : offset+4])
|
||||
if configVersion == odohVersion {
|
||||
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)
|
||||
|
@ -176,7 +181,7 @@ func (q ODoHQuery) decryptResponse(response []byte) ([]byte, error) {
|
|||
responseLength := binary.BigEndian.Uint16(responsePlaintext[0:2])
|
||||
valid := 1
|
||||
for i := 4 + int(responseLength); i < len(responsePlaintext); i++ {
|
||||
valid = valid & subtle.ConstantTimeByteEq(response[i], 0x00)
|
||||
valid &= subtle.ConstantTimeByteEq(response[i], 0x00)
|
||||
}
|
||||
if valid != 1 {
|
||||
return nil, fmt.Errorf("Malformed response")
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -29,28 +29,24 @@ func (plugin *PluginAllowName) Description() string {
|
|||
|
||||
func (plugin *PluginAllowName) Init(proxy *Proxy) error {
|
||||
dlog.Noticef("Loading the set of allowed names from [%s]", proxy.allowNameFile)
|
||||
bin, err := ReadTextFile(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
|
||||
}
|
||||
parts := strings.Fields(line)
|
||||
parts := strings.Split(line, "@")
|
||||
timeRangeName := ""
|
||||
if len(parts) == 2 {
|
||||
if timeRangeParts := strings.Split(parts[1], "@"); len(timeRangeParts) == 2 {
|
||||
timeRangeName = strings.TrimSpace(timeRangeParts[1])
|
||||
} else {
|
||||
dlog.Errorf("Syntax error in allowed names at line %d", 1+lineNo)
|
||||
continue
|
||||
}
|
||||
line = strings.TrimSpace(parts[0])
|
||||
timeRangeName = strings.TrimSpace(parts[1])
|
||||
} else if len(parts) > 2 {
|
||||
dlog.Errorf("Syntax error in allowed names at line %d", 1+lineNo)
|
||||
dlog.Errorf("Syntax error in allowed names at line %d -- Unexpected @ character", 1+lineNo)
|
||||
continue
|
||||
}
|
||||
var weeklyRanges *WeeklyRanges
|
||||
|
@ -100,10 +96,14 @@ func (plugin *PluginAllowName) Eval(pluginsState *PluginsState, msg *dns.Msg) er
|
|||
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" {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -44,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" {
|
||||
|
@ -71,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"
|
||||
|
@ -84,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
|
||||
}
|
||||
|
@ -92,22 +95,18 @@ 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
|
||||
}
|
||||
parts := strings.Fields(line)
|
||||
parts := strings.Split(line, "@")
|
||||
timeRangeName := ""
|
||||
if len(parts) == 2 {
|
||||
if timeRangeParts := strings.Split(parts[1], "@"); len(timeRangeParts) == 2 {
|
||||
timeRangeName = strings.TrimSpace(timeRangeParts[1])
|
||||
} else {
|
||||
dlog.Errorf("Syntax error in block rules at line %d", 1+lineNo)
|
||||
continue
|
||||
}
|
||||
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", 1+lineNo)
|
||||
dlog.Errorf("Syntax error in block rules at line %d -- Unexpected @ character", 1+lineNo)
|
||||
continue
|
||||
}
|
||||
var weeklyRanges *WeeklyRanges
|
||||
|
@ -152,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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -6,8 +6,7 @@ import (
|
|||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type PluginBlockUnqualified struct {
|
||||
}
|
||||
type PluginBlockUnqualified struct{}
|
||||
|
||||
func (plugin *PluginBlockUnqualified) Name() string {
|
||||
return "block_unqualified"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -9,8 +9,7 @@ import (
|
|||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type PluginFirefox struct {
|
||||
}
|
||||
type PluginFirefox struct{}
|
||||
|
||||
func (plugin *PluginFirefox) Name() string {
|
||||
return "firefox"
|
||||
|
|
|
@ -29,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,9 +49,16 @@ func (plugin *PluginForward) Init(proxy *Proxy) error {
|
|||
var servers []string
|
||||
for _, server := range strings.Split(serversStr, ",") {
|
||||
server = strings.TrimSpace(server)
|
||||
if net.ParseIP(server) != nil {
|
||||
server = fmt.Sprintf("%s:%d", server, 53)
|
||||
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 {
|
||||
|
@ -82,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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,12 +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 {
|
||||
clientIPStr = (*pluginsState.clientAddr).(*net.TCPAddr).IP.String()
|
||||
}
|
||||
qName := pluginsState.qName
|
||||
|
||||
if pluginsState.cacheHit {
|
||||
|
@ -86,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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -68,9 +68,13 @@ type Proxy struct {
|
|||
nxLogFile 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,10 +98,6 @@ type Proxy struct {
|
|||
anonDirectCertFallback bool
|
||||
pluginBlockUndelegated bool
|
||||
child bool
|
||||
daemonize bool
|
||||
requiredProps stamps.ServerInformalProperties
|
||||
ServerNames []string
|
||||
DisabledServerNames []string
|
||||
SourceIPv4 bool
|
||||
SourceIPv6 bool
|
||||
SourceDNSCrypt bool
|
||||
|
@ -117,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)
|
||||
}
|
||||
|
@ -140,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)
|
||||
}
|
||||
|
@ -185,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)
|
||||
}
|
||||
|
@ -201,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)
|
||||
}
|
||||
|
@ -233,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
|
||||
|
@ -242,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")
|
||||
|
@ -284,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
|
||||
|
@ -311,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))
|
||||
}
|
||||
|
@ -369,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)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
@ -388,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)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
@ -414,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
|
||||
}
|
||||
|
@ -428,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
|
||||
}
|
||||
|
@ -442,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
|
||||
}
|
||||
|
@ -476,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
|
||||
|
@ -485,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())
|
||||
}
|
||||
|
@ -514,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
|
||||
|
@ -523,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())
|
||||
}
|
||||
|
@ -566,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 := "-"
|
||||
|
@ -586,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 {
|
||||
|
@ -599,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
|
||||
|
@ -615,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" {
|
||||
|
@ -633,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)
|
||||
}
|
||||
|
@ -654,7 +738,7 @@ 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)
|
||||
|
@ -662,17 +746,18 @@ func (proxy *Proxy) processIncomingQuery(clientProto string, serverProto string,
|
|||
serverInfo.noticeBegin(proxy)
|
||||
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
|
||||
|
@ -683,7 +768,7 @@ func (proxy *Proxy) processIncomingQuery(clientProto string, serverProto string,
|
|||
} else if serverInfo.Proto == stamps.StampProtoTypeODoHTarget {
|
||||
tid := TransactionID(query)
|
||||
if len(serverInfo.odohTargetConfigs) == 0 {
|
||||
return
|
||||
return response
|
||||
}
|
||||
target := serverInfo.odohTargetConfigs[rand.Intn(len(serverInfo.odohTargetConfigs))]
|
||||
odohQuery, err := target.encryptQuery(query)
|
||||
|
@ -702,7 +787,10 @@ func (proxy *Proxy) processIncomingQuery(clientProto string, serverProto string,
|
|||
dlog.Warnf("Failed to decrypt response from [%v]", serverName)
|
||||
response = nil
|
||||
}
|
||||
} else if responseCode == 401 {
|
||||
} 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 {
|
||||
|
@ -710,6 +798,7 @@ func (proxy *Proxy) processIncomingQuery(clientProto string, serverProto string,
|
|||
// Failed to refresh the proxy server information.
|
||||
dlog.Noticef("Key update failed for [%v]", serverName)
|
||||
serverInfo.noticeFailure(proxy)
|
||||
clocksmith.Sleep(10 * time.Second)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
@ -726,7 +815,7 @@ func (proxy *Proxy) processIncomingQuery(clientProto string, serverProto string,
|
|||
pluginsState.returnCode = PluginsReturnCodeNetworkError
|
||||
pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals)
|
||||
serverInfo.noticeFailure(proxy)
|
||||
return
|
||||
return response
|
||||
}
|
||||
} else {
|
||||
dlog.Fatal("Unsupported protocol")
|
||||
|
@ -735,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 {
|
||||
|
@ -778,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 {
|
||||
|
@ -786,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)
|
||||
|
@ -803,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)
|
||||
|
|
|
@ -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,8 +139,9 @@ 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 {
|
||||
fmt.Printf("[%v]", err)
|
||||
break
|
||||
}
|
||||
if response.Rcode == dns.RcodeSuccess {
|
||||
|
@ -133,6 +160,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 +176,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 +200,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 +220,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 +242,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 +272,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 +296,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 +342,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 +362,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
|
||||
}
|
||||
|
|
|
@ -67,6 +67,7 @@ type ServerInfo struct {
|
|||
|
||||
type LBStrategy interface {
|
||||
getCandidate(serversCount int) int
|
||||
getActiveCount(serversCount int) int
|
||||
}
|
||||
|
||||
type LBStrategyP2 struct{}
|
||||
|
@ -75,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 {
|
||||
|
@ -126,7 +147,12 @@ type ServersInfo struct {
|
|||
}
|
||||
|
||||
func NewServersInfo() ServersInfo {
|
||||
return ServersInfo{lbStrategy: DefaultLBStrategy, lbEstimator: true, registeredServers: make([]RegisteredServer, 0), registeredRelays: 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) {
|
||||
|
@ -197,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
|
||||
}(®isteredServers[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
|
||||
|
@ -225,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]
|
||||
|
@ -265,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
|
||||
|
@ -297,12 +356,13 @@ 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 implementeed for non-DNSCrypt relays
|
||||
// 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 {
|
||||
|
@ -316,7 +376,7 @@ func findFarthestRoute(proxy *Proxy, name string, relayStamps []stamps.ServerSta
|
|||
return nil
|
||||
}
|
||||
|
||||
// Anonyimized DNSCrypt relays
|
||||
// Anonymized DNSCrypt relays
|
||||
serverAddrStr, _ := ExtractHostAndPort(server.stamp.ServerAddrStr, 443)
|
||||
serverAddr := net.ParseIP(serverAddrStr)
|
||||
if serverAddr == nil {
|
||||
|
@ -360,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
|
||||
|
@ -371,17 +442,30 @@ 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)
|
||||
if relayStamp.Proto == relayProto {
|
||||
relayStamps = append(relayStamps, relayStamp)
|
||||
relayStampToName[relayStamp.String()] = relayName
|
||||
}
|
||||
} else if relayName == "*" {
|
||||
proxy.serversInfo.RLock()
|
||||
for _, registeredServer := range proxy.serversInfo.registeredRelays {
|
||||
relayStamps = append(relayStamps, registeredServer.stamp)
|
||||
if registeredServer.stamp.Proto == relayProto {
|
||||
relayStamps = append(relayStamps, registeredServer.stamp)
|
||||
relayStampToName[registeredServer.stamp.String()] = registeredServer.name
|
||||
}
|
||||
}
|
||||
proxy.serversInfo.RUnlock()
|
||||
wildcard = true
|
||||
|
@ -389,14 +473,9 @@ func route(proxy *Proxy, name string) (*Relay, error) {
|
|||
} else {
|
||||
proxy.serversInfo.RLock()
|
||||
for _, registeredServer := range proxy.serversInfo.registeredRelays {
|
||||
if registeredServer.name == relayName {
|
||||
relayStamps = append(relayStamps, registeredServer.stamp)
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, registeredServer := range proxy.serversInfo.registeredServers {
|
||||
if registeredServer.name == relayName {
|
||||
if registeredServer.name == relayName && registeredServer.stamp.Proto == relayProto {
|
||||
relayStamps = append(relayStamps, registeredServer.stamp)
|
||||
relayStampToName[registeredServer.stamp.String()] = relayName
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -404,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 {
|
||||
|
@ -415,15 +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
|
||||
proxy.serversInfo.RLock()
|
||||
for _, registeredServer := range proxy.serversInfo.registeredRelays {
|
||||
if registeredServer.stamp.ServerAddrStr == relayCandidateStamp.ServerAddrStr {
|
||||
relayName = registeredServer.name
|
||||
break
|
||||
}
|
||||
}
|
||||
proxy.serversInfo.RUnlock()
|
||||
relayName := relayStampToName[relayCandidateStamp.String()]
|
||||
switch relayCandidateStamp.Proto {
|
||||
case stamps.StampProtoTypeDNSCrypt, stamps.StampProtoTypeDNSCryptRelay:
|
||||
relayUDPAddr, err := net.ResolveUDPAddr("udp", relayCandidateStamp.ServerAddrStr)
|
||||
|
@ -435,9 +507,14 @@ 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:
|
||||
relayBaseURL, err := url.Parse("https://" + url.PathEscape(relayCandidateStamp.ProviderName) + relayCandidateStamp.Path)
|
||||
relayBaseURL, err := url.Parse(
|
||||
"https://" + url.PathEscape(relayCandidateStamp.ProviderName) + relayCandidateStamp.Path,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -460,7 +537,8 @@ func route(proxy *Proxy, name string) (*Relay, error) {
|
|||
if len(relayCandidateStamp.ServerAddrStr) > 0 {
|
||||
ipOnly, _ := ExtractHostAndPort(relayCandidateStamp.ServerAddrStr, -1)
|
||||
if ip := ParseIP(ipOnly); ip != nil {
|
||||
proxy.xTransport.saveCachedIP(relayCandidateStamp.ProviderName, ip, -1*time.Second)
|
||||
host, _ := ExtractHostAndPort(relayCandidateStamp.ProviderName, -1)
|
||||
proxy.xTransport.saveCachedIP(host, ip, -1*time.Second)
|
||||
}
|
||||
}
|
||||
dlog.Noticef("Anonymizing queries for [%v] via [%v]", name, relayName)
|
||||
|
@ -473,7 +551,7 @@ func route(proxy *Proxy, name string) (*Relay, error) {
|
|||
|
||||
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)
|
||||
}
|
||||
|
@ -488,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
|
||||
}
|
||||
|
@ -496,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
|
||||
|
@ -544,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()
|
||||
|
@ -567,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()
|
||||
|
@ -585,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{
|
||||
|
@ -624,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
|
||||
|
@ -690,27 +779,37 @@ func fetchTargetConfigsFromWellKnown(proxy *Proxy, url *url.URL) ([]ODoHTargetCo
|
|||
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 || len(odohTargetConfigs) == 0 {
|
||||
return ServerInfo{}, fmt.Errorf("[%s] does not have an ODoH configuration", name)
|
||||
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)
|
||||
relay, err := route(proxy, name, stamp.Proto)
|
||||
if err != nil {
|
||||
return ServerInfo{}, err
|
||||
}
|
||||
if relay == nil || relay.ODoH == nil {
|
||||
relay = nil
|
||||
}
|
||||
|
||||
if relay == nil {
|
||||
dlog.Warnf("No ODoH relay defined for [%v]", name)
|
||||
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 {
|
||||
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")
|
||||
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,
|
||||
|
@ -722,10 +821,7 @@ func _fetchODoHTargetInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, i
|
|||
odohTargetConfigs[i], odohTargetConfigs[j] = odohTargetConfigs[j], odohTargetConfigs[i]
|
||||
})
|
||||
for _, odohTargetConfig := range odohTargetConfigs {
|
||||
url := targetURL
|
||||
if relay != nil {
|
||||
url = relay.ODoH.URL
|
||||
}
|
||||
url := relay.ODoH.URL
|
||||
|
||||
query := dohTestPacket(0xcafe)
|
||||
odohQuery, err := odohTargetConfig.encryptQuery(query)
|
||||
|
@ -748,7 +844,12 @@ func _fetchODoHTargetInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, i
|
|||
continue
|
||||
}
|
||||
|
||||
responseBody, responseCode, tls, rtt, err := proxy.xTransport.ObliviousDoHQuery(useGet, url, odohQuery.odohMessage, proxy.timeout)
|
||||
responseBody, responseCode, tls, rtt, err := proxy.xTransport.ObliviousDoHQuery(
|
||||
useGet,
|
||||
url,
|
||||
odohQuery.odohMessage,
|
||||
proxy.timeout,
|
||||
)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
@ -770,42 +871,57 @@ func _fetchODoHTargetInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, i
|
|||
if msg.Rcode != dns.RcodeNameError {
|
||||
dlog.Criticalf("[%s] may be a lying resolver", name)
|
||||
}
|
||||
|
||||
protocol := tls.NegotiatedProtocol
|
||||
if len(protocol) == 0 {
|
||||
protocol = "http/1.x"
|
||||
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, tls.Version, protocol, tls.CipherSuite)
|
||||
dlog.Infof(
|
||||
"[%s] TLS version: %x - Protocol: %v - Cipher suite: %v",
|
||||
name,
|
||||
tlsVersion,
|
||||
protocol,
|
||||
tlsCipherSuite,
|
||||
)
|
||||
showCerts := proxy.showCerts
|
||||
found := false
|
||||
var wantedHash [32]byte
|
||||
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 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 {
|
||||
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 !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")
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build android
|
||||
// +build android
|
||||
|
||||
package main
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !linux && !windows
|
||||
// +build !linux,!windows
|
||||
|
||||
package main
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !freebsd && !openbsd && !windows && !darwin && !linux
|
||||
// +build !freebsd,!openbsd,!windows,!darwin,!linux
|
||||
|
||||
package main
|
||||
|
|
|
@ -3,7 +3,6 @@ package main
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"os"
|
||||
|
@ -33,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
|
||||
|
@ -41,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) {
|
||||
|
@ -130,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)
|
||||
|
@ -166,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 {
|
||||
|
@ -196,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
|
||||
|
@ -214,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
|
||||
}
|
||||
|
@ -239,7 +262,7 @@ 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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package main
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !android
|
||||
// +build !android
|
||||
|
||||
package main
|
||||
|
|
|
@ -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,6 +24,8 @@ 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"
|
||||
)
|
||||
|
@ -46,21 +49,32 @@ 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
|
||||
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 {
|
||||
|
@ -69,6 +83,7 @@ func NewXTransport() *XTransport {
|
|||
}
|
||||
xTransport := XTransport{
|
||||
cachedIPs: CachedIPs{cache: make(map[string]*CachedIPItem)},
|
||||
altSupport: AltSupport{cache: make(map[string]uint16)},
|
||||
keepAlive: DefaultKeepAlive,
|
||||
timeout: DefaultTimeout,
|
||||
bootstrapResolvers: []string{DefaultBootstrapResolver},
|
||||
|
@ -78,6 +93,7 @@ func NewXTransport() *XTransport {
|
|||
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 {
|
||||
|
@ -164,11 +180,15 @@ func (xTransport *XTransport) rebuildTransport() {
|
|||
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 := ioutil.ReadFile(clientCreds.rootCA)
|
||||
additionalCaCert, err := os.ReadFile(clientCreds.rootCA)
|
||||
if err != nil {
|
||||
dlog.Fatal(err)
|
||||
}
|
||||
|
@ -177,7 +197,7 @@ func (xTransport *XTransport) rebuildTransport() {
|
|||
|
||||
if certPool != nil {
|
||||
// Some operating systems don't include Let's Encrypt ISRG Root X1 certificate yet
|
||||
var letsEncryptX1Cert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
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)
|
||||
|
@ -187,7 +207,12 @@ func (xTransport *XTransport) rebuildTransport() {
|
|||
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)
|
||||
dlog.Fatalf(
|
||||
"Unable to use certificate [%v] (key: [%v]): %v",
|
||||
clientCreds.clientCert,
|
||||
clientCreds.clientKey,
|
||||
err,
|
||||
)
|
||||
}
|
||||
tlsClientConfig.Certificates = []tls.Certificate{cert}
|
||||
}
|
||||
|
@ -200,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) {
|
||||
|
@ -235,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{}
|
||||
|
@ -280,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 bootstrap 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 bootstrap resolver %s[%s]: %v", host, proto, resolver, err)
|
||||
dlog.Infof("Unable to resolve [%s] using resolver [%s] (%s): %v", host, resolver, proto, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -310,19 +408,37 @@ 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 bootstrap resolvers over %s", host, proto)
|
||||
} else {
|
||||
dlog.Debugf("Resolving [%s] using bootstrap 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.bootstrapResolvers)
|
||||
if err == nil {
|
||||
|
@ -346,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, int, *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}
|
||||
|
@ -372,14 +522,19 @@ 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, 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 `bootstrap_resolvers` has been set to resolvers that can be reached", host)
|
||||
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,
|
||||
URL: url,
|
||||
|
@ -388,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)
|
||||
|
@ -400,35 +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, 0, 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, resp.StatusCode, tls, 0, err
|
||||
return nil, statusCode, tls, rtt, err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return bin, resp.StatusCode, tls, rtt, err
|
||||
return bin, statusCode, tls, rtt, err
|
||||
}
|
||||
|
||||
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)
|
||||
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, int, *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) dohLikeQuery(dataType string, useGet bool, url *url.URL, body []byte, timeout time.Duration) ([]byte, int, *tls.ConnectionState, time.Duration, error) {
|
||||
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)
|
||||
|
@ -440,10 +663,20 @@ func (xTransport *XTransport) dohLikeQuery(dataType string, useGet bool, url *ur
|
|||
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) {
|
||||
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) {
|
||||
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
64
go.mod
|
@ -1,28 +1,52 @@
|
|||
module github.com/DNSCrypt/dnscrypt-proxy
|
||||
module github.com/dnscrypt/dnscrypt-proxy
|
||||
|
||||
go 1.16
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/BurntSushi/toml v1.4.0
|
||||
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-20210414164033-fdb47fe0c84c
|
||||
github.com/jedisct1/go-hpke-compact v0.0.0-20210607160958-a8af3a0d4a3c
|
||||
github.com/jedisct1/go-minisign v0.0.0-20210414164026-819d7e2534ac
|
||||
github.com/jedisct1/xsecretbox v0.0.0-20210330110434-7cb86b57caf0
|
||||
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.42
|
||||
github.com/powerman/check v1.3.1
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5
|
||||
golang.org/x/sys v0.0.0-20210603125802-9665404d3644
|
||||
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.44.0
|
||||
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-20240506185415-9bf2ced13842 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/tools v0.21.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
|
||||
)
|
||||
|
|
218
go.sum
218
go.sum
|
@ -1,153 +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/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
|
||||
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
|
||||
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/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/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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/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-20210414164033-fdb47fe0c84c h1:C4YliYa18NEFs92gIYDhcnkAZL3dea0fmqDwJ9wCvjk=
|
||||
github.com/jedisct1/go-dnsstamps v0.0.0-20210414164033-fdb47fe0c84c/go.mod h1:t35n6rsPE3nD3RXbc5hI5Ax1ci/SSYTpx0BdMXh/1aE=
|
||||
github.com/jedisct1/go-hpke-compact v0.0.0-20210607160958-a8af3a0d4a3c h1:FYRBSw8+E9zw9hA7IOc0G6TDRDdbpLHlOx/avdmEty4=
|
||||
github.com/jedisct1/go-hpke-compact v0.0.0-20210607160958-a8af3a0d4a3c/go.mod h1:8jLxHdP84UJy7CNm4uzXJCl1DJkRee53TuE8UbXuoWs=
|
||||
github.com/jedisct1/go-minisign v0.0.0-20210414164026-819d7e2534ac h1:eHNaWGqKp8Xjf/yyzfhgO4bmSpiScZg+vCpjdhr2x4k=
|
||||
github.com/jedisct1/go-minisign v0.0.0-20210414164026-819d7e2534ac/go.mod h1:oPTyITpvr7hPx/9w76gWrgbZwbb+7gZ9/On8hFc+LNE=
|
||||
github.com/jedisct1/xsecretbox v0.0.0-20210330110434-7cb86b57caf0 h1:URIhPa4hmOo+YgZx58jLy/LyeaEBl2B/Vbfvy1gafp8=
|
||||
github.com/jedisct1/xsecretbox v0.0.0-20210330110434-7cb86b57caf0/go.mod h1:kB9Pj7ys7y1jA+GB1zruSdShvPzZny9SWwn3qDEXB0o=
|
||||
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.42 h1:gWGe42RGaIqXQZ+r3WUGEKBEtvPHY2SXo4dqixDNxuY=
|
||||
github.com/miekg/dns v1.1.42/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||
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.1 h1:86xiEjMNn0K4b3bhQsLfrLqJb0DWLXHBK3wBd5PoJH4=
|
||||
github.com/powerman/check v1.3.1/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.44.0 h1:So5wOr7jyO4vzL2sd8/pD9Kesciv91zSk8BoFngItQ0=
|
||||
github.com/quic-go/quic-go v0.44.0/go.mod h1:z4cx/9Ny9UtGITIPzmPTXh1ULfOyWh4qGQlpnPcWmek=
|
||||
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-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
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/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-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.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-20190529164535-6a60838ec259/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/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210603125802-9665404d3644 h1:CA1DEQ4NdKphKeL70tvsWNdT5oFh1lOjihRcEDROi0I=
|
||||
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-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/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/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/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
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=
|
||||
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=
|
||||
|
|
|
@ -53,8 +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
|
||||
|
||||
# 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
|
||||
|
@ -92,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
|
||||
|
@ -113,10 +113,13 @@ https://hostfiles.frogeye.fr/firstparty-trackers.txt
|
|||
# https://block.energized.pro/blu/formats/domains.txt
|
||||
|
||||
# OISD.NL - Blocks ads, phishing, malware, tracking and more. WARNING: this is a huge list.
|
||||
# https://dbl.oisd.nl/
|
||||
# https://dblw.oisd.nl/
|
||||
|
||||
# OISD.NL (smaller subset) - Blocks ads, phishing, malware, tracking and more. Tries to miminize false positives.
|
||||
https://hosts.oisd.nl/basic/
|
||||
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
|
||||
|
@ -131,6 +134,7 @@ https://hosts.oisd.nl/basic/
|
|||
# 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
|
||||
|
@ -138,11 +142,13 @@ https://hosts.oisd.nl/basic/
|
|||
|
||||
# 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
|
||||
|
@ -150,9 +156,5 @@ https://hosts.oisd.nl/basic/
|
|||
# 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
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ 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 = {}
|
||||
|
@ -65,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,})$"
|
||||
)
|
||||
|
@ -75,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):
|
||||
|
@ -92,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, "
|
||||
|
@ -120,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")
|
||||
|
@ -262,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",
|
||||
)
|
||||
|
|
|
@ -1,5 +1,2 @@
|
|||
TAGS
|
||||
tags
|
||||
.*.swp
|
||||
tomlcheck/tomlcheck
|
||||
toml.test
|
||||
/toml.test
|
||||
/toml-test
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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.18 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.
|
||||
|
|
|
@ -1,157 +1,210 @@
|
|||
package toml
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"io/fs"
|
||||
"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
|
||||
UnmarshalTOML(any) 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 any) 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 any) (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 any) (MetaData, error) {
|
||||
fp, err := os.Open(path)
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
defer fp.Close()
|
||||
return NewDecoder(fp).Decode(v)
|
||||
}
|
||||
|
||||
// DecodeFS reads the contents of a file from [fs.FS] and decodes it with
|
||||
// [Decode].
|
||||
func DecodeFS(fsys fs.FS, path string, v any) (MetaData, error) {
|
||||
fp, err := fsys.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{}
|
||||
undecoded any
|
||||
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 any) (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, any, 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 := io.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.)
|
||||
func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error {
|
||||
// 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 any) 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 {
|
||||
|
||||
func (md *MetaData) unify(data any, 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,48 +216,32 @@ 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 {
|
||||
err := v.UnmarshalTOML(data)
|
||||
if err != nil {
|
||||
return md.parseErr(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
switch k {
|
||||
case reflect.Ptr:
|
||||
elem := reflect.New(rv.Type().Elem())
|
||||
err := md.unify(data, reflect.Indirect(elem))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rv.Set(elem)
|
||||
return nil
|
||||
case reflect.Struct:
|
||||
return md.unifyStruct(data, rv)
|
||||
case reflect.Map:
|
||||
|
@ -218,27 +255,23 @@ 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 {
|
||||
tmap, ok := mapping.(map[string]interface{})
|
||||
func (md *MetaData) unifyStruct(mapping any, rv reflect.Value) error {
|
||||
tmap, ok := mapping.(map[string]any)
|
||||
if !ok {
|
||||
if mapping == nil {
|
||||
return nil
|
||||
}
|
||||
return e("type mismatch for %s: expected table but found %T",
|
||||
rv.Type().String(), mapping)
|
||||
return md.e("type mismatch for %s: expected table but found %s", rv.Type().String(), fmtType(mapping))
|
||||
}
|
||||
|
||||
for key, datum := range tmap {
|
||||
|
@ -259,74 +292,88 @@ 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
|
||||
tmap, ok := mapping.(map[string]interface{})
|
||||
func (md *MetaData) unifyMap(mapping any, 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]any)
|
||||
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
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
|
||||
func (md *MetaData) unifyArray(data any, rv reflect.Value) error {
|
||||
datav := reflect.ValueOf(data)
|
||||
if datav.Kind() != reflect.Slice {
|
||||
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)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error {
|
||||
func (md *MetaData) unifySlice(data any, rv reflect.Value) error {
|
||||
datav := reflect.ValueOf(data)
|
||||
if datav.Kind() != reflect.Slice {
|
||||
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 +384,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 any, 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 {
|
||||
func (md *MetaData) unifyFloat64(data any, 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,73 +431,85 @@ func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
return 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)
|
||||
}
|
||||
}
|
||||
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")
|
||||
if (rvk == reflect.Float32 && (num < -maxSafeFloat32Int || num > maxSafeFloat32Int)) ||
|
||||
(rvk == reflect.Float64 && (num < -maxSafeFloat64Int || num > maxSafeFloat64Int)) {
|
||||
return md.parseErr(errUnsafeFloat{i: num, size: rvk.String()})
|
||||
}
|
||||
rv.SetFloat(float64(num))
|
||||
return nil
|
||||
}
|
||||
return badtype("integer", data)
|
||||
|
||||
return md.badtype("float", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error {
|
||||
func (md *MetaData) unifyInt(data any, rv reflect.Value) error {
|
||||
_, 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(int64(dur))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
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 any, rv reflect.Value) error {
|
||||
if b, ok := data.(bool); ok {
|
||||
rv.SetBool(b)
|
||||
return nil
|
||||
}
|
||||
return badtype("boolean", data)
|
||||
return md.badtype("boolean", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error {
|
||||
func (md *MetaData) unifyAnything(data any, rv reflect.Value) error {
|
||||
rv.Set(reflect.ValueOf(data))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error {
|
||||
func (md *MetaData) unifyText(data any, 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,30 +526,62 @@ 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
|
||||
return md.parseErr(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) badtype(dst string, data any) error {
|
||||
return md.e("incompatible types: TOML value has type %s; destination has type %s", fmtType(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 ...any) 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 {
|
||||
func rvalue(v any) 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 +597,17 @@ 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)
|
||||
// fmt %T with "interface {}" replaced with "any", which is far more readable.
|
||||
func fmtType(t any) string {
|
||||
return strings.ReplaceAll(fmt.Sprintf("%T", t), "interface {}", "any")
|
||||
}
|
||||
|
|
|
@ -1,121 +0,0 @@
|
|||
package toml
|
||||
|
||||
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.
|
||||
type MetaData struct {
|
||||
mapping map[string]interface{}
|
||||
types map[string]tomlType
|
||||
keys []Key
|
||||
decoded map[string]bool
|
||||
context Key // Used only during decoding.
|
||||
}
|
||||
|
||||
// IsDefined returns true if the key given exists in the TOML data. The key
|
||||
// should be specified hierarchially. e.g.,
|
||||
//
|
||||
// // access the TOML key 'a.b.c'
|
||||
// IsDefined("a", "b", "c")
|
||||
//
|
||||
// IsDefined will return false if an empty key given. Keys are case sensitive.
|
||||
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
|
||||
for _, k := range key {
|
||||
if hash, ok = hashOrVal.(map[string]interface{}); !ok {
|
||||
return false
|
||||
}
|
||||
if hashOrVal, ok = hash[k]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (md *MetaData) Type(key ...string) string {
|
||||
fullkey := strings.Join(key, ".")
|
||||
if typ, ok := md.types[fullkey]; ok {
|
||||
return typ.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.
|
||||
//
|
||||
// All keys returned are non-empty.
|
||||
func (md *MetaData) Keys() []Key {
|
||||
return md.keys
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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,
|
||||
// and so no keys will be considered decoded.
|
||||
//
|
||||
// In this sense, the Undecoded keys correspond to keys in the TOML document
|
||||
// that do not have a concrete type in your representation.
|
||||
func (md *MetaData) Undecoded() []Key {
|
||||
undecoded := make([]Key, 0, len(md.keys))
|
||||
for _, key := range md.keys {
|
||||
if !md.decoded[key.String()] {
|
||||
undecoded = append(undecoded, key)
|
||||
}
|
||||
}
|
||||
return undecoded
|
||||
}
|
|
@ -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
|
||||
|
||||
// DecodeReader is an alias for NewDecoder(r).Decode(v).
|
||||
//
|
||||
// Deprecated: use NewDecoder(reader).Decode(&value).
|
||||
func DecodeReader(r io.Reader, v any) (MetaData, error) { return NewDecoder(r).Decode(v) }
|
||||
|
||||
// PrimitiveDecode is an alias for MetaData.PrimitiveDecode().
|
||||
//
|
||||
// Deprecated: use MetaData.PrimitiveDecode.
|
||||
func PrimitiveDecode(primValue Primitive, v any) error {
|
||||
md := MetaData{decoded: make(map[string]struct{})}
|
||||
return md.unify(primValue.undecoded, rvalue(v))
|
||||
}
|
|
@ -1,27 +1,8 @@
|
|||
/*
|
||||
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
|
||||
//
|
||||
// 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
|
||||
|
|
|
@ -2,90 +2,149 @@ package toml
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"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
|
||||
// Marshaler is the interface implemented by types that can marshal themselves
|
||||
// into valid TOML.
|
||||
type Marshaler interface {
|
||||
MarshalTOML() ([]byte, error)
|
||||
}
|
||||
|
||||
// Marshal returns a TOML representation of the Go value.
|
||||
//
|
||||
// See [Encoder] for a description of the encoding process.
|
||||
func Marshal(v any) ([]byte, error) {
|
||||
buff := new(bytes.Buffer)
|
||||
if err := NewEncoder(buff).Encode(v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buff.Bytes(), nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
Indent string // string for a single indentation level; default is two spaces.
|
||||
hasWritten bool // written any output to w yet?
|
||||
w *bufio.Writer
|
||||
}
|
||||
|
||||
// NewEncoder returns a TOML encoder that encodes Go values to the io.Writer
|
||||
// given. By default, a single indentation level is 2 spaces.
|
||||
// NewEncoder create a new Encoder.
|
||||
func NewEncoder(w io.Writer) *Encoder {
|
||||
return &Encoder{
|
||||
w: bufio.NewWriter(w),
|
||||
Indent: " ",
|
||||
}
|
||||
return &Encoder{w: bufio.NewWriter(w), Indent: " "}
|
||||
}
|
||||
|
||||
// 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.)
|
||||
func (enc *Encoder) Encode(v interface{}) error {
|
||||
// An error is returned if the value given cannot be encoded to a valid TOML
|
||||
// document.
|
||||
func (enc *Encoder) Encode(v any) 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 +165,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 +184,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 +209,126 @@ 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) {
|
||||
if math.Signbit(f) {
|
||||
enc.wf("-")
|
||||
}
|
||||
enc.wf("nan")
|
||||
} else if math.IsInf(f, 0) {
|
||||
if math.Signbit(f) {
|
||||
enc.wf("-")
|
||||
}
|
||||
enc.wf("inf")
|
||||
} else {
|
||||
enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 32)))
|
||||
}
|
||||
case reflect.Float64:
|
||||
f := rv.Float()
|
||||
if math.IsNaN(f) {
|
||||
if math.Signbit(f) {
|
||||
enc.wf("-")
|
||||
}
|
||||
enc.wf("nan")
|
||||
} else if math.IsInf(f, 0) {
|
||||
if math.Signbit(f) {
|
||||
enc.wf("-")
|
||||
}
|
||||
enc.wf("inf")
|
||||
} 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: %s", fmtType(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 +337,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 +358,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 +405,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 +507,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 +593,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 +603,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 +676,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 +706,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 {
|
||||
func (enc *Encoder) wf(format string, v ...any) {
|
||||
_, err := fmt.Fprintf(enc.w, format, v...)
|
||||
if err != nil {
|
||||
encPanic(err)
|
||||
}
|
||||
enc.hasWritten = true
|
||||
|
@ -536,13 +747,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 +776,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
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,356 @@
|
|||
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, expandTab(lines[pe.Position.Line-3]))
|
||||
}
|
||||
if pe.Position.Line > 1 {
|
||||
fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-1, expandTab(lines[pe.Position.Line-2]))
|
||||
}
|
||||
|
||||
/// Expand tabs, so that the ^^^s are at the correct position, but leave
|
||||
/// "column 10-13" intact. Adjusting this to the visual column would be
|
||||
/// better, but we don't know the tabsize of the user in their editor, which
|
||||
/// can be 8, 4, 2, or something else. We can't know. So leaving it as the
|
||||
/// character index is probably the "most correct".
|
||||
expanded := expandTab(lines[pe.Position.Line-1])
|
||||
diff := len(expanded) - len(lines[pe.Position.Line-1])
|
||||
|
||||
fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line, expanded)
|
||||
fmt.Fprintf(b, "% 10s%s%s\n", "", strings.Repeat(" ", col+diff), 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
|
||||
}
|
||||
|
||||
func expandTab(s string) string {
|
||||
var (
|
||||
b strings.Builder
|
||||
l int
|
||||
fill = func(n int) string {
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
b[i] = ' '
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
)
|
||||
b.Grow(len(s))
|
||||
for _, r := range s {
|
||||
switch r {
|
||||
case '\t':
|
||||
tw := 8 - l%8
|
||||
b.WriteString(fill(tw))
|
||||
l += tw
|
||||
default:
|
||||
b.WriteRune(r)
|
||||
l += 1
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
type (
|
||||
errLexControl struct{ r rune }
|
||||
errLexEscape struct{ r rune }
|
||||
errLexUTF8 struct{ b byte }
|
||||
errParseDate struct{ v string }
|
||||
errLexInlineTableNL struct{}
|
||||
errLexStringNL struct{}
|
||||
errParseRange struct {
|
||||
i any // int or float
|
||||
size string // "int64", "uint16", etc.
|
||||
}
|
||||
errUnsafeFloat struct {
|
||||
i interface{} // float32 or float64
|
||||
size string // "float32" or "float64"
|
||||
}
|
||||
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 errParseDate) Error() string { return fmt.Sprintf("invalid datetime: %q", e.v) }
|
||||
func (e errParseDate) Usage() string { return usageDate }
|
||||
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 errUnsafeFloat) Error() string {
|
||||
return fmt.Sprintf("%v is out of the safe %s range", e.i, e.size)
|
||||
}
|
||||
func (e errUnsafeFloat) Usage() string { return usageUnsafeFloat }
|
||||
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 │ 65,535
|
||||
uint32 │ 0 │ 4,294,967,295
|
||||
uint64 │ 0 │ 1.8 × 10¹⁸
|
||||
|
||||
int refers to int32 on 32-bit systems and int64 on 64-bit systems.
|
||||
`
|
||||
|
||||
const usageUnsafeFloat = `
|
||||
This number is outside of the "safe" range for floating point numbers; whole
|
||||
(non-fractional) numbers outside the below range can not always be represented
|
||||
accurately in a float, leading to some loss of accuracy.
|
||||
|
||||
Explicitly mark a number as a fractional unit by adding ".0", which will incur
|
||||
some loss of accuracy; for example:
|
||||
|
||||
f = 2_000_000_000.0
|
||||
|
||||
Accuracy ranges:
|
||||
|
||||
float32 = 16,777,215
|
||||
float64 = 9,007,199,254,740,991
|
||||
`
|
||||
|
||||
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.
|
||||
`
|
||||
|
||||
const usageDate = `
|
||||
A TOML datetime must be in one of the following formats:
|
||||
|
||||
2006-01-02T15:04:05Z07:00 Date and time, with timezone.
|
||||
2006-01-02T15:04:05 Date and time, but without timezone.
|
||||
2006-01-02 Date without a time or timezone.
|
||||
15:04:05 Just a time, without any timezone.
|
||||
|
||||
Seconds may optionally have a fraction, up to nanosecond precision:
|
||||
|
||||
15:04:05.123
|
||||
15:04:05.856018510
|
||||
`
|
||||
|
||||
// TOML 1.1:
|
||||
// The seconds part in times is optional, and may be omitted:
|
||||
// 2006-01-02T15:04Z07:00
|
||||
// 2006-01-02T15:04
|
||||
// 15:04
|
|
@ -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
|
@ -0,0 +1,148 @@
|
|||
package toml
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
context Key // Used only during decoding.
|
||||
|
||||
keyInfo map[string]keyInfo
|
||||
mapping map[string]any
|
||||
keys []Key
|
||||
decoded map[string]struct{}
|
||||
data []byte // Input file; for errors.
|
||||
}
|
||||
|
||||
// IsDefined reports if the key exists in the TOML data.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Returns false for an empty key.
|
||||
func (md *MetaData) IsDefined(key ...string) bool {
|
||||
if len(key) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
hash map[string]any
|
||||
ok bool
|
||||
hashOrVal any = md.mapping
|
||||
)
|
||||
for _, k := range key {
|
||||
if hash, ok = hashOrVal.(map[string]any); !ok {
|
||||
return false
|
||||
}
|
||||
if hashOrVal, ok = hash[k]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (md *MetaData) Type(key ...string) string {
|
||||
if ki, ok := md.keyInfo[Key(key).String()]; ok {
|
||||
return ki.tomlType.typeString()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// All keys returned are non-empty.
|
||||
func (md *MetaData) Keys() []Key {
|
||||
return md.keys
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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,
|
||||
// and so no keys will be considered decoded.
|
||||
//
|
||||
// In this sense, the Undecoded keys correspond to keys in the TOML document
|
||||
// that do not have a concrete type in your representation.
|
||||
func (md *MetaData) Undecoded() []Key {
|
||||
undecoded := make([]Key, 0, len(md.keys))
|
||||
for _, key := range md.keys {
|
||||
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 {
|
||||
// This is called quite often, so it's a bit funky to make it faster.
|
||||
var b strings.Builder
|
||||
b.Grow(len(k) * 25)
|
||||
outer:
|
||||
for i, kk := range k {
|
||||
if i > 0 {
|
||||
b.WriteByte('.')
|
||||
}
|
||||
if kk == "" {
|
||||
b.WriteString(`""`)
|
||||
} else {
|
||||
for _, r := range kk {
|
||||
// "Inline" isBareKeyChar
|
||||
if !((r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '_' || r == '-') {
|
||||
b.WriteByte('"')
|
||||
b.WriteString(dblQuotedReplacer.Replace(kk))
|
||||
b.WriteByte('"')
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
b.WriteString(kk)
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (k Key) maybeQuoted(i int) string {
|
||||
if k[i] == "" {
|
||||
return `""`
|
||||
}
|
||||
for _, r := range k[i] {
|
||||
if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '_' || r == '-' {
|
||||
continue
|
||||
}
|
||||
return `"` + dblQuotedReplacer.Replace(k[i]) + `"`
|
||||
}
|
||||
return k[i]
|
||||
}
|
||||
|
||||
// Like append(), but only increase the cap by 1.
|
||||
func (k Key) add(piece string) Key {
|
||||
if cap(k) > len(k) {
|
||||
return append(k, piece)
|
||||
}
|
||||
newKey := make(Key, len(k)+1)
|
||||
copy(newKey, k)
|
||||
newKey[len(k)] = piece
|
||||
return newKey
|
||||
}
|
||||
|
||||
func (k Key) parent() Key { return k[:len(k)-1] } // all except the last piece.
|
||||
func (k Key) last() string { return k[len(k)-1] } // last piece of this key.
|
File diff suppressed because it is too large
Load Diff
|
@ -1 +0,0 @@
|
|||
au BufWritePost *.go silent!make tags > /dev/null 2>&1
|
|
@ -25,10 +25,8 @@ type field struct {
|
|||
// breaking ties with index sequence.
|
||||
type byName []field
|
||||
|
||||
func (x byName) Len() int { return len(x) }
|
||||
|
||||
func (x byName) Len() int { return len(x) }
|
||||
func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
|
||||
func (x byName) Less(i, j int) bool {
|
||||
if x[i].name != x[j].name {
|
||||
return x[i].name < x[j].name
|
||||
|
@ -45,10 +43,8 @@ func (x byName) Less(i, j int) bool {
|
|||
// byIndex sorts field by index sequence.
|
||||
type byIndex []field
|
||||
|
||||
func (x byIndex) Len() int { return len(x) }
|
||||
|
||||
func (x byIndex) Len() int { return len(x) }
|
||||
func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
|
||||
func (x byIndex) Less(i, j int) bool {
|
||||
for k, xik := range x[i].index {
|
||||
if k >= len(x[j].index) {
|
||||
|
@ -70,8 +66,8 @@ func typeFields(t reflect.Type) []field {
|
|||
next := []field{{typ: t}}
|
||||
|
||||
// Count of queued names for current level and the next.
|
||||
count := map[reflect.Type]int{}
|
||||
nextCount := map[reflect.Type]int{}
|
||||
var count map[reflect.Type]int
|
||||
var nextCount map[reflect.Type]int
|
||||
|
||||
// Types already visited at an earlier level.
|
||||
visited := map[reflect.Type]bool{}
|
||||
|
|
|
@ -16,19 +16,14 @@ func typeEqual(t1, t2 tomlType) bool {
|
|||
return t1.typeString() == t2.typeString()
|
||||
}
|
||||
|
||||
func typeIsHash(t tomlType) bool {
|
||||
func typeIsTable(t tomlType) bool {
|
||||
return typeEqual(t, tomlHash) || typeEqual(t, tomlArrayHash)
|
||||
}
|
||||
|
||||
type tomlBaseType string
|
||||
|
||||
func (btype tomlBaseType) typeString() string {
|
||||
return string(btype)
|
||||
}
|
||||
|
||||
func (btype tomlBaseType) String() string {
|
||||
return btype.typeString()
|
||||
}
|
||||
func (btype tomlBaseType) typeString() string { return string(btype) }
|
||||
func (btype tomlBaseType) String() string { return btype.typeString() }
|
||||
|
||||
var (
|
||||
tomlInteger tomlBaseType = "Integer"
|
||||
|
@ -54,7 +49,7 @@ func (p *parser) typeOfPrimitive(lexItem item) tomlType {
|
|||
return tomlFloat
|
||||
case itemDatetime:
|
||||
return tomlDatetime
|
||||
case itemString:
|
||||
case itemString, itemStringEsc:
|
||||
return tomlString
|
||||
case itemMultilineString:
|
||||
return tomlString
|
||||
|
@ -68,24 +63,3 @@ func (p *parser) typeOfPrimitive(lexItem item) tomlType {
|
|||
p.bug("Cannot infer primitive type of lex item '%s'.", lexItem)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// typeOfArray returns a tomlType for an array given a list of types of its
|
||||
// values.
|
||||
//
|
||||
// In the current spec, if an array is homogeneous, then its type is always
|
||||
// "Array". If the array is not homogeneous, an error is generated.
|
||||
func (p *parser) typeOfArray(types []tomlType) tomlType {
|
||||
// Empty arrays are cool.
|
||||
if len(types) == 0 {
|
||||
return tomlArray
|
||||
}
|
||||
|
||||
theType := types[0]
|
||||
for _, t := range types[1:] {
|
||||
if !typeEqual(theType, t) {
|
||||
p.panicf("Array contains values of type '%s' and '%s', but "+
|
||||
"arrays must be homogeneous.", theType, t)
|
||||
}
|
||||
}
|
||||
return tomlArray
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue