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 # In case we don't have a base network file, make one 42 # this is intentionally 00- as it will not preceed /etc/systemd/network/00-* 43 # or /lib/systemd/network/-* files. 44 local file=/run/systemd/network/00-bmc-$RA_IF.network 45 printf '[Match]\nName=%s\n[Network]\nDHCP=false\nIPv6AcceptRA=false\nLinkLocalAddressing=yes' \ 46 "$RA_IF" >"$file" 47 48 # Override any existing gateway information within files 49 # Make sure we cover `00-*` and `-*` files 50 for file in /run/systemd/network/{00,}-bmc-$RA_IF.network; do 51 mkdir -p "$file.d" 52 printf '[Route]\nGateway=%s\nGatewayOnLink=true\nMetric=%d\n[Neighbor]\nMACAddress=%s\nAddress=%s' \ 53 "$rtr" "$ROUTE_METRIC" "$mac" "$rtr" >"$file.d"/10-gateway.conf 54 done 55 56 # Don't force networkd to reload as this can break phosphor-networkd 57 # Fall back to reload only if ip link commands fail 58 (ip -6 route replace default via "$rtr" onlink dev "$RA_IF" metric "$ROUTE_METRIC" && \ 59 ip -6 neigh replace "$rtr" dev "$RA_IF" lladdr "$mac") || \ 60 gbmc_net_networkd_reload "$RA_IF" || true 61 62 echo "Set router $rtr on $RA_IF" >&2 63} 64 65default_update_fqdn() { 66 local fqdn="$1" 67 [ -z "$fqdn" ] && return 68 hostnamectl set-hostname "$fqdn" || true 69 echo "Set hostname $fqdn on $RA_IF" >&2 70} 71 72retries=1 73min_w=10 74declare -A rtrs 75rtrs=() 76while true; do 77 # shellcheck disable=SC2206 78 data=(${rtrs["${old_rtr}"]-}) 79 curr_dl="${data[1]-$(( min_w + SECONDS ))}" 80 args=(-m "$RA_IF" -w $(( (curr_dl - SECONDS) * 1000 ))) 81 if (( retries > 0 )); then 82 args+=(-r "$retries") 83 else 84 args+=(-d) 85 fi 86 while read -r line; do 87 # `script` terminates all lines with a CRLF, remove it 88 line="${line:0:-1}" 89 # shellcheck disable=SC2026 90 if [ -z "$line" ]; then 91 lifetime=-1 92 mac= 93 hextet= 94 pfx= 95 host= 96 domain= 97 elif [[ "$line" =~ ^Router' 'lifetime' '*:' '*([0-9]*) ]]; then 98 lifetime="${BASH_REMATCH[1]}" 99 elif [[ "$line" =~ ^Source' 'link-layer' 'address' '*:' '*([a-fA-F0-9:]*)$ ]]; then 100 mac="${BASH_REMATCH[1]}" 101 elif [[ "$line" =~ ^Prefix' '*:' '*(.*)/([0-9]+)$ ]]; then 102 t_pfx="${BASH_REMATCH[1]}" 103 t_pfx_len="${BASH_REMATCH[2]}" 104 ip_to_bytes t_pfx_b "$t_pfx" || continue 105 (( (t_pfx_len == 76 || t_pfx_len == 80) && (t_pfx_b[8] & 0xfd) == 0xfd )) || continue 106 (( t_pfx_b[9] &= 0xf0 )) 107 (( t_pfx_b[9] |= IP_OFFSET )) 108 hextet="fd$(printf '%02x' "${t_pfx_b[9]}")" 109 pfx="$(ip_bytes_to_str t_pfx_b)" 110 elif [[ "$line" =~ ^'DNS search list'' '*:' '*([^.]+)(.*[.]google[.]com)' '*$ ]]; then 111 # Ideally, we use PCRE and with lookahead and can do this in a single regex 112 # ^([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)$ 113 # Instead we do multiple steps to extract the needed info 114 host="${BASH_REMATCH[1]}" 115 domain="${BASH_REMATCH[2]#.}" 116 if [[ "$host" =~ (-n[a-fA-F0-9]{1,4})$ ]]; then 117 host="${host%"${BASH_REMATCH[1]}"}" 118 fi 119 elif [[ "$line" =~ ^from' '(.*)$ ]]; then 120 rtr="${BASH_REMATCH[1]}" 121 # Only valid default routers can be considered, 0 lifetime implies 122 # a non-default router 123 (( lifetime > 0 )) || continue 124 125 dl=$((lifetime + SECONDS)) 126 fqdn= 127 if [[ -n $host && -n $hextet && -n $domain ]]; then 128 fqdn="$host-n$hextet.$domain" 129 fi 130 rtrs["$rtr"]="$mac $dl $pfx $fqdn" 131 # We have some notoriously noisy lab environments with many routers being broadcast 132 # We always prefer "fe80::1" in prod and labs for routing, so prefer that gateway. 133 # We also want to take the first router we find to speed up acquisition on boot. 134 if [[ "$rtr" = "fe80::1" || "$old_rtr" = "invalid" ]]; then 135 if [[ "$rtr" != "$old_rtr" && "$mac" != "$old_mac" ]]; then 136 echo "Got defgw $rtr at $mac on $RA_IF" >&2 137 update_rtr "$rtr" "$mac" || true 138 retries=-1 139 old_mac="$mac" 140 old_rtr="$rtr" 141 fi 142 fi 143 # Only update router properties if we use this router 144 [[ "$rtr" == "$old_rtr" ]] || continue 145 if [[ $pfx != "$old_pfx" ]]; then 146 echo "Got PFX $pfx from $rtr on $RA_IF" >&2 147 old_pfx="$pfx" 148 update_pfx "$pfx" || true 149 fi 150 if [[ $fqdn != "$old_fqdn" ]]; then 151 echo "Got FQDN $fqdn from $rtr on $RA_IF" >&2 152 old_fqdn="$fqdn" 153 update_fqdn "$fqdn" || true 154 fi 155 fi 156 done < <(exec script -q -c "rdisc6 ${args[*]}" /dev/null 2>/dev/null) 157 # Purge any expired routers 158 for rtr in "${!rtrs[@]}"; do 159 # shellcheck disable=SC2206 160 data=(${rtrs["$rtr"]}) 161 dl=${data[1]} 162 if (( dl <= SECONDS )); then 163 unset "rtrs[$rtr]" 164 fi 165 done 166 # Consider changing the gateway if the old one doesn't send RAs for the entire period 167 # This ensures we don't flip flop between multiple defaults if they exist. 168 if [[ "$old_rtr" != "invalid" && -z "${rtrs["$old_rtr"]-}" ]]; then 169 echo "Old router $old_rtr disappeared" >&2 170 old_rtr=invalid 171 for rtr in "${!rtrs[@]}"; do 172 # shellcheck disable=SC2206 173 data=(${rtrs["$rtr"]}) 174 mac=${data[0]} 175 dl=${data[1]} 176 pfx=${data[2]} 177 fqdn=${data[3]} 178 update_rtr "$rtr" "$mac" || true 179 update_pfx "$pfx" || true 180 update_fqdn "$fqdn" || true 181 break 182 done 183 fi 184 185 # If rdisc6 exits early we still want to wait for the deadline before retrying 186 (( timeout = curr_dl - SECONDS )) 187 sleep $(( timeout < 0 ? 0 : timeout )) 188done 189