From d58772f177b6098994d13cf6d274beef81998b78 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Sun, 26 Mar 2023 23:12:24 +0300 Subject: [PATCH] Pull request 1779: 3290-docker-healthcheck Merge in DNS/adguard-home from 3290-docker-healthcheck to master Updates #3290. Squashed commit of the following: commit 3ac8f26c1c22855d973910fd13c096776aa8dfa6 Merge: bc17565f 0df32601 Author: Eugene Burkov Date: Mon Mar 27 01:09:03 2023 +0500 Merge branch 'master' into 3290-docker-healthcheck commit bc17565fcb5acba68129734450fb08b4fe341771 Author: Eugene Burkov Date: Sun Mar 26 18:04:08 2023 +0500 all: fix script commit e150fee8025dacdc5aa1d12916d1f42e89216156 Author: Eugene Burkov Date: Sun Mar 26 17:18:12 2023 +0500 all: imp naming commit 26b6448d10af39f8363eadd962af228a9d4ae51d Author: Eugene Burkov Date: Sun Mar 26 03:13:47 2023 +0500 all: support https web commit b5c09ce8b2ac52d6e47a00f76125bee229f616a0 Author: Eugene Burkov Date: Sat Mar 25 20:03:45 2023 +0500 all: imp scripts fmt, naming commit 8c3798c46974e48cc0c379c2ecc46a6d8d54164b Merge: e33b0c5c fb7b8bba Author: Eugene Burkov Date: Sat Mar 25 00:25:38 2023 +0500 Merge branch 'master' into 3290-docker-healthcheck commit e33b0c5cbfe5a28b29734c9ceee046b0f82a0efe Author: Eugene Burkov Date: Fri Mar 24 16:47:26 2023 +0500 all: fix docs commit 57bfd898b9c468b2b72eba06294ed5e5e0226c20 Author: Eugene Burkov Date: Fri Mar 24 16:44:40 2023 +0500 dnsforward: add special-use domain handling commit f04ae13f441a25d0b4dc1359b328552f7507fc23 Author: Eugene Burkov Date: Fri Mar 24 16:05:10 2023 +0500 all: imp code commit 32f150f88390320d2da85b54e27f6c751eccb851 Author: Eugene Burkov Date: Fri Mar 24 04:19:10 2023 +0500 all: mv Dockerfile, log changes commit a094a44ccfa26988f0e71f19288e08b26e255e2b Author: Eugene Burkov Date: Fri Mar 24 04:04:27 2023 +0500 all: finish scripts, imp names commit 4db0d0e7cb7ed69030994bc1b579534dd2c3395d Author: Eugene Burkov Date: Thu Mar 23 18:33:47 2023 +0500 docker: add script and awk program --- CHANGELOG.md | 2 + {scripts/make => docker}/Dockerfile | 9 +++- docker/dns-bind.awk | 22 ++++++++ docker/healthcheck.sh | 83 +++++++++++++++++++++++++++++ docker/web-bind.awk | 23 ++++++++ internal/dnsforward/dns.go | 15 ++++++ internal/dnsforward/dns64.go | 2 - scripts/make/build-docker.sh | 16 +++++- 8 files changed, 167 insertions(+), 5 deletions(-) rename {scripts/make => docker}/Dockerfile (89%) create mode 100644 docker/dns-bind.awk create mode 100755 docker/healthcheck.sh create mode 100644 docker/web-bind.awk diff --git a/CHANGELOG.md b/CHANGELOG.md index 20ffa2b2..1248e7fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ NOTE: Add new changes BELOW THIS COMMENT. ### Added +- Docker container's healthcheck ([#3290]). - The new HTTP API `POST /control/protection`, that updates protection state and adds an optional pause duration ([#1333]). The format of request body is described in `openapi/openapi.yaml`. The duration of this pause could @@ -132,6 +133,7 @@ In this release, the schema version has changed from 17 to 20. [#1163]: https://github.com/AdguardTeam/AdGuardHome/issues/1163 [#1333]: https://github.com/AdguardTeam/AdGuardHome/issues/1333 [#1472]: https://github.com/AdguardTeam/AdGuardHome/issues/1472 +[#3290]: https://github.com/AdguardTeam/AdGuardHome/issues/3290 [#5567]: https://github.com/AdguardTeam/AdGuardHome/issues/5567 [#5584]: https://github.com/AdguardTeam/AdGuardHome/issues/5584 [#5631]: https://github.com/AdguardTeam/AdGuardHome/issues/5631 diff --git a/scripts/make/Dockerfile b/docker/Dockerfile similarity index 89% rename from scripts/make/Dockerfile rename to docker/Dockerfile index 848c2122..38a34bcd 100644 --- a/scripts/make/Dockerfile +++ b/docker/Dockerfile @@ -52,13 +52,20 @@ RUN setcap 'cap_net_bind_service=+eip' /opt/adguardhome/AdGuardHome # TODO(a.garipov): Remove the old, non-standard 784 and 8853 ports for # DNS-over-QUIC in a future release. EXPOSE 53/tcp 53/udp 67/udp 68/udp 80/tcp 443/tcp 443/udp 784/udp\ - 853/tcp 853/udp 3000/tcp 3000/udp 3001/tcp 3001/udp 5443/tcp\ + 853/tcp 853/udp 3000/tcp 3000/udp 5443/tcp\ 5443/udp 6060/tcp 8853/udp WORKDIR /opt/adguardhome/work +# Install helpers for healthcheck. +COPY --chown=nobody:nogroup\ + ./${DIST_DIR}/docker/scripts\ + /opt/adguardhome/scripts + ENTRYPOINT ["/opt/adguardhome/AdGuardHome"] +HEALTHCHECK --interval=30s --timeout=10s --retries=3 CMD [ "/opt/adguardhome/scripts/healthcheck.sh" ] + CMD [ \ "--no-check-update", \ "-c", "/opt/adguardhome/conf/AdGuardHome.yaml", \ diff --git a/docker/dns-bind.awk b/docker/dns-bind.awk new file mode 100644 index 00000000..28c9094a --- /dev/null +++ b/docker/dns-bind.awk @@ -0,0 +1,22 @@ +/^[^[:space:]]/ { is_dns = /^dns:/ } + +/^[[:space:]]+bind_hosts:/ { if (is_dns) prev_line = FNR } + +/^[[:space:]]+- .+/ { + if (FNR - prev_line == 1) { + addrs[addrsnum++] = $2 + prev_line = FNR + } +} + +/^[[:space:]]+port:/ { if (is_dns) port = $2 } + +END { + for (i in addrs) { + if (match(addrs[i], ":")) { + print "[" addrs[i] "]:" port + } else { + print addrs[i] ":" port + } + } +} \ No newline at end of file diff --git a/docker/healthcheck.sh b/docker/healthcheck.sh new file mode 100755 index 00000000..2ab11033 --- /dev/null +++ b/docker/healthcheck.sh @@ -0,0 +1,83 @@ +#!/bin/sh + +# AdGuard Home Docker healthcheck script + +# Exit the script if a pipeline fails (-e), prevent accidental filename +# expansion (-f), and consider undefined variables as errors (-u). +set -e -f -u + +# Function error_exit is an echo wrapper that writes to stderr and stops the +# script execution with code 1. +error_exit() { + echo "$1" 1>&2 + + exit 1 +} + +agh_dir="/opt/adguardhome" +readonly agh_dir + +filename="${agh_dir}/conf/AdGuardHome.yaml" +readonly filename + +if ! [ -f "$filename" ] +then + wget "http://127.0.0.1:3000" -O /dev/null -q || exit 1 + + exit 0 +fi + +help_dir="${agh_dir}/scripts" +readonly help_dir + +# Parse web host + +web_url="$( awk -f "${help_dir}/web-bind.awk" "$filename" )" +readonly web_url + +if [ "$web_url" = '' ] +then + error_exit "no web bindings could be retrieved from $filename" +fi + +# TODO(e.burkov): Deal with 0 port. +case "$web_url" +in +(*':0') + error_exit '0 in web port is not supported by healthcheck' + ;; +(*) + # Go on. + ;; +esac + +# Parse DNS hosts + +dns_hosts="$( awk -f "${help_dir}/dns-bind.awk" "$filename" )" +readonly dns_hosts + +if [ "$dns_hosts" = '' ] +then + error_exit "no DNS bindings could be retrieved from $filename" +fi + +# TODO(e.burkov): Deal with 0 port. +case "$( echo "$dns_hosts" | head -n 1 )" +in +(*':0') + error_exit '0 in DNS port is not supported by healthcheck' + ;; +(*) + # Go on. + ;; +esac + +# Check + +wget "$web_url" -O /dev/null -q || exit 1 + +echo "$dns_hosts" | while read -r host +do + nslookup -type=a healthcheck.adguardhome.test. "$host" > /dev/null ||\ + error_exit "nslookup failed for $host" +done diff --git a/docker/web-bind.awk b/docker/web-bind.awk new file mode 100644 index 00000000..d9d198dd --- /dev/null +++ b/docker/web-bind.awk @@ -0,0 +1,23 @@ +BEGIN { scheme = "http" } + +/^bind_host:/ { host = $2 } + +/^bind_port:/ { port = $2 } + +/force_https: true$/ { scheme = "https" } + +/port_https:/ { https_port = $2 } + +/server_name:/ { https_host = $2 } + +END { + if (scheme == "https") { + host = https_host + port = https_port + } + if (match(host, ":")) { + print scheme "://[" host "]:" port + } else { + print scheme "://" host ":" port + } +} \ No newline at end of file diff --git a/internal/dnsforward/dns.go b/internal/dnsforward/dns.go index 5ac11e87..8aafaad6 100644 --- a/internal/dnsforward/dns.go +++ b/internal/dnsforward/dns.go @@ -184,6 +184,21 @@ func (s *Server) processInitial(dctx *dnsContext) (rc resultCode) { return resultCodeFinish } + // Handle a reserved domain healthcheck.adguardhome.test. + // + // [Section 6.2 of RFC 6761] states that DNS Registries/Registrars must not + // grant requests to register test names in the normal way to any person or + // entity, making domain names under test. TLD free to use in internal + // purposes. + // + // [Section 6.2 of RFC 6761]: https://www.rfc-editor.org/rfc/rfc6761.html#section-6.2 + if q.Name == "healthcheck.adguardhome.test." { + // Generate a NODATA negative response to make nslookup exit with 0. + pctx.Res = s.makeResponse(pctx.Req) + + return resultCodeFinish + } + // Get the ClientID, if any, before getting client-specific filtering // settings. var key [8]byte diff --git a/internal/dnsforward/dns64.go b/internal/dnsforward/dns64.go index 23b0febb..d0ef3b93 100644 --- a/internal/dnsforward/dns64.go +++ b/internal/dnsforward/dns64.go @@ -36,8 +36,6 @@ func (s *Server) setupDNS64() { // valid IPv4. It panics, if there are no configured DNS64 prefixes, because // synthesis should not be performed unless DNS64 function enabled. func (s *Server) mapDNS64(ip netip.Addr) (mapped net.IP) { - // Don't mask the address here since it should have already been masked on - // initialization stage. pref := s.dns64Pref.Masked().Addr().As16() ipData := ip.As4() diff --git a/scripts/make/build-docker.sh b/scripts/make/build-docker.sh index 38b1ea30..14fd515b 100644 --- a/scripts/make/build-docker.sh +++ b/scripts/make/build-docker.sh @@ -87,7 +87,7 @@ readonly docker_image_full_name docker_tags # Copy the binaries into a new directory under new names, so that it's easier to # COPY them later. DO NOT remove the trailing underscores. See file -# scripts/make/Dockerfile. +# docker/Dockerfile. dist_docker="${dist_dir}/docker" readonly dist_docker @@ -105,6 +105,18 @@ cp "${dist_dir}/AdGuardHome_linux_arm_7/AdGuardHome/AdGuardHome"\ cp "${dist_dir}/AdGuardHome_linux_ppc64le/AdGuardHome/AdGuardHome"\ "${dist_docker}/AdGuardHome_linux_ppc64le_" +# Copy the helper scripts. See file docker/Dockerfile. +dist_docker_scripts="${dist_docker}/scripts" +readonly dist_docker_scripts + +mkdir -p "$dist_docker_scripts" +cp "./docker/dns-bind.awk"\ + "${dist_docker_scripts}/dns-bind.awk" +cp "./docker/web-bind.awk"\ + "${dist_docker_scripts}/web-bind.awk" +cp "./docker/healthcheck.sh"\ + "${dist_docker_scripts}/healthcheck.sh" + # Don't use quotes with $docker_tags and $debug_flags because we want word # splitting and or an empty space if tags are empty. $sudo_cmd docker\ @@ -118,5 +130,5 @@ $sudo_cmd docker\ --platform "$docker_platforms"\ $docker_tags\ -t "$docker_image_full_name"\ - -f ./scripts/make/Dockerfile\ + -f ./docker/Dockerfile\ .