From e33ec595eac55d4cf209ea4d3e1dc487803978e7 Mon Sep 17 00:00:00 2001 From: "William A. Kennington III" Date: Wed, 10 Mar 2021 17:43:48 -0800 Subject: meta-google: gbmc-ip-monitor: Add package Add a daemon that monitors all link / addr / route changes on a system, and runs a set of installed hooks to perform customized behavior when these changes occur. Change-Id: Id2a6b7dc2534ebae1beca7135528a6e1e4eada57 Signed-off-by: William A. Kennington III --- .../networking/files/gbmc-ip-monitor-test.sh | 181 +++++++++++++++++++++ .../networking/files/gbmc-ip-monitor.service | 9 + .../networking/files/gbmc-ip-monitor.sh | 122 ++++++++++++++ .../recipes-google/networking/gbmc-ip-monitor.bb | 35 ++++ 4 files changed, 347 insertions(+) create mode 100755 meta-google/recipes-google/networking/files/gbmc-ip-monitor-test.sh create mode 100644 meta-google/recipes-google/networking/files/gbmc-ip-monitor.service create mode 100755 meta-google/recipes-google/networking/files/gbmc-ip-monitor.sh create mode 100644 meta-google/recipes-google/networking/gbmc-ip-monitor.bb 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 < 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: 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" < 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: 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 < 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: 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" < 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: 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}/ +} -- cgit v1.2.3