diff options
Diffstat (limited to 'meta-google/recipes-google')
4 files changed, 347 insertions, 0 deletions
diff --git a/meta-google/recipes-google/networking/files/gbmc-ip-monitor-test.sh b/meta-google/recipes-google/networking/files/gbmc-ip-monitor-test.sh new file mode 100755 index 000000000..8b5f3492f --- /dev/null +++ b/meta-google/recipes-google/networking/files/gbmc-ip-monitor-test.sh @@ -0,0 +1,181 @@ +#!/bin/bash +# Copyright 2021 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. + +cd "$(dirname "$0")" +source gbmc-ip-monitor.sh +if [ -e ../gbmc-ip-monitor.bb ]; then + source '../../test/test-sh/lib.sh' +else + source "$SYSROOT/usr/share/test/lib.sh" +fi + +test_init_empty() { + ip() { + return 0 + } + str="$(gbmc_ip_monitor_generate_init)" || fail + expect_streq "$str" '[INIT]' +} + +test_init_link_populated() { + ip() { + if [ "$1" = 'link' ]; then + cat <<EOF +1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 + link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 +2: eno2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000 + link/ether aa:aa:aa:aa:aa:aa brd ff:ff:ff:ff:ff:ff + altname enp0s31f6 +EOF + fi + return 0 + } + str="$(gbmc_ip_monitor_generate_init)" || fail + expect_streq "$str" <<EOF +[LINK]1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 + link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 +[LINK]2: eno2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000 + link/ether aa:aa:aa:aa:aa:aa brd ff:ff:ff:ff:ff:ff + altname enp0s31f6 +[INIT] +EOF +} + +test_init_addr_populated() { + ip() { + if [ "$1" = 'addr' ]; then + cat <<EOF +1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 + link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 + inet 127.0.0.1/8 scope host lo + valid_lft forever preferred_lft forever + inet6 ::1/128 scope host + valid_lft forever preferred_lft forever +2: eno2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 + link/ether aa:aa:aa:aa:aa:aa brd ff:ff:ff:ff:ff:ff + altname enp0s31f6 + inet 192.168.242.57/23 brd 192.168.243.255 scope global dynamic noprefixroute eno2 + valid_lft 83967sec preferred_lft 83967sec + inet6 fd01:ff2:5687:4:cf03:45f3:983a:96eb/64 scope global temporary dynamic + valid_lft 518788sec preferred_lft 183sec +EOF + fi + return 0 + } + str="$(gbmc_ip_monitor_generate_init)" || fail + expect_streq "$str" <<EOF +[ADDR]1: lo inet 127.0.0.1/8 scope host lo +[ADDR]1: lo inet6 ::1/128 scope host +[ADDR]2: eno2 inet 192.168.242.57/23 brd 192.168.243.255 scope global dynamic noprefixroute eno2 +[ADDR]2: eno2 inet6 fd01:ff2:5687:4:cf03:45f3:983a:96eb/64 scope global temporary dynamic +[INIT] +EOF +} + +test_init_route_populated() { + ip() { + if [ "$1" = "-4" -a "${2-}" = 'route' ]; then + cat <<EOF +default via 192.168.243.254 dev eno2 proto dhcp metric 100 +192.168.242.0/23 dev eno2 proto kernel scope link src 192.168.242.57 metric 100 +EOF + elif [ "$1" = "-6" -a "${2-}" = 'route' ]; then + cat <<EOF +::1 dev lo proto kernel metric 256 pref medium +fd01:ff2:5687:4::/64 dev eno2 proto ra metric 100 pref medium +fe80::/64 dev eno2 proto kernel metric 100 pref medium +EOF + fi + return 0 + } + str="$(gbmc_ip_monitor_generate_init)" || fail + expect_streq "$str" <<EOF +[ROUTE]default via 192.168.243.254 dev eno2 proto dhcp metric 100 +[ROUTE]192.168.242.0/23 dev eno2 proto kernel scope link src 192.168.242.57 metric 100 +[ROUTE]::1 dev lo proto kernel metric 256 pref medium +[ROUTE]fd01:ff2:5687:4::/64 dev eno2 proto ra metric 100 pref medium +[ROUTE]fe80::/64 dev eno2 proto kernel metric 100 pref medium +[INIT] +EOF +} + +testParseNonTag() { + expect_err 2 gbmc_ip_monitor_parse_line '' + expect_err 2 gbmc_ip_monitor_parse_line ' ' + expect_err 2 gbmc_ip_monitor_parse_line ' Data' + expect_err 2 gbmc_ip_monitor_parse_line ' [LINK]' + expect_err 2 gbmc_ip_monitor_parse_line ' [ROUTE]' +} + +testParseInit() { + expect_err 0 gbmc_ip_monitor_parse_line '[INIT]' + expect_streq "$change" 'init' +} + +testParseAddrAdd() { + expect_err 0 gbmc_ip_monitor_parse_line '[ADDR]2: eno2 inet6 fd01:ff2:5687:4:cf03:45f3:983a:96eb/128 scope global temporary dynamic' + expect_streq "$change" 'addr' + expect_streq "$action" 'add' + expect_streq "$intf" 'eno2' + expect_streq "$fam" 'inet6' + expect_streq "$ip" 'fd01:ff2:5687:4:cf03:45f3:983a:96eb' + expect_streq "$scope" 'global' + expect_streq "$flags" 'temporary dynamic' +} + +testParseAddrDel() { + expect_err 0 gbmc_ip_monitor_parse_line '[ADDR]Deleted 2: eno2 inet6 fe80::aaaa:aaff:feaa:aaaa/64 scope link' + expect_streq "$change" 'addr' + expect_streq "$action" 'del' + expect_streq "$intf" 'eno2' + expect_streq "$fam" 'inet6' + expect_streq "$ip" 'fe80::aaaa:aaff:feaa:aaaa' + expect_streq "$scope" 'link' + expect_streq "$flags" '' +} + +testParseRouteAdd() { + expect_err 0 gbmc_ip_monitor_parse_line "[ROUTE]fd01:ff2:5687:4::/64 dev eno2 proto ra metric 100 pref medium" + expect_streq "$change" 'route' + expect_streq "$action" 'add' + expect_streq "$route" 'fd01:ff2:5687:4::/64 dev eno2 proto ra metric 100 pref medium' +} + +testParseRouteDel() { + expect_err 0 gbmc_ip_monitor_parse_line "[ROUTE]Deleted fd01:ff2:5687:4::/64 dev eno2 proto ra metric 100 pref medium" + expect_streq "$change" 'route' + expect_streq "$action" 'del' + expect_streq "$route" 'fd01:ff2:5687:4::/64 dev eno2 proto ra metric 100 pref medium' +} + +testParseLinkAdd() { + expect_err 0 gbmc_ip_monitor_parse_line "[LINK]2: eno2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000" \ + < <(echo 'link/ether aa:aa:aa:aa:aa:aa brd ff:ff:ff:ff:ff:ff') + expect_streq "$change" 'link' + expect_streq "$action" 'add' + expect_streq "$intf" 'eno2' + expect_streq "$mac" 'aa:aa:aa:aa:aa:aa' +} + +testParseLinkDel() { + expect_err 0 gbmc_ip_monitor_parse_line "[LINK]Deleted 2: eno2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000" \ + < <(echo 'link/ether aa:aa:aa:aa:aa:aa brd ff:ff:ff:ff:ff:ff') + expect_streq "$change" 'link' + expect_streq "$action" 'del' + expect_streq "$intf" 'eno2' + expect_streq "$mac" 'aa:aa:aa:aa:aa:aa' +} + +main diff --git a/meta-google/recipes-google/networking/files/gbmc-ip-monitor.service b/meta-google/recipes-google/networking/files/gbmc-ip-monitor.service new file mode 100644 index 000000000..435eac91d --- /dev/null +++ b/meta-google/recipes-google/networking/files/gbmc-ip-monitor.service @@ -0,0 +1,9 @@ +[Unit] +Before=systemd-networkd.service + +[Service] +Type=notify +ExecStart=/usr/libexec/gbmc-ip-monitor.sh + +[Install] +WantedBy=multi-user.target diff --git a/meta-google/recipes-google/networking/files/gbmc-ip-monitor.sh b/meta-google/recipes-google/networking/files/gbmc-ip-monitor.sh new file mode 100755 index 000000000..baeff9a85 --- /dev/null +++ b/meta-google/recipes-google/networking/files/gbmc-ip-monitor.sh @@ -0,0 +1,122 @@ +#!/bin/bash +# Copyright 2021 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. + +# A list of functions which get executed for each netlink event received. +# These are configured by the files included below. +GBMC_IP_MONITOR_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-ip-monitor/*.sh; do + source "$conf" +done + +gbmc_ip_monitor_run_hooks() { + local hook + for hook in "${GBMC_IP_MONITOR_HOOKS[@]}"; do + "$hook" || continue + done +} + +gbmc_ip_monitor_generate_init() { + ip link | sed 's,^[^ ],[LINK]\0,' + local intf= + local line + while read line; do + [[ "$line" =~ ^([0-9]+:[[:space:]][^:]+) ]] && intf="${BASH_REMATCH[1]}" + [[ "$line" =~ ^[[:space:]]*inet ]] && echo "[ADDR]$intf $line" + done < <(ip addr) + ip -4 route | sed 's,^,[ROUTE],' + ip -6 route | sed 's,^,[ROUTE],' + echo '[INIT]' +} + +gbmc_ip_monitor_parse_line() { + local line="$1" + if [[ "$line" == '[INIT]'* ]]; then + change=init + echo "Initialized" >&2 + elif [[ "$line" == '[ADDR]'* ]]; then + change=addr + action=add + pfx_re='^\[ADDR\](Deleted )?[0-9]+:[[:space:]]*' + intf_re='([^ ]+)[[:space:]]+' + fam_re='([^ ]+)[[:space:]]+' + addr_re='([^/]+)/[0-9]+[[:space:]]+(brd[[:space:]]+[^ ]+[[:space:]]+)?' + scope_re='scope[[:space:]]+([^ ]+)[[:space:]]*(.*)' + combined_re="${pfx_re}${intf_re}${fam_re}${addr_re}${scope_re}" + if ! [[ "$line" =~ ${combined_re} ]]; then + echo "Failed to parse addr: $line" >&2 + return 1 + fi + if [ -n "${BASH_REMATCH[1]}" ]; then + action=del + fi + intf="${BASH_REMATCH[2]}" + fam="${BASH_REMATCH[3]}" + ip="${BASH_REMATCH[4]}" + scope="${BASH_REMATCH[6]}" + flags="${BASH_REMATCH[7]}" + elif [[ "$line" == '[ROUTE]'* ]]; then + line="${line#[ROUTE]}" + change=route + action=add + if ! [[ "$line" =~ ^\[ROUTE\](Deleted )?(.*)$ ]]; then + echo "Failed to parse link: $line" >&2 + return 1 + fi + if [ -n "${BASH_REMATCH[1]}" ]; then + action=del + fi + route="${BASH_REMATCH[2]}" + elif [[ "$line" == '[LINK]'* ]]; then + change=link + action=add + pfx_re='^\[LINK\](Deleted )?[0-9]+:[[:space:]]*' + intf_re='([^:]+):[[:space:]]+' + if ! [[ "$line" =~ ${pfx_re}${intf_re} ]]; then + echo "Failed to parse link: $line" >&2 + return 1 + fi + if [ -n "${BASH_REMATCH[1]}" ]; then + action=del + fi + intf="${BASH_REMATCH[2]}" + read line || break + data=($line) + mac="${data[1]}" + else + return 2 + fi +} + +cleanup() { + local st="$?" + trap - HUP INT QUIT ABRT TERM EXIT + jobs -l -p | xargs -r kill || true + exit $st +} +trap cleanup HUP INT QUIT ABRT TERM EXIT + +return 0 2>/dev/null + +while read line; do + gbmc_ip_monitor_parse_line || continue + gbmc_ip_monitor_run_hooks || continue + if [ "$change" = 'init' ]; then + systemd-notify --ready + fi +done < <(gbmc_ip_monitor_generate_init; exec ip monitor link addr route label) diff --git a/meta-google/recipes-google/networking/gbmc-ip-monitor.bb b/meta-google/recipes-google/networking/gbmc-ip-monitor.bb new file mode 100644 index 000000000..32804302b --- /dev/null +++ b/meta-google/recipes-google/networking/gbmc-ip-monitor.bb @@ -0,0 +1,35 @@ +SUMMARY = "Allows hooking netlink events to perform network actions" +PR = "r1" +LICENSE = "Apache-2.0" +LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/Apache-2.0;md5=89aea4e17d99a7cacdbeed46a0096b10" + +inherit systemd + +SRC_URI += " \ + file://gbmc-ip-monitor.service \ + file://gbmc-ip-monitor.sh \ + file://gbmc-ip-monitor-test.sh \ + " + +S = "${WORKDIR}" + +DEPENDS += "test-sh" + +RDEPENDS_${PN} += " \ + bash \ + iproute2 \ + " + +SYSTEMD_SERVICE_${PN} += "gbmc-ip-monitor.service" + +do_compile() { + SYSROOT="$PKG_CONFIG_SYSROOT_DIR" bash gbmc-ip-monitor-test.sh || exit +} + +do_install_append() { + install -d -m0755 ${D}${libexecdir} + install -m0755 gbmc-ip-monitor.sh ${D}${libexecdir}/ + + install -d -m0755 ${D}${systemd_system_unitdir} + install -m0644 gbmc-ip-monitor.service ${D}${systemd_system_unitdir}/ +} |