浏览代码

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".
Juan Navarro 4 年之前
父节点
当前提交
24dbd9459d

+ 3 - 0
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/*
 

+ 112 - 4
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

+ 3 - 0
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/*
 

+ 112 - 4
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