meta-google: gbmc-bridge: Rework IP address persistence

This consolidates all of the mechanisms which write out a persistent IP
into a single place. It also transitions to writing a very simple
persistent file instead of systemd style network units.

Change-Id: Ib99d7646178d2c5383cf23b09248bf24544c1d9e
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/meta-google/recipes-google/ncsi/files/25-gbmc-ncsi-clear-ip.sh.in b/meta-google/recipes-google/ncsi/files/50-gbmc-ncsi-clear-ip.sh.in
similarity index 64%
rename from meta-google/recipes-google/ncsi/files/25-gbmc-ncsi-clear-ip.sh.in
rename to meta-google/recipes-google/ncsi/files/50-gbmc-ncsi-clear-ip.sh.in
index e17a5e2..5056cfd 100644
--- a/meta-google/recipes-google/ncsi/files/25-gbmc-ncsi-clear-ip.sh.in
+++ b/meta-google/recipes-google/ncsi/files/50-gbmc-ncsi-clear-ip.sh.in
@@ -12,15 +12,22 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-[ -z "${gbmc_ncsi_clear_ip-}" ] || exit
+[ -n "${gbmc_ncsi_clear_ip-}" ] && return
 
 source /usr/libexec/ncsid_lib.sh || exit
 
 gbmc_ncsi_clear_ip_hook() {
-  UpdateIP xyz.openbmc_project.Network '@NCSI_IF@' '0.0.0.0' '0' || true
-  UpdateIP xyz.openbmc_project.Network '@NCSI_IF@' '::' '0' || true
+  local ip="${1-}"
+
+  # We only want to clear our IPs if we are assigning a new IP
+  [ -z "$ip" ] && return
+
+  echo "Removing Persistent NCSI IPs" >&2
+  SetStatic xyz.openbmc_project.Network '@NCSI_IF@' 2>/dev/null || true
+  UpdateIP xyz.openbmc_project.Network '@NCSI_IF@' '0.0.0.0' '0' 2>/dev/null || true
+  UpdateIP xyz.openbmc_project.Network '@NCSI_IF@' '::' '0' 2>/dev/null || true
 }
 
-GBMC_BR_DHCP_HOOKS+=(gbmc_ncsi_clear_ip_hook)
+GBMC_BR_LIB_SET_IP_HOOKS+=(gbmc_ncsi_clear_ip_hook)
 
 gbmc_ncsi_clear_ip=1
diff --git a/meta-google/recipes-google/ncsi/files/gbmc-ncsi-br-pub-addr.sh.in b/meta-google/recipes-google/ncsi/files/gbmc-ncsi-br-pub-addr.sh.in
index 9f008a9..1992dd1 100644
--- a/meta-google/recipes-google/ncsi/files/gbmc-ncsi-br-pub-addr.sh.in
+++ b/meta-google/recipes-google/ncsi/files/gbmc-ncsi-br-pub-addr.sh.in
@@ -12,7 +12,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-[ -z "${gbmc_ncsi_br_pub_addr_lib-}" ] || return
+[ -n "${gbmc_ncsi_br_pub_addr_lib-}" ] && return
+
+[ ! -e /usr/share/gbmc-br-lib.sh ] && return
+
+source /usr/share/network/lib.sh || exit
+source /usr/share/gbmc-br-lib.sh || exit
 
 gbmc_ncsi_br_pub_addr_init=
 gbmc_ncsi_br_pub_addr_lastip=
@@ -44,55 +49,16 @@
   local contents=
   if (( ${#pfx_bytes[@]} != 0 )); then
     pfx_bytes[8]=0xfd
-    # Save our old prefix assuming we have one
-    local old_offset="${pfx_bytes[9]}"
-    if (( old_offset == 0 )); then
-      old_offset=0x01
-    else
-      pfx_bytes[9]=0x00
+    # We never want to use the stateless pfx
+    if (( pfx_bytes[9] == 0 )); then
+      pfx_bytes[9]=0x01
     fi
-    local stateless_pfx="$(ip_bytes_to_str pfx_bytes)"
-    pfx_bytes[9]="$old_offset"
-    local ncsi_pfx="$(ip_bytes_to_str pfx_bytes)"
-    read -r -d '' contents <<EOF
-[Network]
-Address=$ncsi_pfx/128
-IPv6PrefixDelegation=yes
-[IPv6PrefixDelegation]
-RouterLifetimeSec=60
-[IPv6Prefix]
-Prefix=$stateless_pfx/80
-PreferredLifetimeSec=60
-ValidLifetimeSec=60
-[IPv6RoutePrefix]
-Route=$ncsi_pfx/80
-LifetimeSec=60
-[Route]
-Destination=$stateless_pfx/76
-Type=unreachable
-Metric=1024
-EOF
-    # Delete DHCP configured addresses if we have a host published address
-    rm -f /etc/systemd/network/{00,}-bmc-gbmcbr.network.d/50-public.conf
-  fi
-
-  local file
-  for file in /run/systemd/network/{00,}-bmc-gbmcbr.network.d/50-public.conf; do
-    mkdir -p -m 755 "$(dirname "$file")"
-    if [ -z "$contents" ]; then
-      rm -f "$file"
-    else
-      printf '%s' "$contents" >"$file"
-    fi
-  done
-
-  # Ensure that systemd-networkd performs a reconfiguration as it doesn't
-  # currently check the mtime of drop-in files.
-  touch -c /lib/systemd/network/*-bmc-gbmcbr.network
-
-  if [ "$(systemctl is-active systemd-networkd)" != 'inactive' ]; then
-    networkctl reload
-    networkctl reconfigure gbmcbr
+    # Remove any existing persisted IP
+    gbmc_br_set_ip
+    # Load the IP to the bridge non-persistently
+    gbmc_br_reload_ip "$(ip_bytes_to_str pfx_bytes)"
+  else
+    gbmc_br_reload_ip
   fi
 }
 
diff --git a/meta-google/recipes-google/ncsi/files/gbmc-ncsi-ip-from-ra.sh.in b/meta-google/recipes-google/ncsi/files/gbmc-ncsi-ip-from-ra.sh.in
index 6255f70..5daa152 100755
--- a/meta-google/recipes-google/ncsi/files/gbmc-ncsi-ip-from-ra.sh.in
+++ b/meta-google/recipes-google/ncsi/files/gbmc-ncsi-ip-from-ra.sh.in
@@ -13,8 +13,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+[ ! -e /usr/share/gbmc-br-lib.sh ] && exit
+
 source /usr/share/network/lib.sh || exit
-source /usr/libexec/ncsid_lib.sh || exit
+source /usr/share/gbmc-br-lib.sh || exit
 
 NCSI_IF='@NCSI_IF@'
 
@@ -44,45 +46,8 @@
   # We no longer need NCSId if we are in this configuration
   systemctl stop --no-block ncsid@"$NCSI_IF" || true
 
-  # Delete any stale IP Addresses from the primary interface as we won't use them
-  UpdateIP xyz.openbmc_project.Network "$NCSI_IF" '0.0.0.0' '0' || true
-  UpdateIP xyz.openbmc_project.Network "$NCSI_IF" '::' '0' || true
-
-  read -r -d '' contents <<EOF
-[Network]
-Address=$pfx/128
-IPv6PrefixDelegation=yes
-[IPv6PrefixDelegation]
-RouterLifetimeSec=60
-[IPv6Prefix]
-Prefix=$stateless_pfx/80
-PreferredLifetimeSec=60
-ValidLifetimeSec=60
-[IPv6RoutePrefix]
-Route=$pfx/80
-LifetimeSec=60
-[Route]
-Destination=$stateless_pfx/76
-Type=unreachable
-Metric=1024
-EOF
-  for file in /run/systemd/network/{00,}-bmc-gbmcbr.network.d/49-public-ra.conf; do
-    mkdir -p -m 755 "$(dirname "$file")"
-    printf '%s' "$contents" >"$file"
-  done
-  touch -c /lib/systemd/network/*-bmc-gbmcbr.network || true
-
-  contents='[Network]'$'\n'
-  contents+="Gateway=$rtr"$'\n'
-  for file in /run/systemd/network/{00,}-bmc-"$NCSI_IF".network.d/49-public-ra.conf; do
-    mkdir -p -m 755 "$(dirname "$file")"
-    printf '%s' "$contents" >"$file"
-  done
-  touch -c /etc/systemd/network/*-bmc-"$NCSI_IF".network || true
-
-  if [ "$(systemctl is-active systemd-networkd)" != 'inactive' ]; then
-    networkctl reload && networkctl reconfigure gbmcbr "$NCSI_IF" || true
-  fi
+  # Save the IP address for the interface
+  gbmc_br_set_ip "$pfx" || true
 
   # DHCP Relay workaround until alternate source port is supported
   # TODO: Remove this once internal relaying cleanups land
@@ -106,8 +71,6 @@
       (( t_pfx_b[9] |= 1 ))
       hextet="fd$(printf '%02x' ${t_pfx_b[9]})"
       pfx="$(ip_bytes_to_str t_pfx_b)"
-      (( t_pfx_b[9] &= 0xf0 ))
-      stateless_pfx="$(ip_bytes_to_str t_pfx_b)"
     elif [[ "$line" =~ ^'DNS search list'' '*:' '*([a-z]+[0-9]+)[^.]*[.](.*.google.com)$ ]]; then
       host="${BASH_REMATCH[1]}"
       domain="${BASH_REMATCH[2]}"
diff --git a/meta-google/recipes-google/ncsi/gbmc-ncsi-config.bb b/meta-google/recipes-google/ncsi/gbmc-ncsi-config.bb
index b761b16..364afee 100644
--- a/meta-google/recipes-google/ncsi/gbmc-ncsi-config.bb
+++ b/meta-google/recipes-google/ncsi/gbmc-ncsi-config.bb
@@ -21,7 +21,7 @@
   file://gbmc-ncsi-br-pub-addr.sh.in \
   file://gbmc-ncsi-br-deprecated-ips.sh.in \
   file://gbmc-ncsi-set-nicenabled.service.in \
-  file://25-gbmc-ncsi-clear-ip.sh.in \
+  file://50-gbmc-ncsi-clear-ip.sh.in \
   "
 
 S = "${WORKDIR}"
@@ -38,7 +38,7 @@
   "
 
 FILES:${PN} += " \
-  ${datadir}/gbmc-br-dhcp \
+  ${datadir}/gbmc-br-lib \
   ${datadir}/gbmc-ip-monitor \
   ${systemd_unitdir} \
   "
@@ -108,11 +108,11 @@
     >${WORKDIR}/gbmc-ncsi-br-deprecated-ips.sh
   install -m644 ${WORKDIR}/gbmc-ncsi-br-deprecated-ips.sh $mondir
 
-  dhcpdir=${D}${datadir}/gbmc-br-dhcp/
-  install -d -m0755 $dhcpdir
-  sed "s,@NCSI_IF@,$if_name,g" ${WORKDIR}/25-gbmc-ncsi-clear-ip.sh.in \
-    >${WORKDIR}/25-gbmc-ncsi-clear-ip.sh
-  install -m644 ${WORKDIR}/25-gbmc-ncsi-clear-ip.sh $dhcpdir
+  brlibdir=${D}${datadir}/gbmc-br-lib/
+  install -d -m0755 $brlibdir
+  sed "s,@NCSI_IF@,$if_name,g" ${WORKDIR}/50-gbmc-ncsi-clear-ip.sh.in \
+    >${WORKDIR}/50-gbmc-ncsi-clear-ip.sh
+  install -m644 ${WORKDIR}/50-gbmc-ncsi-clear-ip.sh $brlibdir
 
   sed "s,@NCSI_IF@,$if_name,g" ${WORKDIR}/gbmc-ncsi-set-nicenabled.service.in \
     >${D}${systemd_system_unitdir}/gbmc-ncsi-set-nicenabled.service
diff --git a/meta-google/recipes-google/networking/gbmc-bridge.bb b/meta-google/recipes-google/networking/gbmc-bridge.bb
index 850b14a..3f2857b 100644
--- a/meta-google/recipes-google/networking/gbmc-bridge.bb
+++ b/meta-google/recipes-google/networking/gbmc-bridge.bb
@@ -25,11 +25,14 @@
   file://gbmc-br-dhcp.service \
   file://gbmc-br-dhcp-term.sh \
   file://gbmc-br-dhcp-term.service \
+  file://gbmc-br-lib.sh \
+  file://gbmc-br-load-ip.service \
   "
 
 FILES:${PN}:append = " \
   ${datadir}/gbmc-ip-monitor \
   ${datadir}/gbmc-br-dhcp \
+  ${datadir}/gbmc-br-lib.sh \
   ${systemd_unitdir}/network \
   ${sysconfdir}/nftables \
   ${sysconfdir}/avahi/services \
@@ -48,6 +51,7 @@
   gbmc-br-ensure-ra.service \
   gbmc-br-dhcp.service \
   gbmc-br-dhcp-term.service \
+  gbmc-br-load-ip.service \
   "
 
 GBMC_BR_MAC_ADDR ?= ""
@@ -111,8 +115,11 @@
   install -m0644 ${WORKDIR}/gbmc-br-ensure-ra.service ${D}${systemd_system_unitdir}/
   install -m0644 ${WORKDIR}/gbmc-br-dhcp.service ${D}${systemd_system_unitdir}/
   install -m0644 ${WORKDIR}/gbmc-br-dhcp-term.service ${D}${systemd_system_unitdir}/
+  install -m0644 ${WORKDIR}/gbmc-br-load-ip.service ${D}${systemd_system_unitdir}/
   install -d -m0755 ${D}${datadir}/gbmc-br-dhcp
   install -m0644 ${WORKDIR}/50-gbmc-psu-hardreset.sh ${D}${datadir}/gbmc-br-dhcp/
+
+  install -m0644 ${WORKDIR}/gbmc-br-lib.sh ${D}${datadir}/
 }
 
 do_rm_work:prepend() {
diff --git a/meta-google/recipes-google/networking/gbmc-bridge/-bmc-gbmcbr.network.in b/meta-google/recipes-google/networking/gbmc-bridge/-bmc-gbmcbr.network.in
index afea5cc..09ef620 100644
--- a/meta-google/recipes-google/networking/gbmc-bridge/-bmc-gbmcbr.network.in
+++ b/meta-google/recipes-google/networking/gbmc-bridge/-bmc-gbmcbr.network.in
@@ -7,5 +7,8 @@
 LLMNR=true
 MulticastDNS=true
 LinkLocalAddressing=ipv6
+IPv6PrefixDelegation=yes
 [IPv6AcceptRA]
 DHCPv6Client=false
+[IPv6PrefixDelegation]
+RouterLifetimeSec=30
diff --git a/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-dhcp.sh b/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-dhcp.sh
index 19fa7b1..9c61036 100644
--- a/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-dhcp.sh
+++ b/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-dhcp.sh
@@ -15,27 +15,20 @@
 
 # A list of functions which get executed for each bound DHCP lease.
 # These are configured by the files included below.
+# Shellcheck does not understand how this gets referenced
+# shellcheck disable=SC2034
 GBMC_BR_DHCP_HOOKS=()
 
-# Load configurations from a known location in the filesystem to populate
-# hooks that are executed after each event.
-shopt -s nullglob
-for conf in /usr/share/gbmc-br-dhcp/*.sh; do
-  # SC doesn't like dynamic source loading
-  # shellcheck disable=SC1090
-  source "$conf"
-done
-
-gbmc_br_dhcp_run_hooks() {
-  local hook
-  for hook in "${GBMC_BR_DHCP_HOOKS[@]}"; do
-    "$hook" || return
-  done
-}
-
 # SC can't find this path during repotest
 # shellcheck disable=SC1091
 source /usr/share/network/lib.sh || exit
+# SC can't find this path during repotest
+# shellcheck disable=SC1091
+source /usr/share/gbmc-br-lib.sh || exit
+
+# Load configurations from a known location in the filesystem to populate
+# hooks that are executed after each event.
+gbmc_br_source_dir /usr/share/gbmc-br-dhcp || exit
 
 # Write out the current PID and cleanup when complete
 trap 'rm -f /run/gbmc-br-dhcp.pid' EXIT
@@ -51,57 +44,25 @@
   # Ensure we are a BMC and have a suffix nibble, the 0th index is reserved
   if (( pfx_bytes[8] != 0xfd || pfx_bytes[9] & 0xf == 0 )); then
     echo "Invalid address" >&2
-    exit
+    exit 1
   fi
   # Ensure we don't have more than a /80 address
   for (( i = 10; i < 16; ++i )); do
     if (( pfx_bytes[i] != 0 )); then
       echo "Invalid address" >&2
-      exit
+      exit 1
     fi
   done
 
   pfx="$(ip_bytes_to_str pfx_bytes)"
-  (( pfx_bytes[9] &= 0xf0 ))
-  stateless_pfx="$(ip_bytes_to_str pfx_bytes)"
-  read -r -d '' contents <<EOF
-[Network]
-Address=$pfx/128
-IPv6PrefixDelegation=yes
-[IPv6PrefixDelegation]
-RouterLifetimeSec=60
-[IPv6Prefix]
-Prefix=$stateless_pfx/80
-PreferredLifetimeSec=60
-ValidLifetimeSec=60
-[IPv6RoutePrefix]
-Route=$pfx/80
-LifetimeSec=60
-[Route]
-Destination=$stateless_pfx/76
-Type=unreachable
-Metric=1024
-EOF
-
-  for file in /etc/systemd/network/{00,}-bmc-gbmcbr.network.d/50-public.conf; do
-    mkdir -p "$(dirname "$file")"
-    printf '%s' "$contents" >"$file"
-  done
-
-  # Ensure that systemd-networkd performs a reconfiguration as it doesn't
-  # currently check the mtime of drop-in files.
-  touch -c /lib/systemd/network/*-bmc-gbmcbr.network
-
-  if [ "$(systemctl is-active systemd-networkd)" != 'inactive' ]; then
-    networkctl reload && networkctl reconfigure gbmcbr
-  fi
+  gbmc_br_set_ip "$pfx" || exit
 
   if [ -n "${fqdn-}" ]; then
     echo "Using hostname $fqdn" >&2
     hostnamectl set-hostname "$fqdn" || true
   fi
 
-  gbmc_br_dhcp_run_hooks || exit
+  gbmc_br_run_hooks GBMC_BR_DHCP_HOOKS || exit
 
   # Ensure that the installer knows we have completed processing DHCP by
   # running a service that reports completion
diff --git a/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-lib.sh b/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-lib.sh
new file mode 100644
index 0000000..7ccee8e
--- /dev/null
+++ b/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-lib.sh
@@ -0,0 +1,133 @@
+#!/bin/bash
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+[ -n "${gbmc_br_lib_init-}" ] && return
+
+# SC can't find this path during repotest
+# shellcheck disable=SC1091
+source /usr/share/network/lib.sh || exit
+
+# A list of functions which get executed for each configured IP.
+# These are configured by the files included below.
+# Shellcheck does not understand how this gets referenced
+# shellcheck disable=SC2034
+GBMC_BR_LIB_SET_IP_HOOKS=()
+
+gbmc_br_source_dir() {
+  local dir="$1"
+
+  local file
+  while read -r -d $'\0' file; do
+    # SC doesn't like dynamic source loading
+    # shellcheck disable=SC1090
+    source "$file" || return
+  done < <(shopt -s nullglob; for f in "$dir"/*.sh; do printf '%s\0' "$f"; done)
+}
+
+# Load configurations from a known location in the filesystem to populate
+# hooks that are executed after each event.
+gbmc_br_source_dir /usr/share/gbmc-br-lib || exit
+
+gbmc_br_run_hooks() {
+  local -n hookvar="$1"
+  shift
+  local hook
+  for hook in "${hookvar[@]}"; do
+    "$hook" "$@" || return
+  done
+}
+
+gbmc_br_reload() {
+  if [ "$(systemctl is-active systemd-networkd)" != 'inactive' ]; then
+    networkctl reload && networkctl reconfigure gbmcbr
+  fi
+}
+
+gbmc_br_no_ip() {
+  echo "Runtime removing gbmcbr IP" >&2
+  rm -f /run/systemd/network/{00,}-bmc-gbmcbr.network.d/50-public.conf
+  gbmc_br_reload
+}
+
+gbmc_br_reload_ip() {
+  local ip="${1-}"
+
+  if [ -z "$ip" ] && ! ip="$(cat /var/google/gbmc-br-ip 2>/dev/null)"; then
+    echo "Ignoring unconfigured IP" >&2
+    gbmc_br_no_ip
+    return 0
+  fi
+
+  local pfx_bytes=()
+  if ! ip_to_bytes pfx_bytes "$ip"; then
+    echo "Ignoring Invalid IPv6: $ip" >&2
+    gbmc_br_no_ip
+    return 0
+  fi
+
+  local pfx
+  pfx="$(ip_bytes_to_str pfx_bytes)"
+  (( pfx_bytes[9] &= 0xf0 ))
+  local stateless_pfx
+  stateless_pfx="$(ip_bytes_to_str pfx_bytes)"
+  local contents
+  read -r -d '' contents <<EOF
+[Network]
+Address=$pfx/128
+[IPv6Prefix]
+Prefix=$stateless_pfx/80
+PreferredLifetimeSec=60
+ValidLifetimeSec=60
+[IPv6RoutePrefix]
+Route=$pfx/80
+LifetimeSec=60
+[Route]
+Destination=$stateless_pfx/76
+Type=unreachable
+Metric=1024
+EOF
+  echo "Runtime setting gbmcbr IP: $pfx" >&2
+
+  local file
+  for file in /run/systemd/network/{00,}-bmc-gbmcbr.network.d/50-public.conf; do
+    mkdir -p "$(dirname "$file")"
+    printf '%s' "$contents" >"$file"
+  done
+
+  gbmc_br_reload
+}
+
+gbmc_br_set_ip() {
+  local ip="${1-}"
+
+  if [ -n "$ip" ]; then
+    mkdir -p /var/google || return
+    echo "$ip" >/var/google/gbmc-br-ip || return
+  else
+    rm -rf /var/google/gbmc-br-ip
+  fi
+
+  # Remove legacy network configuration
+  rm -rf /etc/systemd/network/{00,}-bmc-gbmcbr.network.d
+
+  gbmc_br_run_hooks GBMC_BR_LIB_SET_IP_HOOKS "$ip" || return
+
+  gbmc_br_reload_ip "$ip"
+}
+
+gbmc_br_lib_init=1
+return 0 2>/dev/null
+echo "gbmc-br-lib is a library, not executed directly" >&2
+exit 1
diff --git a/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-load-ip.service b/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-load-ip.service
new file mode 100644
index 0000000..51c68eb
--- /dev/null
+++ b/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-load-ip.service
@@ -0,0 +1,9 @@
+[Unit]
+Before=gbmc-ip-monitor.service
+Before=systemd-networkd.service
+
+[Service]
+ExecStart=/bin/bash -c 'source /usr/share/gbmc-br-lib.sh && gbmc_br_reload_ip'
+
+[Install]
+WantedBy=multi-user.target