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