1#!/bin/bash
2# Copyright 2022 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
19# Wait until a well known service is network available
20echo 'Waiting for network reachability' >&2
21while true; do
22  before=$SECONDS
23  addrs="$(ip addr show gbmcbr | grep '^ *inet6' | awk '{print $2}')"
24  for addr in $addrs; do
25    # Remove the prefix length
26    ip="${addr%/*}"
27    ip_to_bytes ip_bytes "$ip" || continue
28    # Ignore ULAs and non-gBMC addresses
29    (( (ip_bytes[0] & 0xfc) == 0xfc || ip_bytes[8] != 0xfd )) && continue
30    # Only allow for the short, well known addresses <pfx>:fd01:: and not
31    # <pfx>:fd00:83c1:292d:feef. Otherwise, powercycle may be unavailable.
32    (( (ip_bytes[9] & 0x0f) == 0 )) && continue
33    for i in {10..15}; do
34      (( ip_bytes[i] != 0 )) && continue 2
35    done
36    echo "Trying reachability from $ip" >&2
37    for i in {0..5}; do
38      ping -I "$ip" -c 1 -W 1 2001:4860:4860::8888 >/dev/null 2>&1 && break 3
39      sleep 1
40    done
41  done
42  # Ensure we only complete the addr lookup loop every 10s
43  tosleep=$((before + 10 - SECONDS))
44  if (( tosleep > 0 )); then
45    sleep "$tosleep"
46  fi
47done
48
49# We need to guarantee we wait at least 10 minutes from reachable in
50# case networking just came up
51wait_min=10
52echo "Network is reachable, waiting $wait_min min" >&2
53sleep $((60 * wait_min))
54
55get_dhcp_unit_json() {
56  busctl -j call \
57    org.freedesktop.systemd1 \
58    /org/freedesktop/systemd1/unit/system_2dgbmc_5cx2dbr_5cx2ddhcp_2eslice \
59    org.freedesktop.DBus.Properties \
60    GetAll s org.freedesktop.systemd1.Unit
61}
62
63# Follow the process and make sure it idles for at least 10 minutes before
64# shutting down. This allows for failures and retries to happen.
65while true; do
66  json="$(get_dhcp_unit_json)" || exit
67  last_ms="$(echo "$json" | jq -r '.data[0].StateChangeTimestampMonotonic.data')"
68  if pid="$(cat /run/gbmc-br-dhcp.pid 2>/dev/null)" && [ -n "$pid" ]; then
69    # If the DHCP configuration process is running, wait for it to finish
70    echo "DHCP still running ($pid), waiting" >&2
71    while [[ -e /proc/$pid ]]; do
72      sleep 1
73    done
74    # Wait for systemd to detect the process state change
75    while true; do
76      json="$(get_dhcp_unit_json)" || exit
77      ms="$(echo "$json" | jq -r '.data[0].StateChangeTimestampMonotonic.data')"
78      (( ms != last_ms )) && break
79      sleep 1
80    done
81  fi
82
83  echo 'Checking DHCP Active State' >&2
84  json="$(get_dhcp_unit_json)" || exit
85  activestr="$(echo "$json" | jq -r '.data[0].ActiveState.data')"
86
87  # The process is already stopped, we are done
88  [[ "$activestr" == 'inactive' ]] && exit
89
90  # If the process is running, give it at least 10 minutes from when it started
91  cur_s="$(cut -d' ' -f1 /proc/uptime)"
92  # Remove floating point if applied since bash can't perform float arithmetic
93  cur_s="${cur_s%.*}"
94  if [[ "$activestr" == 'active' ]]; then
95    active_ms="$(echo "$json" | jq -r '.data[0].ActiveEnterTimestampMonotonic.data')"
96  else
97    active_ms=$((cur_s*1000*1000))
98  fi
99  w=$((active_ms/1000/1000 + (wait_min*60) - cur_s))
100  [ "$w" -lt 0 ] && break
101  echo "Waiting ${w}s for DHCP process" >&2
102  sleep "$w"
103done
104
105echo "Stopping DHCP processing" >&2
106systemctl stop --no-block gbmc-br-dhcp@'*'
107