From 24dbd9459d6854b97289410f3ef9ed21a231558e Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Mon, 19 Apr 2021 12:51:00 +0200 Subject: [PATCH] Use DNS requests to discover external IP address Using DNS requests is a much more robust and reliable method to discover a machine's external IP address, instead of the previous method of using Curl against an HTTP service. Using Curl is fine, but the kind of services that are typically used (here it was icanhazip.com, but there are lots more with a similar behavior) are are not as dependable as the official DNS request methods supported by some of the biggest service providers. This uses "dig", which Alpine provides in the package "bind-tools" and Debian in "dnstools". --- docker/coturn/alpine/Dockerfile | 3 + .../usr/local/bin/detect-external-ip.sh | 116 +++++++++++++++++- docker/coturn/debian/Dockerfile | 3 + .../usr/local/bin/detect-external-ip.sh | 116 +++++++++++++++++- 4 files changed, 230 insertions(+), 8 deletions(-) diff --git a/docker/coturn/alpine/Dockerfile b/docker/coturn/alpine/Dockerfile index ac06a36..05f0e4d 100644 --- a/docker/coturn/alpine/Dockerfile +++ b/docker/coturn/alpine/Dockerfile @@ -109,6 +109,9 @@ RUN apk update \ libpq mariadb-connector-c sqlite-libs \ hiredis \ mongo-c-driver \ + # Install `dig` tool for `detect-external-ip.sh`. + && apk add --no-cache \ + bind-tools \ # Cleanup unnecessary stuff. && rm -rf /var/cache/apk/* diff --git a/docker/coturn/alpine/rootfs/usr/local/bin/detect-external-ip.sh b/docker/coturn/alpine/rootfs/usr/local/bin/detect-external-ip.sh index b4e4c57..49bdc8d 100644 --- a/docker/coturn/alpine/rootfs/usr/local/bin/detect-external-ip.sh +++ b/docker/coturn/alpine/rootfs/usr/local/bin/detect-external-ip.sh @@ -1,7 +1,115 @@ -#!/bin/sh +#!/usr/bin/env sh +# shellcheck shell=dash -if [ -z "$REAL_EXTERNAL_IP" ]; then - export REAL_EXTERNAL_IP="$(curl -4 https://icanhazip.com 2>/dev/null)" +#/ Use DNS to find out about the external IP of the running system. +#/ +#/ This script is useful when running from a machine that sits behind a NAT. +#/ Due to how NAT works, machines behind it belong to an internal or private +#/ subnet, with a different address space than the external or public side. +#/ +#/ Typically it is possible to make an HTTP request to a number of providers +#/ that offer the external IP in their response body (eg: ifconfig.me). However, +#/ why do a slow and heavy HTTP request, when DNS exists and is much faster? +#/ Well established providers such as OpenDNS or Google offer special hostnames +#/ that, when resolved, will actually return the IP address of the caller. +#/ +#/ https://unix.stackexchange.com/questions/22615/how-can-i-get-my-external-ip-address-in-a-shell-script/81699#81699 +#/ +#/ +#/ Arguments +#/ --------- +#/ +#/ --ipv4 +#/ +#/ Find the external IPv4 address. +#/ Optional. Default: Enabled. +#/ +#/ --ipv6 +#/ +#/ Find the external IPv6 address. +#/ Optional. Default: Disabled. + + + +# Shell setup +# =========== + +# Shell options for strict error checking. +for OPTION in errexit errtrace pipefail nounset; do + set -o | grep -wq "$OPTION" && set -o "$OPTION" +done + +# Trace all commands (to stderr). +#set -o xtrace + + + +# Shortcut: REAL_EXTERNAL_IP +# ========================== + +if [ -n "${REAL_EXTERNAL_IP:-}" ]; then + echo "$REAL_EXTERNAL_IP" + exit 0 fi -exec echo "$REAL_EXTERNAL_IP" + + +# Parse call arguments +# ==================== + +CFG_IPV4="true" + +while [ $# -gt 0 ]; do + case "${1-}" in + --ipv4) CFG_IPV4="true" ;; + --ipv6) CFG_IPV4="false" ;; + *) + echo "Invalid argument: '${1-}'" >&2 + exit 1 + ;; + esac + shift +done + + + +# Discover the external IP address +# ================================ + +if [ "$CFG_IPV4" = "true" ]; then + COMMANDS='dig @resolver1.opendns.com myip.opendns.com A -4 +short + dig @ns1.google.com o-o.myaddr.l.google.com TXT -4 +short | tr -d \" + dig @1.1.1.1 whoami.cloudflare TXT CH -4 +short | tr -d \" + dig @ns1-1.akamaitech.net whoami.akamai.net A -4 +short' + + is_valid_ip() { + # Check if the input looks like an IPv4 address. + # Doesn't check if the actual values are valid; assumes they are. + echo "$1" | grep -Eq '^([0-9]{1,3}\.){3}[0-9]{1,3}$' + } +else + COMMANDS='dig @resolver1.opendns.com myip.opendns.com AAAA -6 +short + dig @ns1.google.com o-o.myaddr.l.google.com TXT -6 +short | tr -d \" + dig @2606:4700:4700::1111 whoami.cloudflare TXT CH -6 +short | tr -d \"' + + is_valid_ip() { + # Check if the input looks like an IPv6 address. + # It's almost impossible to check the IPv6 representation because it + # varies wildly, so just check that there are at least 2 colons. + [ "$(echo "$1" | awk -F':' '{print NF-1}')" -ge 2 ] + } +fi + +echo "$COMMANDS" | while read -r COMMAND; do + if IP="$(eval "$COMMAND")" && is_valid_ip "$IP"; then + echo "$IP" + exit 100 # Exits the pipe subshell. + fi +done + +if [ $? -eq 100 ]; then + exit 0 +else + echo "[$0] All providers failed" >&2 + exit 1 +fi diff --git a/docker/coturn/debian/Dockerfile b/docker/coturn/debian/Dockerfile index 7b64b39..0b4eeb0 100644 --- a/docker/coturn/debian/Dockerfile +++ b/docker/coturn/debian/Dockerfile @@ -164,6 +164,9 @@ RUN apt-get update \ libssl1.1 \ libpq5 libmariadb3 libsqlite3-0 \ libhiredis0.14 \ + # Install `dig` tool for `detect-external-ip.sh`. + && apt-get install -y --no-install-recommends --no-install-suggests \ + dnsutils \ # Cleanup unnecessary stuff. && rm -rf /var/lib/apt/lists/* diff --git a/docker/coturn/debian/rootfs/usr/local/bin/detect-external-ip.sh b/docker/coturn/debian/rootfs/usr/local/bin/detect-external-ip.sh index b4e4c57..49bdc8d 100644 --- a/docker/coturn/debian/rootfs/usr/local/bin/detect-external-ip.sh +++ b/docker/coturn/debian/rootfs/usr/local/bin/detect-external-ip.sh @@ -1,7 +1,115 @@ -#!/bin/sh +#!/usr/bin/env sh +# shellcheck shell=dash -if [ -z "$REAL_EXTERNAL_IP" ]; then - export REAL_EXTERNAL_IP="$(curl -4 https://icanhazip.com 2>/dev/null)" +#/ Use DNS to find out about the external IP of the running system. +#/ +#/ This script is useful when running from a machine that sits behind a NAT. +#/ Due to how NAT works, machines behind it belong to an internal or private +#/ subnet, with a different address space than the external or public side. +#/ +#/ Typically it is possible to make an HTTP request to a number of providers +#/ that offer the external IP in their response body (eg: ifconfig.me). However, +#/ why do a slow and heavy HTTP request, when DNS exists and is much faster? +#/ Well established providers such as OpenDNS or Google offer special hostnames +#/ that, when resolved, will actually return the IP address of the caller. +#/ +#/ https://unix.stackexchange.com/questions/22615/how-can-i-get-my-external-ip-address-in-a-shell-script/81699#81699 +#/ +#/ +#/ Arguments +#/ --------- +#/ +#/ --ipv4 +#/ +#/ Find the external IPv4 address. +#/ Optional. Default: Enabled. +#/ +#/ --ipv6 +#/ +#/ Find the external IPv6 address. +#/ Optional. Default: Disabled. + + + +# Shell setup +# =========== + +# Shell options for strict error checking. +for OPTION in errexit errtrace pipefail nounset; do + set -o | grep -wq "$OPTION" && set -o "$OPTION" +done + +# Trace all commands (to stderr). +#set -o xtrace + + + +# Shortcut: REAL_EXTERNAL_IP +# ========================== + +if [ -n "${REAL_EXTERNAL_IP:-}" ]; then + echo "$REAL_EXTERNAL_IP" + exit 0 fi -exec echo "$REAL_EXTERNAL_IP" + + +# Parse call arguments +# ==================== + +CFG_IPV4="true" + +while [ $# -gt 0 ]; do + case "${1-}" in + --ipv4) CFG_IPV4="true" ;; + --ipv6) CFG_IPV4="false" ;; + *) + echo "Invalid argument: '${1-}'" >&2 + exit 1 + ;; + esac + shift +done + + + +# Discover the external IP address +# ================================ + +if [ "$CFG_IPV4" = "true" ]; then + COMMANDS='dig @resolver1.opendns.com myip.opendns.com A -4 +short + dig @ns1.google.com o-o.myaddr.l.google.com TXT -4 +short | tr -d \" + dig @1.1.1.1 whoami.cloudflare TXT CH -4 +short | tr -d \" + dig @ns1-1.akamaitech.net whoami.akamai.net A -4 +short' + + is_valid_ip() { + # Check if the input looks like an IPv4 address. + # Doesn't check if the actual values are valid; assumes they are. + echo "$1" | grep -Eq '^([0-9]{1,3}\.){3}[0-9]{1,3}$' + } +else + COMMANDS='dig @resolver1.opendns.com myip.opendns.com AAAA -6 +short + dig @ns1.google.com o-o.myaddr.l.google.com TXT -6 +short | tr -d \" + dig @2606:4700:4700::1111 whoami.cloudflare TXT CH -6 +short | tr -d \"' + + is_valid_ip() { + # Check if the input looks like an IPv6 address. + # It's almost impossible to check the IPv6 representation because it + # varies wildly, so just check that there are at least 2 colons. + [ "$(echo "$1" | awk -F':' '{print NF-1}')" -ge 2 ] + } +fi + +echo "$COMMANDS" | while read -r COMMAND; do + if IP="$(eval "$COMMAND")" && is_valid_ip "$IP"; then + echo "$IP" + exit 100 # Exits the pipe subshell. + fi +done + +if [ $? -eq 100 ]; then + exit 0 +else + echo "[$0] All providers failed" >&2 + exit 1 +fi