133f70c2dSWilliam A. Kennington III#!/bin/bash
233f70c2dSWilliam A. Kennington III# Copyright 2021 Google LLC
333f70c2dSWilliam A. Kennington III#
433f70c2dSWilliam A. Kennington III# Licensed under the Apache License, Version 2.0 (the "License");
533f70c2dSWilliam A. Kennington III# you may not use this file except in compliance with the License.
633f70c2dSWilliam A. Kennington III# You may obtain a copy of the License at
733f70c2dSWilliam A. Kennington III#
833f70c2dSWilliam A. Kennington III#      http://www.apache.org/licenses/LICENSE-2.0
933f70c2dSWilliam A. Kennington III#
1033f70c2dSWilliam A. Kennington III# Unless required by applicable law or agreed to in writing, software
1133f70c2dSWilliam A. Kennington III# distributed under the License is distributed on an "AS IS" BASIS,
1233f70c2dSWilliam A. Kennington III# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1333f70c2dSWilliam A. Kennington III# See the License for the specific language governing permissions and
1433f70c2dSWilliam A. Kennington III# limitations under the License.
1533f70c2dSWilliam A. Kennington III
1633f70c2dSWilliam A. Kennington III# shellcheck source=meta-google/recipes-google/networking/network-sh/lib.sh
1733f70c2dSWilliam A. Kennington IIIsource /usr/share/network/lib.sh || exit
18*a108fcdfSWilliam A. Kennington III# shellcheck source=meta-google/recipes-google/networking/gbmc-net-common/gbmc-net-lib.sh
19*a108fcdfSWilliam A. Kennington IIIsource /usr/share/gbmc-net-lib.sh || exit
2033f70c2dSWilliam A. Kennington III
2133f70c2dSWilliam A. Kennington III: "${RA_IF:?No RA interface set}"
2233f70c2dSWilliam A. Kennington III: "${IP_OFFSET=?1}"
2333f70c2dSWilliam A. Kennington III: "${ROUTE_METRIC:?No Metric set}"
2433f70c2dSWilliam A. Kennington III
2533f70c2dSWilliam A. Kennington III# We would prefer empty string but it's easier for associative array handling
2633f70c2dSWilliam A. Kennington III# to use invalid
2733f70c2dSWilliam A. Kennington IIIold_rtr=invalid
2833f70c2dSWilliam A. Kennington IIIold_mac=invalid
2933f70c2dSWilliam A. Kennington IIIold_pfx=invalid
3033f70c2dSWilliam A. Kennington IIIold_fqdn=invalid
3133f70c2dSWilliam A. Kennington III
3233f70c2dSWilliam A. Kennington IIIdefault_update_rtr() {
3333f70c2dSWilliam A. Kennington III  local rtr="$1"
3433f70c2dSWilliam A. Kennington III  local mac="$2"
3533f70c2dSWilliam A. Kennington III
3633f70c2dSWilliam A. Kennington III  if ip addr show | grep -q "^[ ]*inet6 $rtr/"; then
3733f70c2dSWilliam A. Kennington III    echo "Router is ourself, ignoring" >&2
3833f70c2dSWilliam A. Kennington III    return 0
3933f70c2dSWilliam A. Kennington III  fi
4033f70c2dSWilliam A. Kennington III
4133f70c2dSWilliam A. Kennington III  # In case we don't have a base network file, make one
4233f70c2dSWilliam A. Kennington III  # this is intentionally 00- as it will not preceed /etc/systemd/network/00-*
4333f70c2dSWilliam A. Kennington III  # or /lib/systemd/network/-* files.
4433f70c2dSWilliam A. Kennington III  local file=/run/systemd/network/00-bmc-$RA_IF.network
4533f70c2dSWilliam A. Kennington III  printf '[Match]\nName=%s\n[Network]\nDHCP=false\nIPv6AcceptRA=false\nLinkLocalAddressing=yes' \
4633f70c2dSWilliam A. Kennington III    "$RA_IF" >"$file"
4733f70c2dSWilliam A. Kennington III
4833f70c2dSWilliam A. Kennington III  # Override any existing gateway information within files
4933f70c2dSWilliam A. Kennington III  # Make sure we cover `00-*` and `-*` files
5033f70c2dSWilliam A. Kennington III  for file in /run/systemd/network/{00,}-bmc-$RA_IF.network; do
5133f70c2dSWilliam A. Kennington III    mkdir -p "$file.d"
5233f70c2dSWilliam A. Kennington III    printf '[Route]\nGateway=%s\nGatewayOnLink=true\nMetric=%d\n[Neighbor]\nMACAddress=%s\nAddress=%s' \
5333f70c2dSWilliam A. Kennington III      "$rtr" "$ROUTE_METRIC" "$mac" "$rtr" >"$file.d"/10-gateway.conf
5433f70c2dSWilliam A. Kennington III  done
5533f70c2dSWilliam A. Kennington III
5633f70c2dSWilliam A. Kennington III  # Don't force networkd to reload as this can break phosphor-networkd
5733f70c2dSWilliam A. Kennington III  # Fall back to reload only if ip link commands fail
5833f70c2dSWilliam A. Kennington III  (ip -6 route replace default via "$rtr" onlink dev "$RA_IF" metric "$ROUTE_METRIC" && \
5933f70c2dSWilliam A. Kennington III    ip -6 neigh replace "$rtr" dev "$RA_IF" lladdr "$mac") || \
60*a108fcdfSWilliam A. Kennington III    gbmc_net_networkd_reload "$RA_IF" || true
6133f70c2dSWilliam A. Kennington III
6233f70c2dSWilliam A. Kennington III  echo "Set router $rtr on $RA_IF" >&2
6333f70c2dSWilliam A. Kennington III}
6433f70c2dSWilliam A. Kennington III
6533f70c2dSWilliam A. Kennington IIIdefault_update_fqdn() {
6633f70c2dSWilliam A. Kennington III  local fqdn="$1"
6733f70c2dSWilliam A. Kennington III  [ -z "$fqdn" ] && return
6833f70c2dSWilliam A. Kennington III  hostnamectl set-hostname "$fqdn" || true
6933f70c2dSWilliam A. Kennington III  echo "Set hostname $fqdn on $RA_IF" >&2
7033f70c2dSWilliam A. Kennington III}
7133f70c2dSWilliam A. Kennington III
7233f70c2dSWilliam A. Kennington IIIretries=1
7333f70c2dSWilliam A. Kennington IIImin_w=10
7433f70c2dSWilliam A. Kennington IIIdeclare -A rtrs
7533f70c2dSWilliam A. Kennington IIIrtrs=()
7633f70c2dSWilliam A. Kennington IIIwhile true; do
7733f70c2dSWilliam A. Kennington III  # shellcheck disable=SC2206
7833f70c2dSWilliam A. Kennington III  data=(${rtrs["${old_rtr}"]-})
7933f70c2dSWilliam A. Kennington III  curr_dl="${data[1]-$(( min_w + SECONDS ))}"
8033f70c2dSWilliam A. Kennington III  args=(-m "$RA_IF" -w $(( (curr_dl - SECONDS) * 1000 )))
8133f70c2dSWilliam A. Kennington III  if (( retries > 0 )); then
8233f70c2dSWilliam A. Kennington III    args+=(-r "$retries")
8333f70c2dSWilliam A. Kennington III  else
8433f70c2dSWilliam A. Kennington III    args+=(-d)
8533f70c2dSWilliam A. Kennington III  fi
8633f70c2dSWilliam A. Kennington III  while read -r line; do
8733f70c2dSWilliam A. Kennington III    # `script` terminates all lines with a CRLF, remove it
8833f70c2dSWilliam A. Kennington III    line="${line:0:-1}"
8933f70c2dSWilliam A. Kennington III    # shellcheck disable=SC2026
9033f70c2dSWilliam A. Kennington III    if [ -z "$line" ]; then
9133f70c2dSWilliam A. Kennington III      lifetime=-1
9233f70c2dSWilliam A. Kennington III      mac=
9333f70c2dSWilliam A. Kennington III      hextet=
9433f70c2dSWilliam A. Kennington III      pfx=
9533f70c2dSWilliam A. Kennington III      host=
9633f70c2dSWilliam A. Kennington III      domain=
9733f70c2dSWilliam A. Kennington III    elif [[ "$line" =~ ^Router' 'lifetime' '*:' '*([0-9]*) ]]; then
9833f70c2dSWilliam A. Kennington III      lifetime="${BASH_REMATCH[1]}"
9933f70c2dSWilliam A. Kennington III    elif [[ "$line" =~ ^Source' 'link-layer' 'address' '*:' '*([a-fA-F0-9:]*)$ ]]; then
10033f70c2dSWilliam A. Kennington III      mac="${BASH_REMATCH[1]}"
10133f70c2dSWilliam A. Kennington III    elif [[ "$line" =~ ^Prefix' '*:' '*(.*)/([0-9]+)$ ]]; then
10233f70c2dSWilliam A. Kennington III      t_pfx="${BASH_REMATCH[1]}"
10333f70c2dSWilliam A. Kennington III      t_pfx_len="${BASH_REMATCH[2]}"
10433f70c2dSWilliam A. Kennington III      ip_to_bytes t_pfx_b "$t_pfx" || continue
10533f70c2dSWilliam A. Kennington III      (( (t_pfx_len == 76 || t_pfx_len == 80) && (t_pfx_b[8] & 0xfd) == 0xfd )) || continue
10633f70c2dSWilliam A. Kennington III      (( t_pfx_b[9] &= 0xf0 ))
10733f70c2dSWilliam A. Kennington III      (( t_pfx_b[9] |= IP_OFFSET ))
10833f70c2dSWilliam A. Kennington III      hextet="fd$(printf '%02x' "${t_pfx_b[9]}")"
10933f70c2dSWilliam A. Kennington III      pfx="$(ip_bytes_to_str t_pfx_b)"
11033f70c2dSWilliam A. Kennington III    elif [[ "$line" =~ ^'DNS search list'' '*:' '*([^.]+)(.*[.]google[.]com)' '*$ ]]; then
11133f70c2dSWilliam A. Kennington III      # Ideally, we use PCRE and with lookahead and can do this in a single regex
11233f70c2dSWilliam A. Kennington III      #   ^([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)$
11333f70c2dSWilliam A. Kennington III      # Instead we do multiple steps to extract the needed info
11433f70c2dSWilliam A. Kennington III      host="${BASH_REMATCH[1]}"
11533f70c2dSWilliam A. Kennington III      domain="${BASH_REMATCH[2]#.}"
11633f70c2dSWilliam A. Kennington III      if [[ "$host" =~ (-n[a-fA-F0-9]{1,4})$ ]]; then
11733f70c2dSWilliam A. Kennington III        host="${host%"${BASH_REMATCH[1]}"}"
11833f70c2dSWilliam A. Kennington III      fi
11933f70c2dSWilliam A. Kennington III    elif [[ "$line" =~ ^from' '(.*)$ ]]; then
12033f70c2dSWilliam A. Kennington III      rtr="${BASH_REMATCH[1]}"
12133f70c2dSWilliam A. Kennington III      # Only valid default routers can be considered, 0 lifetime implies
12233f70c2dSWilliam A. Kennington III      # a non-default router
12333f70c2dSWilliam A. Kennington III      (( lifetime > 0 )) || continue
12433f70c2dSWilliam A. Kennington III
12533f70c2dSWilliam A. Kennington III      dl=$((lifetime + SECONDS))
12633f70c2dSWilliam A. Kennington III      fqdn=
12733f70c2dSWilliam A. Kennington III      if [[ -n $host && -n $hextet && -n $domain ]]; then
12833f70c2dSWilliam A. Kennington III        fqdn="$host-n$hextet.$domain"
12933f70c2dSWilliam A. Kennington III      fi
13033f70c2dSWilliam A. Kennington III      rtrs["$rtr"]="$mac $dl $pfx $fqdn"
13133f70c2dSWilliam A. Kennington III      # We have some notoriously noisy lab environments with many routers being broadcast
13233f70c2dSWilliam A. Kennington III      # We always prefer "fe80::1" in prod and labs for routing, so prefer that gateway.
13333f70c2dSWilliam A. Kennington III      # We also want to take the first router we find to speed up acquisition on boot.
13433f70c2dSWilliam A. Kennington III      if [[ "$rtr" = "fe80::1" || "$old_rtr" = "invalid" ]]; then
13533f70c2dSWilliam A. Kennington III        if [[ "$rtr" != "$old_rtr" && "$mac" != "$old_mac" ]]; then
13633f70c2dSWilliam A. Kennington III          echo "Got defgw $rtr at $mac on $RA_IF" >&2
13733f70c2dSWilliam A. Kennington III          update_rtr "$rtr" "$mac" || true
13833f70c2dSWilliam A. Kennington III          retries=-1
13933f70c2dSWilliam A. Kennington III          old_mac="$mac"
14033f70c2dSWilliam A. Kennington III          old_rtr="$rtr"
14133f70c2dSWilliam A. Kennington III        fi
14233f70c2dSWilliam A. Kennington III      fi
14333f70c2dSWilliam A. Kennington III      # Only update router properties if we use this router
14433f70c2dSWilliam A. Kennington III      [[ "$rtr" == "$old_rtr" ]] || continue
14533f70c2dSWilliam A. Kennington III      if [[ $pfx != "$old_pfx" ]]; then
14633f70c2dSWilliam A. Kennington III        echo "Got PFX $pfx from $rtr on $RA_IF" >&2
14733f70c2dSWilliam A. Kennington III        old_pfx="$pfx"
14833f70c2dSWilliam A. Kennington III        update_pfx "$pfx" || true
14933f70c2dSWilliam A. Kennington III      fi
15033f70c2dSWilliam A. Kennington III      if [[ $fqdn != "$old_fqdn" ]]; then
15133f70c2dSWilliam A. Kennington III        echo "Got FQDN $fqdn from $rtr on $RA_IF" >&2
15233f70c2dSWilliam A. Kennington III        old_fqdn="$fqdn"
15333f70c2dSWilliam A. Kennington III        update_fqdn "$fqdn" || true
15433f70c2dSWilliam A. Kennington III      fi
15533f70c2dSWilliam A. Kennington III    fi
15633f70c2dSWilliam A. Kennington III  done < <(exec script -q -c "rdisc6 ${args[*]}" /dev/null 2>/dev/null)
15733f70c2dSWilliam A. Kennington III  # Purge any expired routers
15833f70c2dSWilliam A. Kennington III  for rtr in "${!rtrs[@]}"; do
15933f70c2dSWilliam A. Kennington III    # shellcheck disable=SC2206
16033f70c2dSWilliam A. Kennington III    data=(${rtrs["$rtr"]})
16133f70c2dSWilliam A. Kennington III    dl=${data[1]}
16233f70c2dSWilliam A. Kennington III    if (( dl <= SECONDS )); then
16333f70c2dSWilliam A. Kennington III      unset "rtrs[$rtr]"
16433f70c2dSWilliam A. Kennington III    fi
16533f70c2dSWilliam A. Kennington III  done
16633f70c2dSWilliam A. Kennington III  # Consider changing the gateway if the old one doesn't send RAs for the entire period
16733f70c2dSWilliam A. Kennington III  # This ensures we don't flip flop between multiple defaults if they exist.
16833f70c2dSWilliam A. Kennington III  if [[ "$old_rtr" != "invalid" && -z "${rtrs["$old_rtr"]-}" ]]; then
16933f70c2dSWilliam A. Kennington III    echo "Old router $old_rtr disappeared" >&2
17033f70c2dSWilliam A. Kennington III    old_rtr=invalid
17133f70c2dSWilliam A. Kennington III    for rtr in "${!rtrs[@]}"; do
17233f70c2dSWilliam A. Kennington III      # shellcheck disable=SC2206
17333f70c2dSWilliam A. Kennington III      data=(${rtrs["$rtr"]})
17433f70c2dSWilliam A. Kennington III      mac=${data[0]}
17533f70c2dSWilliam A. Kennington III      dl=${data[1]}
17633f70c2dSWilliam A. Kennington III      pfx=${data[2]}
17733f70c2dSWilliam A. Kennington III      fqdn=${data[3]}
17833f70c2dSWilliam A. Kennington III      update_rtr "$rtr" "$mac" || true
17933f70c2dSWilliam A. Kennington III      update_pfx "$pfx" || true
18033f70c2dSWilliam A. Kennington III      update_fqdn "$fqdn" || true
18133f70c2dSWilliam A. Kennington III      break
18233f70c2dSWilliam A. Kennington III    done
18333f70c2dSWilliam A. Kennington III  fi
18433f70c2dSWilliam A. Kennington III
18533f70c2dSWilliam A. Kennington III  # If rdisc6 exits early we still want to wait for the deadline before retrying
18633f70c2dSWilliam A. Kennington III  (( timeout = curr_dl - SECONDS ))
18733f70c2dSWilliam A. Kennington III  sleep $(( timeout < 0 ? 0 : timeout ))
18833f70c2dSWilliam A. Kennington IIIdone
189