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
16source "$(dirname "${BASH_SOURCE[0]}")"/ncsid_lib.sh
17
18NCSI_IF="$1"
19
20# We would prefer empty string but it's easier for associative array handling
21# to use invalid
22old_rtr=invalid
23old_mac=
24
25function set_rtr() {
26    if ! ip -6 route show | grep -q '^default'; then
27        echo 'default route missing, reconfiguring...' >&2
28        old_rtr=invalid
29        old_mac=
30    fi
31    [ "$rtr" != "$old_rtr" -a "$mac" != "$old_mac" ] || return
32
33    echo "Setting default router: $rtr at $mac" >&2
34
35    # Delete and static gateways and neighbors
36    while read entry; do
37        eval "$(echo "$entry" | JSONToVars)" || return
38        echo "Deleting neighbor $object"
39        DeleteObject "$service" "$object" || true
40    done < <(GetNeighborObjects "$netdev" 2>/dev/null)
41
42    busctl set-property xyz.openbmc_project.Network "$(EthObjRoot "$NCSI_IF")" \
43        xyz.openbmc_project.Network.EthernetInterface DefaultGateway6 s "" || true
44
45    # In case we don't have a base network file, make one
46    net_file=/run/systemd/network/00-bmc-$NCSI_IF.network
47    printf '[Match]\nName=%s\n[Network]\nDHCP=false\nIPv6AcceptRA=false\nLinkLocalAddressing=yes' \
48        "$NCSI_IF" >$net_file
49
50    # Override any existing gateway info
51    mkdir -p $net_file.d
52    printf '[Network]\nGateway=%s\n[Neighbor]\nMACAddress=%s\nAddress=%s' \
53        "$rtr" "$mac" "$rtr" >$net_file.d/10-gateway.conf
54
55    networkctl reload && networkctl reconfigure "$NCSI_IF" || true
56
57    retries=-1
58    old_mac="$mac"
59    old_rtr="$rtr"
60}
61
62retries=1
63min_w=10
64declare -A rtrs
65rtrs=()
66while true; do
67    data=(${rtrs["${old_rtr}"]-})
68    curr_dl="${data[1]-$min_w}"
69    args=(-m "$NCSI_IF" -w $(( (curr_dl - SECONDS) * 1000 )))
70    if (( retries > 0 )); then
71        args+=(-r "$retries")
72    else
73        args+=(-d)
74    fi
75    while read line; do
76        # `script` terminates all lines with a CRLF, remove it
77        line="${line:0:-1}"
78        if [ -z "$line" ]; then
79            lifetime=-1
80            mac=
81        elif [[ "$line" =~ ^Router' 'lifetime' '*:' '*([0-9]*) ]]; then
82            lifetime="${BASH_REMATCH[1]}"
83        elif [[ "$line" =~ ^Source' 'link-layer' 'address' '*:' '*([a-fA-F0-9:]*)$ ]]; then
84            mac="${BASH_REMATCH[1]}"
85        elif [[ "$line" =~ ^from' '(.*)$ ]]; then
86            rtr="${BASH_REMATCH[1]}"
87            # Only valid default routers can be considered, 0 lifetime implies
88            # a non-default router
89            if (( lifetime > 0 )); then
90                dl=$((lifetime + SECONDS))
91                rtrs["$rtr"]="$mac $dl"
92                # We have some notoriously noisy lab environments with many routers being broadcast
93                # We always prefer "fe80::1" in prod and labs for routing, so prefer that gateway.
94                # We also want to take the first router we find to speed up acquisition on boot.
95                if [ "$rtr" = "fe80::1" -o -z "$old_rtr" ]; then
96                    set_rtr || true
97                fi
98            fi
99            lifetime=-1
100            mac=
101        fi
102    done < <(exec script -q -c "rdisc6 ${args[*]}" /dev/null 2>/dev/null)
103    # Purge any expired routers
104    for rtr in "${!rtrs[@]}"; do
105        data=(${rtrs["$rtr"]})
106        dl=${data[1]}
107        if (( dl <= SECONDS )); then
108            unset rtrs["$rtr"]
109        fi
110    done
111    # Consider changing the gateway if the old one doesn't send RAs for the entire period
112    # This ensures we don't flip flop between multiple defaults if they exist.
113    if [ -z "${rtrs["$old_rtr"]-}" ]; then
114        echo "Old router $old_rtr disappeared" >&2
115        for rtr in "${!rtrs[@]}"; do
116            data=(${rtrs["$rtr"]})
117            mac=${data[0]}
118            dl=${data[1]}
119            set_rtr && break
120        done
121    fi
122
123    # If rdisc6 exits early we still want to wait for the deadline before retrying
124    (( timeout = curr_dl - SECONDS ))
125    sleep $(( timeout < 0 ? 0 : timeout ))
126done
127