diff --git a/management/dns_update.py b/management/dns_update.py index 81e754e0..db5b9d73 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -104,7 +104,7 @@ def do_dns_update(env): zonefiles[i][1] += ".signed" # Write the main nsd.conf file. - if write_nsd_conf(zonefiles): + if write_nsd_conf(zonefiles, env): # Make sure updated_domains contains *something* if we wrote an updated # nsd.conf so that we know to restart nsd. if len(updated_domains) == 0: @@ -383,7 +383,7 @@ $TTL 86400 ; default time to live ######################################################################## -def write_nsd_conf(zonefiles): +def write_nsd_conf(zonefiles, env): # Basic header. nsdconf = """ server: @@ -397,15 +397,13 @@ server: """ # Since we have bind9 listening on localhost for locally-generated - # DNS queries that require a recursive nameserver, we must have - # nsd listen only on public network interfaces. Those interfaces - # may have addresses different from the public IP address that the - # Internet sees this machine on. Get those interface addresses - # from `hostname -i` (which omits all localhost addresses). - for ipaddr in shell("check_output", ["/bin/hostname", "-I"]).strip().split(" "): + # DNS queries that require a recursive nameserver, and the system + # might have other network interfaces for e.g. tunnelling, we have + # to be specific about the network interfaces that nsd binds to. + for ipaddr in (env.get("PRIVATE_IP", "") + " " + env.get("PRIVATE_IPV6", "")).split(" "): + if ipaddr == "": continue nsdconf += " ip-address: %s\n" % ipaddr - # Append the zones. for domain, zonefile in zonefiles: nsdconf += """ diff --git a/setup/functions.sh b/setup/functions.sh index 82423832..126abc56 100644 --- a/setup/functions.sh +++ b/setup/functions.sh @@ -77,62 +77,58 @@ function get_default_publicip { # API, but if that fails (maybe we don't have Internet access # right now) then use the IP address that this machine knows # itself as. - get_publicip_from_web_service || get_publicip_fallback + get_publicip_from_web_service 4 || get_default_privateip 4 } function get_default_publicipv6 { - get_publicipv6_from_web_service || get_publicipv6_fallback + get_publicip_from_web_service 6 || get_default_privateip 6 } function get_publicip_from_web_service { # This seems to be the most reliable way to determine the # machine's public IP address: asking a very nice web API # for how they see us. Thanks go out to icanhazip.com. - curl -4 --fail --silent icanhazip.com 2>/dev/null + # See: https://major.io/icanhazip-com-faq/ + # + # Pass '4' or '6' as an argument to this function to specify + # what type of address to get (IPv4, IPv6). + curl -$1 --fail --silent icanhazip.com 2>/dev/null } -function get_publicipv6_from_web_service { - curl -6 --fail --silent icanhazip.com 2>/dev/null -} +function get_default_privateip { + # Return the IP address of the network interface connected + # to the Internet. + # + # We used to use `hostname -I` and then filter for either + # IPv4 or IPv6 addresses. However if there are multiple + # network interfaces on the machine, not all may be for + # reaching the Internet. + # + # Instead use `ip route get` which asks the kernel to use + # the system's routes to select which interface would be + # used to reach a public address. We'll use 8.8.8.8 as + # the destination. It happens to be Google Public DNS, but + # no connection is made. We're just seeing how the box + # would connect to it. There many be multiple IP addresses + # assigned to an interface. `ip route get` reports the + # preferred. That's good enough for us. See issue #121. + # + # Also see ae67409603c49b7fa73c227449264ddd10aae6a9 and + # issue #3 for why/how we originally added IPv6. + # + # Pass '4' or '6' as an argument to this function to specify + # what type of address to get (IPv4, IPv6). -function get_publicip_fallback { - # Return the IP address that this machine knows itself as. - # It certainly may not be the IP address that this machine - # operates as on the public Internet. The machine might - # have multiple addresses if it has multiple network adapters. - set -- $(hostname --ip-address 2>/dev/null) \ - $(hostname --all-ip-addresses 2>/dev/null) - while (( $# )) && { ! is_ipv4 "$1" || is_loopback_ip "$1"; }; do - shift - done - printf '%s\n' "$1" # return this value -} + target=8.8.8.8 -function get_publicipv6_fallback { - set -- $(hostname --ip-address 2>/dev/null) \ - $(hostname --all-ip-addresses 2>/dev/null) - while (( $# )) && { ! is_ipv6 "$1" || is_loopback_ipv6 "$1"; }; do - shift - done - printf '%s\n' "$1" # return this value -} + # For the IPv6 route, use the corresponding IPv6 address + # of Google Public DNS. Again, it doesn't matter so long + # as it's an address on the public Internet. + if [ "$1" == "6" ]; then target=2001:4860:4860::8888; fi -function is_ipv4 { - # helper for get_publicip_fallback - [[ "$1" == *.*.*.* ]] -} - -function is_ipv6 { - [[ "$1" == *:*:* ]] -} - -function is_loopback_ip { - # helper for get_publicip_fallback - [[ "$1" == 127.* ]] -} - -function is_loopback_ipv6 { - [[ "$1" == ::1 ]] + ip -$1 -o route get $target \ + | grep -v unreachable \ + | sed "s/.* src \([^ ]*\).*/\1/" } function ufw_allow { diff --git a/setup/start.sh b/setup/start.sh index f065448f..597d7f97 100755 --- a/setup/start.sh +++ b/setup/start.sh @@ -136,6 +136,26 @@ if [ -z "$PUBLIC_IPV6" ]; then read -e -i "$DEFAULT_PUBLIC_IPV6" -p "Public IPv6: " PUBLIC_IPV6 fi +# Get the IP addresses of the local network interface(s) that are connected +# to the Internet. We need these when we want to have services bind only to +# the public network interfaces (not loopback, not tunnel interfaces). +if [ -z "$PRIVATE_IP" ]; then + PRIVATE_IP=$(get_default_privateip 4) +fi +if [ -z "$PRIVATE_IPV6" ]; then + PRIVATE_IPV6=$(get_default_privateip 6) +fi +if [[ -z "$PRIVATE_IP" && -z "$PRIVATE_IPV6" ]]; then + echo + echo "I could not determine the IP or IPv6 address of the network inteface" + echo "for connecting to the Internet. Setup must stop." + echo + hostname -I + route + echo + exit +fi + # We need a country code to generate a certificate signing request. However # if a CSR already exists then we won't be generating a new one and there's # no reason to ask for the country code now. $STORAGE_ROOT has not yet been @@ -199,6 +219,8 @@ STORAGE_ROOT=$STORAGE_ROOT PRIMARY_HOSTNAME=$PRIMARY_HOSTNAME PUBLIC_IP=$PUBLIC_IP PUBLIC_IPV6=$PUBLIC_IPV6 +PRIVATE_IP=$PRIVATE_IP +PRIVATE_IPV6=$PRIVATE_IPV6 CSR_COUNTRY=$CSR_COUNTRY MIGRATIONID=$MIGRATIONID EOF