1#!/bin/bash 2# Copyright 2021 Google LLC 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16# shellcheck source=meta-google/recipes-google/networking/network-sh/lib.sh 17source /usr/share/network/lib.sh || exit 18# shellcheck source=meta-google/recipes-google/networking/gbmc-net-common/gbmc-net-lib.sh 19source /usr/share/gbmc-net-lib.sh || exit 20 21: "${RA_IF:?No RA interface set}" 22: "${IP_OFFSET=?1}" 23: "${ROUTE_METRIC:?No Metric set}" 24 25# We would prefer empty string but it's easier for associative array handling 26# to use invalid 27old_rtr=invalid 28old_mac=invalid 29old_pfx=invalid 30old_fqdn=invalid 31 32default_update_rtr() { 33 local rtr="$1" 34 local mac="$2" 35 36 if ip addr show | grep -q "^[ ]*inet6 $rtr/"; then 37 echo "Router is ourself, ignoring" >&2 38 return 0 39 fi 40 41 # Override any existing gateway information within files 42 # Make sure we cover `00-*` and `-*` files 43 for file in /run/systemd/network/{00,}-bmc-$RA_IF.network; do 44 mkdir -p "$file.d" 45 printf '[Route]\nGateway=%s\nGatewayOnLink=true\nMetric=%d\n[Neighbor]\nMACAddress=%s\nAddress=%s' \ 46 "$rtr" "$ROUTE_METRIC" "$mac" "$rtr" >"$file.d"/10-gateway.conf 47 done 48 49 # Don't force networkd to reload as this can break phosphor-networkd 50 # Fall back to reload only if ip link commands fail 51 (ip -6 route replace default via "$rtr" onlink dev "$RA_IF" metric "$ROUTE_METRIC" && \ 52 ip -6 neigh replace "$rtr" dev "$RA_IF" lladdr "$mac") || \ 53 gbmc_net_networkd_reload "$RA_IF" || true 54 55 echo "Set router $rtr on $RA_IF" >&2 56} 57 58default_update_fqdn() { 59 local fqdn="$1" 60 [ -z "$fqdn" ] && return 61 hostnamectl set-hostname "$fqdn" || true 62 echo "Set hostname $fqdn on $RA_IF" >&2 63} 64 65retries=1 66min_w=10 67declare -A rtrs 68rtrs=() 69while true; do 70 # shellcheck disable=SC2206 71 data=(${rtrs["${old_rtr}"]-}) 72 curr_dl="${data[1]-$(( min_w + SECONDS ))}" 73 args=(-m "$RA_IF" -w $(( (curr_dl - SECONDS) * 1000 ))) 74 if (( retries > 0 )); then 75 args+=(-r "$retries") 76 else 77 args+=(-d) 78 fi 79 while read -r line; do 80 # `script` terminates all lines with a CRLF, remove it 81 line="${line:0:-1}" 82 # shellcheck disable=SC2026 83 if [ -z "$line" ]; then 84 lifetime=-1 85 mac= 86 hextet= 87 pfx= 88 host= 89 domain= 90 elif [[ "$line" =~ ^Router' 'lifetime' '*:' '*([0-9]*) ]]; then 91 lifetime="${BASH_REMATCH[1]}" 92 elif [[ "$line" =~ ^Source' 'link-layer' 'address' '*:' '*([a-fA-F0-9:]*)$ ]]; then 93 mac="${BASH_REMATCH[1]}" 94 elif [[ "$line" =~ ^Prefix' '*:' '*(.*)/([0-9]+)$ ]]; then 95 t_pfx="${BASH_REMATCH[1]}" 96 t_pfx_len="${BASH_REMATCH[2]}" 97 ip_to_bytes t_pfx_b "$t_pfx" || continue 98 (( (t_pfx_len == 76 || t_pfx_len == 80) && (t_pfx_b[8] & 0xfd) == 0xfd )) || continue 99 (( t_pfx_b[9] &= 0xf0 )) 100 (( t_pfx_b[9] |= IP_OFFSET )) 101 hextet="fd$(printf '%02x' "${t_pfx_b[9]}")" 102 pfx="$(ip_bytes_to_str t_pfx_b)" 103 elif [[ "$line" =~ ^'DNS search list'' '*:' '*([^.]+)(.*[.]google[.]com)' '*$ ]]; then 104 # Ideally, we use PCRE and with lookahead and can do this in a single regex 105 # ^([a-zA-Z0-9-]+(?=-n[a-fA-F0-9]{1,4})|[a-zA-Z0-9-]+(?!-n[a-fA-F0-9]{1,4}))[^.]*[.]((?:[a-zA-Z0-9]*[.])*google[.]com)$ 106 # Instead we do multiple steps to extract the needed info 107 host="${BASH_REMATCH[1]}" 108 domain="${BASH_REMATCH[2]#.}" 109 if [[ "$host" =~ (-n[a-fA-F0-9]{1,4})$ ]]; then 110 host="${host%"${BASH_REMATCH[1]}"}" 111 fi 112 elif [[ "$line" =~ ^from' '(.*)$ ]]; then 113 rtr="${BASH_REMATCH[1]}" 114 # Only valid default routers can be considered, 0 lifetime implies 115 # a non-default router 116 (( lifetime > 0 )) || continue 117 118 dl=$((lifetime + SECONDS)) 119 fqdn= 120 if [[ -n $host && -n $hextet && -n $domain ]]; then 121 fqdn="$host-n$hextet.$domain" 122 fi 123 rtrs["$rtr"]="$mac $dl $pfx $fqdn" 124 # We have some notoriously noisy lab environments with many routers being broadcast 125 # We always prefer "fe80::1" in prod and labs for routing, so prefer that gateway. 126 # We also want to take the first router we find to speed up acquisition on boot. 127 if [[ "$rtr" = "fe80::1" || "$old_rtr" = "invalid" ]]; then 128 if [[ "$rtr" != "$old_rtr" && "$mac" != "$old_mac" ]]; then 129 echo "Got defgw $rtr at $mac on $RA_IF" >&2 130 update_rtr "$rtr" "$mac" || true 131 retries=-1 132 old_mac="$mac" 133 old_rtr="$rtr" 134 fi 135 fi 136 # Only update router properties if we use this router 137 [[ "$rtr" == "$old_rtr" ]] || continue 138 if [[ $pfx != "$old_pfx" ]]; then 139 echo "Got PFX $pfx from $rtr on $RA_IF" >&2 140 old_pfx="$pfx" 141 update_pfx "$pfx" || true 142 fi 143 if [[ $fqdn != "$old_fqdn" ]]; then 144 echo "Got FQDN $fqdn from $rtr on $RA_IF" >&2 145 old_fqdn="$fqdn" 146 update_fqdn "$fqdn" || true 147 fi 148 fi 149 done < <(exec script -q -c "rdisc6 ${args[*]}" /dev/null 2>/dev/null) 150 # Purge any expired routers 151 for rtr in "${!rtrs[@]}"; do 152 # shellcheck disable=SC2206 153 data=(${rtrs["$rtr"]}) 154 dl=${data[1]} 155 if (( dl <= SECONDS )); then 156 unset "rtrs[$rtr]" 157 fi 158 done 159 # Consider changing the gateway if the old one doesn't send RAs for the entire period 160 # This ensures we don't flip flop between multiple defaults if they exist. 161 if [[ "$old_rtr" != "invalid" && -z "${rtrs["$old_rtr"]-}" ]]; then 162 echo "Old router $old_rtr disappeared" >&2 163 old_rtr=invalid 164 for rtr in "${!rtrs[@]}"; do 165 # shellcheck disable=SC2206 166 data=(${rtrs["$rtr"]}) 167 mac=${data[0]} 168 dl=${data[1]} 169 pfx=${data[2]} 170 fqdn=${data[3]} 171 update_rtr "$rtr" "$mac" || true 172 update_pfx "$pfx" || true 173 update_fqdn "$fqdn" || true 174 break 175 done 176 fi 177 178 # If rdisc6 exits early we still want to wait for the deadline before retrying 179 (( timeout = curr_dl - SECONDS )) 180 sleep $(( timeout < 0 ? 0 : timeout )) 181done 182