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 32ROUTE_TABLE=$ROUTE_METRIC 33 34default_update_rtr() { 35 local rtr="$1" 36 local mac="$2" 37 local op="${3-add}" 38 39 if ip addr show | grep -q "^[ ]*inet6 $rtr/"; then 40 echo "Router is ourself, ignoring" >&2 41 return 0 42 fi 43 44 # It's important that this happens before the main table default router is configured. 45 # Otherwise, the IP source determination logic won't be able to pick the best route. 46 # Also we don't need to remove the route per table. 47 if [[ ${op} == "add" ]]; then 48 # Add additional gateway information 49 for file in /run/systemd/network/{00,}-bmc-$RA_IF.network; do 50 mkdir -p "$file.d" 51 printf '[Route]\nGateway=%s\nGatewayOnLink=true\nMetric=512\nTable=%d' \ 52 "$rtr" "$ROUTE_TABLE" >"$file.d"/10-gateway-table.conf 53 done 54 55 ip -6 route replace default via "$rtr" onlink dev "$RA_IF" metric 512 table "$ROUTE_TABLE" || \ 56 gbmc_net_networkd_reload "$RA_IF" 57 fi 58 59 if [[ ${op} = "add" ]]; then 60 61 # Override any existing gateway information within files 62 # Make sure we cover `00-*` and `-*` files 63 for file in /run/systemd/network/{00,}-bmc-$RA_IF.network; do 64 mkdir -p "$file.d" 65 printf '[Route]\nGateway=%s\nGatewayOnLink=true\nMetric=%d\n[Neighbor]\nMACAddress=%s\nAddress=%s' \ 66 "$rtr" "$ROUTE_METRIC" "$mac" "$rtr" >"$file.d"/10-gateway.conf 67 done 68 69 # Don't force networkd to reload as this can break phosphor-networkd 70 # Fall back to reload only if ip link commands fail 71 (ip -6 route replace default via "$rtr" onlink dev "$RA_IF" metric "$ROUTE_METRIC" && \ 72 ip -6 neigh replace "$rtr" dev "$RA_IF" lladdr "$mac") || \ 73 gbmc_net_networkd_reload "$RA_IF" || true 74 75 echo "Set router $rtr on $RA_IF" >&2 76 elif [[ ${op} = "remove" ]]; then 77 # Override any existing gateway information within files 78 # Make sure we cover `00-*` and `-*` files 79 for file in /run/systemd/network/{00,}-bmc-$RA_IF.network.d/10-gateway.conf; do 80 rm -rf "$file" 81 done 82 83 # Fall back to reload if remove failed 84 (ip -6 route del default via "$rtr" onlink dev "$RA_IF" metric "$ROUTE_METRIC" && \ 85 ip -6 neigh del "$rtr" dev "$RA_IF" lladdr "$mac") || \ 86 gbmc_net_networkd_reload "$RA_IF" || true 87 88 echo "Del router $rtr on $RA_IF" >&2 89 fi 90} 91 92default_update_fqdn() { 93 local fqdn="$1" 94 [ -z "$fqdn" ] && return 95 hostnamectl set-hostname "$fqdn" || true 96 echo "Set hostname $fqdn on $RA_IF" >&2 97} 98 99# mininum retry time 100min_w=10 101# rdisc time 102r_timeout=10 103# routing expiration time 104expire_time=180 105# rs intervals 106rs_intervals=30 107declare -A rtrs 108rtrs=() 109while true; do 110 # shellcheck disable=SC2206 111 data=(${rtrs["${old_rtr}"]-}) 112 next_rs= 113 if [ -z "${data[1]}" ]; then 114 next_rs=$(( min_w + SECONDS )) 115 else 116 next_rs=$(( rs_intervals + SECONDS )) 117 fi 118 args=(-m "$RA_IF" -w $(( r_timeout * 1000 )) -r 1) 119 while read -r line; do 120 # `script` terminates all lines with a CRLF, remove it 121 line="${line:0:-1}" 122 # shellcheck disable=SC2026 123 if [ -z "$line" ]; then 124 lifetime=-1 125 mac= 126 hextet= 127 pfx= 128 host= 129 domain= 130 elif [[ "$line" =~ ^Router' 'lifetime' '*:' '*([0-9]*) ]]; then 131 lifetime="${BASH_REMATCH[1]}" 132 elif [[ "$line" =~ ^Source' 'link-layer' 'address' '*:' '*([a-fA-F0-9:]*)$ ]]; then 133 mac="${BASH_REMATCH[1]}" 134 elif [[ "$line" =~ ^Prefix' '*:' '*(.*)/([0-9]+)$ ]]; then 135 t_pfx="${BASH_REMATCH[1]}" 136 t_pfx_len="${BASH_REMATCH[2]}" 137 ip_to_bytes t_pfx_b "$t_pfx" || continue 138 (( (t_pfx_len == 76 || t_pfx_len == 80) && (t_pfx_b[8] & 0xfd) == 0xfd )) || continue 139 (( t_pfx_b[9] &= 0xf0 )) 140 (( t_pfx_b[9] |= IP_OFFSET )) 141 hextet="fd$(printf '%02x' "${t_pfx_b[9]}")" 142 pfx="$(ip_bytes_to_str t_pfx_b)" 143 elif [[ "$line" =~ ^'DNS search list'' '*:' '*([^.]+)(.*[.]google[.]com)' '*$ ]]; then 144 # Ideally, we use PCRE and with lookahead and can do this in a single regex 145 # ^([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)$ 146 # Instead we do multiple steps to extract the needed info 147 host="${BASH_REMATCH[1]}" 148 domain="${BASH_REMATCH[2]#.}" 149 if [[ "$host" =~ (-n[a-fA-F0-9]{1,4})$ ]]; then 150 host="${host%"${BASH_REMATCH[1]}"}" 151 fi 152 elif [[ "$line" =~ ^from' '(.*)$ ]]; then 153 rtr="${BASH_REMATCH[1]}" 154 # Only valid default routers can be considered, 0 lifetime implies 155 # a non-default router 156 (( lifetime > 0 )) || continue 157 158 dl=$((expire_time + SECONDS)) 159 fqdn= 160 if [[ -n $host && -n $hextet && -n $domain ]]; then 161 fqdn="$host-n$hextet.$domain" 162 fi 163 rtrs["$rtr"]="$mac $dl $pfx $fqdn" 164 # We have some notoriously noisy lab environments with many routers being broadcast 165 # We always prefer "fe80::1" in prod and labs for routing, so prefer that gateway. 166 # We also want to take the first router we find to speed up acquisition on boot. 167 if [[ "$rtr" = "fe80::1" || "$old_rtr" = "invalid" ]]; then 168 if [[ "$rtr" != "$old_rtr" && "$mac" != "$old_mac" ]]; then 169 echo "Got defgw $rtr at $mac on $RA_IF" >&2 170 update_rtr "$rtr" "$mac" || true 171 old_mac="$mac" 172 old_rtr="$rtr" 173 fi 174 fi 175 # Only update router properties if we use this router 176 [[ "$rtr" == "$old_rtr" ]] || continue 177 if [[ $pfx != "$old_pfx" ]]; then 178 echo "Got PFX $pfx from $rtr on $RA_IF" >&2 179 old_pfx="$pfx" 180 update_pfx "$pfx" || true 181 fi 182 if [[ $fqdn != "$old_fqdn" ]]; then 183 echo "Got FQDN $fqdn from $rtr on $RA_IF" >&2 184 old_fqdn="$fqdn" 185 update_fqdn "$fqdn" || true 186 fi 187 fi 188 done < <(exec script -q -c "rdisc6 ${args[*]}" /dev/null 2>/dev/null) 189 # Purge any expired routers 190 for rtr in "${!rtrs[@]}"; do 191 # shellcheck disable=SC2206 192 data=(${rtrs["$rtr"]}) 193 dl=${data[1]} 194 if (( dl <= SECONDS )); then 195 unset "rtrs[$rtr]" 196 fi 197 done 198 # Consider changing the gateway if the old one doesn't send RAs for the entire period 199 # This ensures we don't flip flop between multiple defaults if they exist. 200 if [[ "$old_rtr" != "invalid" && -z "${rtrs["$old_rtr"]-}" ]]; then 201 echo "Old router $old_rtr disappeared" >&2 202 # replace routing config 203 found="false" 204 for rtr in "${!rtrs[@]}"; do 205 # shellcheck disable=SC2206 206 data=(${rtrs["$rtr"]}) 207 mac=${data[0]} 208 dl=${data[1]} 209 pfx=${data[2]} 210 fqdn=${data[3]} 211 update_rtr "$rtr" "$mac" || true 212 update_pfx "$pfx" || true 213 update_fqdn "$fqdn" || true 214 old_rtr="$rtr" 215 old_pfx="$pfx" 216 old_mac="$mac" 217 old_fqdn="$fqdn" 218 found="true" 219 break 220 done 221 # no other route exsits, removing the route 222 if [[ "$found" = "false" ]]; then 223 update_rtr "$old_rtr" "$old_mac" "remove" || true 224 old_rtr=invalid 225 old_mac=invalid 226 fi 227 fi 228 229 # If rdisc6 exits early we still want to wait for the deadline before retrying 230 (( timeout = next_rs - SECONDS )) 231 sleep $(( timeout < 0 ? 0 : timeout )) 232done 233