Initial commit

This commit is contained in:
Your Name
2026-04-23 17:07:55 +08:00
commit b7e39e063b
16725 changed files with 1625565 additions and 0 deletions
@@ -0,0 +1,5 @@
table inet filter {
chain gbmc_br_pub_input {
tcp dport 23 accept
}
}
@@ -0,0 +1,22 @@
SUMMARY = "Google DHCP completion daemon"
DESCRIPTION = "Google DHCP completion daemon"
GOOGLE_MISC_PROJ = "dhcp-done"
require ../google-misc/google-misc.inc
inherit systemd
SYSTEMD_SERVICE:${PN} += "dhcp-done@.service"
DEPENDS += " \
sdeventplus \
stdplus \
"
SRC_URI += "file://50-dhcp-done.rules"
FILES:${PN} += "${sysconfdir}/nftables"
do_install:append() {
nftables_dir=${D}${sysconfdir}/nftables
install -d -m0755 "$nftables_dir"
install -m0644 ${WORKDIR}/50-dhcp-done.rules $nftables_dir/
}
@@ -0,0 +1,185 @@
#!/bin/bash
# shellcheck disable=SC2317
# 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")" || exit
if [ -e ../gbmc-ip-monitor.bb ]; then
# shellcheck source=meta-google/recipes-google/test/test-sh/lib.sh
source '../../test/test-sh/lib.sh'
else
# shellcheck source=meta-google/recipes-google/test/test-sh/lib.sh
source "$SYSROOT/usr/share/test/lib.sh"
fi
# shellcheck source=meta-google/recipes-google/networking/files/gbmc-ip-monitor.sh
source gbmc-ip-monitor.sh
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" && "${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" && "${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 metric 1024 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
@@ -0,0 +1,10 @@
[Unit]
Before=systemd-networkd.service
[Service]
Type=notify
ExecStart=/usr/libexec/gbmc-ip-monitor.sh
TimeoutStartSec=5min
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,145 @@
#!/bin/bash
# shellcheck disable=SC2034
# shellcheck disable=SC2317
# 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
# shellcheck source=/dev/null
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 -r 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_DEFER_OUTSTANDING=
gbmc_ip_monitor_defer_() {
sleep 1
printf '[DEFER]\n' >&"$GBMC_IP_MONITOR_DEFER"
}
gbmc_ip_monitor_defer() {
[ -z "$GBMC_IP_MONITOR_DEFER_OUTSTANDING" ] || return 0
gbmc_ip_monitor_defer_ &
GBMC_IP_MONITOR_DEFER_OUTSTANDING=1
}
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:]]+'
metric_re='(metric[[:space:]]+[^ ]+[[:space:]]+)?'
brd_re='(brd[[:space:]]+[^ ]+[[:space:]]+)?'
scope_re='scope[[:space:]]+([^ ]+)[[:space:]]*(.*)'
combined_re="${pfx_re}${intf_re}${fam_re}${addr_re}${metric_re}${brd_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[7]}"
flags="${BASH_REMATCH[8]}"
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 -ra data || return
mac="${data[1]}"
elif [[ "$line" == '[DEFER]'* ]]; then
GBMC_IP_MONITOR_DEFER_OUTSTANDING=
change=defer
else
return 2
fi
}
return 0 2>/dev/null
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
FIFODIR="$(mktemp -d)"
mkfifo "$FIFODIR"/fifo
exec {GBMC_IP_MONITOR_DEFER}<>"$FIFODIR"/fifo
rm -rf "$FIFODIR"
while read -r line; do
gbmc_ip_monitor_parse_line "$line" || continue
gbmc_ip_monitor_run_hooks || continue
if [ "$change" = 'init' ]; then
systemd-notify --ready
fi
done < <(gbmc_ip_monitor_generate_init; ip monitor link addr route label & cat <&"$GBMC_IP_MONITOR_DEFER")
@@ -0,0 +1,10 @@
[Unit]
Before=systemd-networkd.service
[Service]
Restart=on-failure
Type=oneshot
ExecStart=/usr/libexec/gbmc-mac-config.sh
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,91 @@
#!/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.
# shellcheck source=meta-google/recipes-google/ipmi/ipmi-fru-sh/lib.sh
source /usr/share/ipmi-fru/lib.sh || exit
ipmi_fru_alloc '@EEPROM@' eeprom || exit
header=()
read_header "$eeprom" header || exit
internal_offset=${header[$IPMI_FRU_COMMON_HEADER_INTERNAL_OFFSET_IDX]}
if (( internal_offset == 0 )); then
echo "Internal offset invalid for eeprom" >&2
exit 1
fi
# Our MAC Address configuration lives in the internal area with a format
# Offset Data
# 0 Version (Always 1)
# 1 Type (Always 1 for MAC Address)
# 2 Area Length in bytes (Always 32 bytes or 4 IPMI FRU sectors)
# 3-8 MAC Address Base Octets
# 9 Num Allocate MACs from Base
# 10-30 Padding (Always 0xFF)
# 31 IPMI FRU Checksum
internal=()
read_area "$eeprom" "$internal_offset" internal 4 || exit
if (( internal[1] != 1 || internal[2] != 32 )); then
echo "Not a MAC internal region" >&2
exit 1
fi
mac=("${internal[@]:3:6}")
num="${internal[9]}"
macstr=$(printf '%02x:%02x:%02x:%02x:%02x:%02x' "${mac[@]}")
echo "Base MAC $macstr num $num" >&2
rc=0
# Pre-Determine if we will miss an allocation due to the number of
# addresses the FRU actually supports.
# shellcheck disable=SC2190
declare -A num_to_intfs=(@NUM_TO_INTFS@)
for key in "${!num_to_intfs[@]}"; do
if (( key >= num )); then
echo "${num_to_intfs[$key]} at $key is out of range" >&2
rc=1
fi
done
# Write out each MAC override to the runtime networkd configuration
for (( i=0; i<num; i++ )); do
if (( mac[5] > 0xff )); then
echo "MAC assignment too large: ${mac[*]}" >&2
rc=2
break
fi
for intf in ${num_to_intfs[$i]}; do
macstr=$(printf '%02x:%02x:%02x:%02x:%02x:%02x' "${mac[@]}")
echo "Setting $intf to $macstr" >&2
for override in /run/systemd/network/{00,}-bmc-$intf.network.d; do
mkdir -p "$override"
printf '[Link]\nMACAddress=%s\n' "$macstr" >"$override"/50-mac.conf
done
for override in /run/systemd/network/{00,}-bmc-$intf.netdev.d; do
mkdir -p "$override"
printf '[NetDev]\nMACAddress=%s\n' "$macstr" >"$override"/50-mac.conf
done
# In case we don't have any interface configs, set the MAC directly
# This is safe to apply, as systemd-networkd will always override this
# value based on written configs.
if ip link show "$intf" >/dev/null 2>&1 && \
! ip link set dev "$intf" address "$macstr"; then
echo "Setting MAC($macstr) on $intf failed" >&2
fi
done
(( ++mac[5] ))
done
exit $rc
@@ -0,0 +1,21 @@
[Unit]
Description=IPERF3 Server
[Service]
ExecStart=/usr/bin/iperf3 -s
#Hardening
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectControlGroups=true
MountFlags=private
NoNewPrivileges=true
PrivateDevices=true
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
MemoryDenyWriteExecute=true
DynamicUser=true
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,155 @@
SUMMARY = "Configures the gbmc bridge and filter rules"
PR = "r1"
LICENSE = "Apache-2.0"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/Apache-2.0;md5=89aea4e17d99a7cacdbeed46a0096b10"
inherit systemd
FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"
SRC_URI += " \
file://-bmc-gbmcbr.netdev \
file://-bmc-gbmcbr.network.in \
file://-bmc-gbmcbrdummy.netdev \
file://-bmc-gbmcbrdummy.network \
file://+-bmc-gbmcbrusb.network \
file://ipmi.service.in \
file://50-gbmc-br.rules \
file://gbmc-br-ula.sh \
file://gbmc-br-from-ra.sh \
file://gbmc-br-ensure-ra.sh \
file://gbmc-br-ensure-ra.service \
file://gbmc-br-gw-src.sh \
file://gbmc-br-nft.sh \
file://gbmc-br-dhcp.sh \
file://50-gbmc-psu-hardreset.sh \
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 \
file://gbmc-start-dhcp.sh \
file://50-gbmc-br-cn-redirect.rules \
"
FILES:${PN}:append = " \
${datadir}/gbmc-ip-monitor \
${datadir}/gbmc-br-dhcp \
${datadir}/gbmc-br-lib.sh \
${systemd_unitdir}/network \
${sysconfdir}/nftables \
${sysconfdir}/avahi/services \
"
RDEPENDS:${PN}:append = " \
bash \
dhcp-done \
gbmc-ip-monitor \
mstpd-mstpd \
network-sh \
ndisc6-rdisc6 \
nftables-systemd \
"
SYSTEMD_SERVICE:${PN} += " \
gbmc-br-ensure-ra.service \
gbmc-br-dhcp.service \
gbmc-br-dhcp-term.service \
gbmc-br-load-ip.service \
"
GBMC_BR_MAC_ADDR ?= ""
# Generated via https://cd34.com/rfc4193/ based on a MAC from a machine I own
# and we allocated it downstream. Intended to only be used within a complete
# system of multiple network endpoints.
GBMC_ULA_PREFIX = "fdb5:0481:10ce:0"
def mac_to_eui64(mac):
if not mac:
return ''
b = [int(c, 16) for c in mac.split(':')]
b[0] ^= 2
b.insert(3, 0xfe)
b.insert(3, 0xff)
idx = range(0, len(b)-1, 2)
return ':'.join([format((b[i] << 8) + b[i+1], '04x') for i in idx])
GBMC_BRIDGE_INTFS ?= ""
ethernet_bridge_install() {
# install udev rules if any
if [ -z "${GBMC_BRIDGE_INTFS}"]; then
return
fi
cat /dev/null > ${WORKDIR}/-ether-bridge.network
echo "[Match]" >> ${WORKDIR}/-ether-bridge.network
echo "Name=${GBMC_BRIDGE_INTFS}" >> ${WORKDIR}/-ether-bridge.network
echo "[Network]" >> ${WORKDIR}/-ether-bridge.network
echo "Bridge=gbmcbr" >> ${WORKDIR}/-ether-bridge.network
install -d ${D}/${sysconfdir}/systemd/network
install -m 0644 ${WORKDIR}/-ether-bridge.network ${D}/${sysconfdir}/systemd/network/
}
do_install() {
netdir=${D}${systemd_unitdir}/network
install -d -m0755 $netdir
if [ ! -z "${GBMC_BR_MAC_ADDR}" ]; then
sfx='${@mac_to_eui64(GBMC_BR_MAC_ADDR)}'
addr="Address=${GBMC_ULA_PREFIX}:$sfx/64\nAddress=fe80::$sfx/64"
sed -i "s,@ADDR@,$addr," ${WORKDIR}/-bmc-gbmcbr.network.in
else
sed -i '/@ADDR@/d' ${WORKDIR}/-bmc-gbmcbr.network.in
fi
ethernet_bridge_install
install -m0644 ${WORKDIR}/-bmc-gbmcbr.netdev $netdir/
install -m0644 ${WORKDIR}/-bmc-gbmcbr.network.in $netdir/-bmc-gbmcbr.network
install -m0644 ${WORKDIR}/-bmc-gbmcbrdummy.netdev $netdir/
install -m0644 ${WORKDIR}/-bmc-gbmcbrdummy.network $netdir/
install -m0644 ${WORKDIR}/+-bmc-gbmcbrusb.network $netdir/
nftables_dir=${D}${sysconfdir}/nftables
install -d -m0755 "$nftables_dir"
install -m0644 ${WORKDIR}/50-gbmc-br.rules $nftables_dir/
install -m0644 ${WORKDIR}/50-gbmc-br-cn-redirect.rules $nftables_dir/
avahi_dir=${D}${sysconfdir}/avahi/services
install -d -m 0755 "$avahi_dir"
sed -i 's,@MACHINE@,${MACHINE},g' ${WORKDIR}/ipmi.service.in
sed -i 's,@EXTRA_ATTRS@,,g' ${WORKDIR}/ipmi.service.in
sed 's,@NAME@,bmc,g' ${WORKDIR}/ipmi.service.in >${avahi_dir}/bmc.ipmi.service
sed 's,@NAME@,${MACHINE}-bmc,g' ${WORKDIR}/ipmi.service.in >${avahi_dir}/${MACHINE}-bmc.ipmi.service
mondir=${D}${datadir}/gbmc-ip-monitor
install -d -m0755 "$mondir"
install -m0644 ${WORKDIR}/gbmc-br-ula.sh "$mondir"/
install -m0644 ${WORKDIR}/gbmc-br-from-ra.sh "$mondir"/
install -m0644 ${WORKDIR}/gbmc-br-gw-src.sh "$mondir"/
install -m0644 ${WORKDIR}/gbmc-br-nft.sh "$mondir"/
install -d -m0755 ${D}${libexecdir}
install -m0755 ${WORKDIR}/gbmc-br-ensure-ra.sh ${D}${libexecdir}/
install -m0755 ${WORKDIR}/gbmc-br-dhcp.sh ${D}${libexecdir}/
install -m0755 ${WORKDIR}/gbmc-br-dhcp-term.sh ${D}${libexecdir}/
install -d -m0755 ${D}${systemd_system_unitdir}
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}/
install -d ${D}/${bindir}
install -m0755 ${WORKDIR}/gbmc-start-dhcp.sh ${D}${bindir}/
}
do_rm_work:prepend() {
# HACK: Work around broken do_rm_work not properly calling rm with `--`
# It doesn't like filenames that start with `-`
rm -rf -- ${WORKDIR}/-*
}
@@ -0,0 +1,8 @@
[Match]
Property=ID_BUS=usb ID_VENDOR_ID=18d1 ID_MODEL_ID=022b
[Network]
Bridge=gbmcbr
[Bridge]
# USB speeds tend to be better than 100mbit (100 cost) but worse
# than 1gbit (10 cost). Generally around 200mbit.
Cost=85
@@ -0,0 +1,5 @@
[NetDev]
Name=gbmcbr
Kind=bridge
[Bridge]
STP=true
@@ -0,0 +1,15 @@
[Match]
Name=gbmcbr
[Network]
@ADDR@
DHCP=false
IPv6AcceptRA=true
LLMNR=true
MulticastDNS=true
LinkLocalAddressing=ipv6
IPv6PrefixDelegation=yes
[IPv6AcceptRA]
DHCPv6Client=false
RouteMetric=1056
[IPv6PrefixDelegation]
RouterLifetimeSec=0
@@ -0,0 +1,3 @@
[NetDev]
Name=gbmcbrdummy
Kind=dummy
@@ -0,0 +1,4 @@
[Match]
Name=gbmcbrdummy
[Network]
Bridge=gbmcbr
@@ -0,0 +1,24 @@
table bridge filter {
chain gbmcbr_mark {
type filter hook prerouting priority -300;
iifname == "cn0" mark set 1 return
iifname == "cn1" mark set 2 return
}
}
table inet raw {
chain gbmcbr_nat_input {
type filter hook prerouting priority -300;
# client should only use 10166 for this purpose and
# it should NOT use service port directly
# otherwise drop later if the packets goes into input
tcp dport 10167-10168 mark set 0xff
mark 1 tcp dport 10166 tcp dport set 10167 notrack
mark 2 tcp dport 10166 tcp dport set 10168 notrack
}
chain gbmcbr_nat_output {
type filter hook output priority -300;
tcp sport 10167 tcp sport set 10166 notrack
tcp sport 10168 tcp sport set 10166 notrack
}
}
@@ -0,0 +1,38 @@
table bridge filter {
chain gbmc_br_prerouting {
type filter hook prerouting priority 0;
iifname != gbmcbr accept
# Sometimes our links are over NCSI and we don't want to broadcast
# those packets over the entire bridge. They are only relevant P2P.
ether type 0x88F8 drop
}
}
table inet filter {
chain gbmc_br_input {
type filter hook input priority 0; policy drop;
iifname != gbmcbr accept
mark 0xff drop
ct state established accept
jump gbmc_br_int_input
jump gbmc_br_pub_input
reject
}
set gbmc_br_int_addrs {
type ipv6_addr;
flags interval
elements = {
ff00::/8,
fe80::/64,
fdb5:0481:10ce::/64,
}
}
chain gbmc_br_int_input {
ip6 daddr @gbmc_br_int_addrs accept
ip6 saddr @gbmc_br_int_addrs accept
}
chain gbmc_br_pub_input {
ip6 nexthdr icmpv6 accept
}
}
@@ -0,0 +1,37 @@
#!/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_psu_hardreset-}" ] && return
gbmc_psu_hardreset_needed=
gbmc_psu_hardreset_hook() {
# We don't always need a powercycle, allow skipping
if [ -z "${gbmc_psu_hardreset_needed-}" ]; then
echo "Skipping tray powercycle" >&2
return 0
fi
echo "Powercycling" >&2
systemctl start gbmc-psu-hardreset.target || return
# Ensure that we don't continue the DHCP process while waiting for the
# powercycle.
exit 0
}
GBMC_BR_DHCP_HOOKS+=(gbmc_psu_hardreset_hook)
gbmc_psu_hardreset=1
@@ -0,0 +1,13 @@
[Unit]
Description=gBMC DHCP Client terminator
After=network.target
StartLimitIntervalSec=10
StartLimitBurst=3
[Service]
Restart=on-failure
RestartSec=5
ExecStart=/usr/libexec/gbmc-br-dhcp-term.sh
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,106 @@
#!/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.
# shellcheck source=meta-google/recipes-google/networking/network-sh/lib.sh
source /usr/share/network/lib.sh || exit
# Wait until a well known service is network available
echo 'Waiting for network reachability' >&2
while true; do
before=$SECONDS
addrs="$(ip addr show gbmcbr | grep '^ *inet6' | awk '{print $2}')"
for addr in $addrs; do
# Remove the prefix length
ip="${addr%/*}"
ip_to_bytes ip_bytes "$ip" || continue
# Ignore ULAs and non-gBMC addresses
(( ip_bytes[0] & 0xfc == 0xfc || ip_bytes[8] != 0xfd )) && continue
# Only allow for the short, well known addresses <pfx>:fd01:: and not
# <pfx>:fd00:83c1:292d:feef. Otherwise, powercycle may be unavailable.
(( ip_bytes[9] == 0 )) && continue
for i in {10..15}; do
(( ip_bytes[i] != 0 )) && continue 2
done
echo "Trying reachability from $ip" >&2
for i in {0..5}; do
ping -I "$ip" -c 1 -W 1 2001:4860:4860::8888 >/dev/null 2>&1 && break 3
sleep 1
done
done
# Ensure we only complete the addr lookup loop every 10s
tosleep=$((before + 10 - SECONDS))
if (( tosleep > 0 )); then
sleep "$tosleep"
fi
done
# We need to guarantee we wait at least 5 minutes from reachable in
# case networking just came up
wait_min=5
echo "Network is reachable, waiting $wait_min min" >&2
sleep $((60 * wait_min))
get_dhcp_unit_json() {
busctl -j call \
org.freedesktop.systemd1 \
/org/freedesktop/systemd1/unit/gbmc_2dbr_2ddhcp_2eservice \
org.freedesktop.DBus.Properties \
GetAll s org.freedesktop.systemd1.Unit
}
# Follow the process and make sure it idles for at least 5 minutes before
# shutting down. This allows for failures and retries to happen.
while true; do
json="$(get_dhcp_unit_json)" || exit
last_ms="$(echo "$json" | jq -r '.data[0].StateChangeTimestampMonotonic.data')"
if pid="$(cat /run/gbmc-br-dhcp.pid 2>/dev/null)"; then
# If the DHCP configuration process is running, wait for it to finish
echo "DHCP still running ($pid), waiting" >&2
while [[ -e /proc/$pid ]]; do
sleep 1
done
# Wait for systemd to detect the process state change
while true; do
json="$(get_dhcp_unit_json)" || exit
ms="$(echo "$json" | jq -r '.data[0].StateChangeTimestampMonotonic.data')"
(( ms != last_ms )) && break
sleep 1
done
fi
echo 'Checking DHCP Active State' >&2
json="$(get_dhcp_unit_json)" || exit
activestr="$(echo "$json" | jq -r '.data[0].ActiveState.data')"
# The process is already stopped, we are done
[[ "$activestr" == 'inactive' ]] && exit
# If the process is running, give it at least 5 minutes from when it started
cur_s="$(cut -d' ' -f1 /proc/uptime)"
# Remove floating point if applied since bash can't perform float arithmetic
cur_s="${cur_s%.*}"
if [[ "$activestr" == 'active' ]]; then
active_ms="$(echo "$json" | jq -r '.data[0].ActiveEnterTimestampMonotonic.data')"
else
active_ms=$((cur_s*1000*1000))
fi
w=$((active_ms/1000/1000 + (wait_min*60) - cur_s))
[ "$w" -lt 0 ] && break
echo "Waiting ${w}s for DHCP process" >&2
sleep "$w"
done
echo "Stopping DHCP processing" >&2
systemctl stop --no-block gbmc-br-dhcp
@@ -0,0 +1,14 @@
[Unit]
Description=gBMC DHCP Client
After=network.target
StartLimitIntervalSec=10
StartLimitBurst=3
[Service]
Restart=on-failure
RestartSec=5
ExecCondition=/bin/bash -c "! /bin/systemctl is-active -q dhcp-done@*"
ExecStart=/usr/bin/udhcpc6 -f -q -O fqdn -O bootfile_url -O bootfile_param -i gbmcbr -s /usr/libexec/gbmc-br-dhcp.sh
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,80 @@
#!/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 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=()
# A dict of outstanding items that should prevent DHCP completion
declare -A GBMC_BR_DHCP_OUTSTANDING=()
# 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
echo "$$" >/run/gbmc-br-dhcp.pid
if [ "$1" = bound ]; then
# Variable is from the environment via udhcpc6
# shellcheck disable=SC2154
echo "DHCPv6(gbmcbr): $ipv6/128" >&2
pfx_bytes=()
ip_to_bytes pfx_bytes "$ipv6"
# 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 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 1
fi
done
pfx="$(ip_bytes_to_str pfx_bytes)"
gbmc_br_set_ip "$pfx" || exit
if [ -n "${fqdn-}" ]; then
echo "Using hostname $fqdn" >&2
hostnamectl set-hostname "$fqdn" || true
fi
gbmc_br_run_hooks GBMC_BR_DHCP_HOOKS || exit
# If any of our hooks had expectations we should fail here
if [ "${#GBMC_BR_DHCP_OUTSTANDING[@]}" -gt 0 ]; then
echo "Not done with DHCP process: ${!GBMC_BR_DHCP_OUTSTANDING[*]}" >&2
exit 1
fi
# Ensure that the installer knows we have completed processing DHCP by
# running a service that reports completion
echo 'Start DHCP Done' >&2
systemctl start dhcp-done@DONE --no-block
fi
@@ -0,0 +1,5 @@
[Service]
ExecStart=/usr/libexec/gbmc-br-ensure-ra.sh
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,27 @@
#!/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.
# Every 30 seconds, send out an RA so that the kernel will receive a response.
# This ensures that all BMCs (even ones that think they are routers) get updated
# information from the other systems on the network.
w=30
while true; do
start=$SECONDS
rdisc6 -m gbmcbr -r 1 -w $(( w * 1000 )) >/dev/null 2>/dev/null
# If rdisc6 exits early we still want to wait the full `w` time before
# starting again.
(( timeout = start + w - SECONDS ))
sleep $(( timeout < 0 ? 0 : timeout ))
done
@@ -0,0 +1,102 @@
#!/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.
[[ -n ${gbmc_br_from_ra_lib-} ]] && return
# shellcheck source=meta-google/recipes-google/networking/network-sh/lib.sh
source /usr/share/network/lib.sh || exit
gbmc_br_from_ra_init=
gbmc_br_from_ra_mac=
declare -A gbmc_br_from_ra_pfxs=()
declare -A gbmc_br_from_ra_prev_addrs=()
gbmc_br_from_ra_update() {
[[ -n $gbmc_br_from_ra_init && -n $gbmc_br_from_ra_mac ]] || return
local pfx
for pfx in "${!gbmc_br_from_ra_pfxs[@]}"; do
local cidr
if ! cidr="$(ip_pfx_to_cidr "$pfx")"; then
unset 'gbmc_br_from_ra_pfxs[$pfx]'
continue
fi
if (( cidr == 80 )); then
local sfx
if ! sfx="$(mac_to_eui48 "$gbmc_br_from_ra_mac")"; then
unset 'gbmc_br_from_ra_pfxs[$pfx]'
continue
fi
local addr
if ! addr="$(ip_pfx_concat "$pfx" "$sfx")"; then
unset 'gbmc_br_from_ra_pfxs[$pfx]'
continue
fi
else
unset 'gbmc_br_from_ra_pfxs[$pfx]'
continue
fi
local valid="${gbmc_br_from_ra_pfxs["$pfx"]}"
if (( valid > 0 )); then
if [[ -z ${gbmc_br_from_ra_prev_addrs["$addr"]-} ]]; then
echo "gBMC Bridge RA Addr Add: $addr" >&2
gbmc_br_from_ra_prev_addrs["$addr"]=1
fi
ip addr replace "$addr" dev gbmcbr noprefixroute
else
if [[ -n ${gbmc_br_from_ra_prev_addrs["$addr"]-} ]]; then
echo "gBMC Bridge RA Addr Del: $addr" >&2
unset 'gbmc_br_from_ra_prev_addrs[$addr]'
fi
ip addr del "$addr" dev gbmcbr
unset 'gbmc_br_from_ra_pfxs[$pfx]'
fi
done
}
gbmc_br_from_ra_hook() {
# shellcheck disable=SC2154
if [[ $change == init ]]; then
gbmc_br_from_ra_init=1
gbmc_ip_monitor_defer
elif [[ $change == defer ]]; then
gbmc_br_from_ra_update
elif [[ $change == route && $route != *' via '* ]] &&
[[ $route =~ ^(.* dev gbmcbr proto ra .*)( +expires +([^ ]+)sec).*$ ]]; then
pfx="${route%% *}"
# shellcheck disable=SC2154
if [[ $action == add ]]; then
gbmc_br_from_ra_pfxs["$pfx"]="${BASH_REMATCH[3]}"
gbmc_ip_monitor_defer
elif [[ $action == del ]]; then
gbmc_br_from_ra_pfxs["$pfx"]=0
gbmc_ip_monitor_defer
fi
elif [[ $change == link && $intf == gbmcbr ]]; then
rdisc6 -m gbmcbr -r 1 -w 100 >/dev/null 2>&1
if [[ $action == add && $mac != "$gbmc_br_from_ra_mac" ]]; then
gbmc_br_from_ra_mac="$mac"
gbmc_ip_monitor_defer
fi
if [[ $action == del && $mac == "$gbmc_br_from_ra_mac" ]]; then
gbmc_br_from_ra_mac=
gbmc_ip_monitor_defer
fi
fi
}
GBMC_IP_MONITOR_HOOKS+=(gbmc_br_from_ra_hook)
gbmc_br_from_ra_lib=1
@@ -0,0 +1,117 @@
#!/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.
[[ -n ${gbmc_br_gw_src_lib-} ]] && return
# shellcheck source=meta-google/recipes-google/networking/network-sh/lib.sh
source /usr/share/network/lib.sh || exit
gbmc_br_gw_src_ip_stateful=
gbmc_br_gw_src_ip_stateless=
declare -A gbmc_br_gw_src_routes=()
gbmc_br_gw_defgw=
gbmc_br_set_router() {
local defgw=
local route
for route in "${!gbmc_br_gw_src_routes[@]}"; do
if [[ $route != *' dev gbmcbr '* ]]; then
defgw=1
break
fi
done
[[ $defgw == "$gbmc_br_gw_defgw" ]] && return
gbmc_br_gw_defgw="$defgw"
local files=(/run/systemd/network/{00,}-bmc-gbmcbr.network.d/50-defgw.conf)
if [[ -n $defgw ]]; then
local file
for file in "${files[@]}"; do
mkdir -p "$(dirname "$file")"
printf '[IPv6PrefixDelegation]\nRouterLifetimeSec=30\n' >"$file"
done
else
rm -f "${files[@]}"
fi
if [[ $(systemctl is-active systemd-networkd) != inactive ]]; then
networkctl reload && networkctl reconfigure gbmcbr
fi
}
gbmc_br_gw_src_update() {
local gbmc_br_gw_src_ip="${gbmc_br_gw_src_ip_stateful:-$gbmc_br_gw_src_ip_stateless}"
[[ -n $gbmc_br_gw_src_ip ]] || return
local route
for route in "${!gbmc_br_gw_src_routes[@]}"; do
[[ $route != *" src $gbmc_br_gw_src_ip "* ]] || continue
echo "gBMC Bridge Updating GW source [$gbmc_br_gw_src_ip]: $route" >&2
# shellcheck disable=SC2086
ip route change $route src "$gbmc_br_gw_src_ip" && \
unset 'gbmc_br_gw_src_routes[$route]'
done
}
gbmc_br_gw_src_hook() {
# We only want to match default gateway routes that are dynamic
# (have an expiration time). These will be updated with our preferred
# source.
# shellcheck disable=SC2154
if [[ $change == route && $route == 'default '*':'* ]]; then
if [[ $route =~ ^(.*)( +expires +[^ ]+)(.*)$ ]]; then
route="${BASH_REMATCH[1]}${BASH_REMATCH[3]}"
fi
if [[ $action == add && -z ${gbmc_br_gw_src_routes["$route"]} ]]; then
gbmc_br_gw_src_routes["$route"]=1
gbmc_br_gw_src_update
gbmc_br_set_router
elif [[ $action == del && -n "${gbmc_br_gw_src_routes["$route"]}" ]]; then
unset 'gbmc_br_gw_src_routes[$route]'
gbmc_br_gw_src_update
gbmc_br_set_router
fi
# Match only global IP addresses on the bridge that match the BMC stateless
# prefix (<mpfx>:fd00:). So 2002:af4:3480:2248:fd00:6345:3069:9186 would be
# matched as the preferred source IP for outoging traffic.
elif [[ $change == addr && $intf == gbmcbr && $scope == global ]] &&
[[ $fam == inet6 && $flags != *tentative* ]]; then
local ip_bytes=()
if ! ip_to_bytes ip_bytes "$ip"; then
echo "gBMC Bridge Ensure RA Invalid IP: $ip" >&2
return 1
fi
# Ignore ULAs and non-gBMC addresses
if (( ip_bytes[0] & 0xfe == 0xfc || ip_bytes[8] != 0xfd )); then
return 0
fi
if (( ip_bytes[9] & 0x0f != 0 )); then
local -n gbmc_br_gw_src_ip=gbmc_br_gw_src_ip_stateful
else
local -n gbmc_br_gw_src_ip=gbmc_br_gw_src_ip_stateless
fi
if [[ $action == add && $ip != "$gbmc_br_gw_src_ip" ]]; then
gbmc_br_gw_src_ip="$ip"
gbmc_br_gw_src_update
fi
if [[ $action == del && $ip == "$gbmc_br_gw_src_ip" ]]; then
gbmc_br_gw_src_ip=
fi
fi
}
GBMC_IP_MONITOR_HOOKS+=(gbmc_br_gw_src_hook)
gbmc_br_gw_src_lib=1
@@ -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
# Remove legacy network configuration
rm -rf /etc/systemd/network/{00,}-bmc-gbmcbr.network.d
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-}"
local old_ip=
if [ -n "$ip" ]; then
old_ip="$(cat /var/google/gbmc-br-ip 2>/dev/null)"
[ "$old_ip" == "$ip" ] && return
mkdir -p /var/google || return
echo "$ip" >/var/google/gbmc-br-ip || return
else
[ ! -f "/var/google/gbmc-br-ip" ] && return
rm -rf /var/google/gbmc-br-ip
fi
gbmc_br_run_hooks GBMC_BR_LIB_SET_IP_HOOKS "$ip" || return
gbmc_br_reload_ip "$ip"
}
gbmc_br_lib_init=1
@@ -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
@@ -0,0 +1,74 @@
#!/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.
[[ -n ${gbmc_br_nft_lib-} ]] && return
# shellcheck source=meta-google/recipes-google/networking/network-sh/lib.sh
source /usr/share/network/lib.sh || exit
gbmc_br_nft_pfx=
gbmc_br_nft_update() {
printf 'gBMC Bridge input firewall for %s\n' \
"${gbmc_br_nft_pfx:-(deleted)}" >&2
local contents=
contents+='table inet filter {'$'\n'
contents+=' chain gbmc_br_int_input {'$'\n'
if [[ -n ${gbmc_br_nft_pfx-} ]]; then
contents+=" ip6 saddr $gbmc_br_nft_pfx"
contents+=" ip6 daddr $gbmc_br_nft_pfx accept"$'\n'
fi
contents+=' }'$'\n'
contents+='}'$'\n'
local rfile=/run/nftables/40-gbmc-br-int.rules
mkdir -p "$(dirname "$rfile")"
printf '%s' "$contents" >"$rfile"
# shellcheck disable=SC2015
systemctl reset-failed nftables && systemctl --no-block reload-or-restart nftables || true
}
gbmc_br_nft_hook() {
# Match only global IP addresses on the bridge that match the BMC prefix
# (<mpfx>:fdxx:). So 2002:af4:3480:2248:fd02:6345:3069:9186 would become
# a 2002:af4:3480:2248:fd00/76 rule.
# shellcheck disable=SC2154
if [[ $change == addr && $intf == gbmcbr && $scope == global ]] &&
[[ $fam == inet6 && $flags != *tentative* ]]; then
local ip_bytes=()
if ! ip_to_bytes ip_bytes "$ip"; then
echo "gBMC Bridge NFT Invalid IP: $ip" >&2
return 1
fi
if (( ip_bytes[8] != 0xfd )); then
return 0
fi
local i
for (( i=9; i<16; i++ )); do
ip_bytes["$i"]=0
done
pfx="$(ip_bytes_to_str ip_bytes)/76"
if [[ $action == add && $pfx != "$gbmc_br_nft_pfx" ]]; then
gbmc_br_nft_pfx="$pfx"
gbmc_br_nft_update
fi
fi
}
GBMC_IP_MONITOR_HOOKS+=(gbmc_br_nft_hook)
gbmc_br_nft_lib=1
@@ -0,0 +1,74 @@
#!/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.
[[ -n ${gbmc_br_ula_lib-} ]] && return
# shellcheck source=meta-google/recipes-google/networking/network-sh/lib.sh
source /usr/share/network/lib.sh || exit
gbmc_br_ula_init=
gbmc_br_ula_mac=
gbmc_br_ula_update() {
[[ -n $gbmc_br_ula_init ]] || return
echo "gBMC Bridge ULA MAC: ${gbmc_br_ula_mac:-(deleted)}" >&2
local addr=
contents='[Network]'$'\n'
if [[ -n $gbmc_br_ula_mac ]]; then
local sfx
if sfx="$(mac_to_eui64 "$gbmc_br_ula_mac")" &&
addr="$(ip_pfx_concat "fdb5:0481:10ce::/64" "$sfx")"; then
contents+="Address=$addr"$'\n'
fi
fi
local netfile
for netfile in /run/systemd/network/{00,}-bmc-gbmcbr.network.d/60-ula.conf; do
mkdir -p "$(dirname "$netfile")"
printf '%s' "$contents" >"$netfile"
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_ula_hook() {
# shellcheck disable=SC2154
if [[ $change == init ]]; then
gbmc_br_ula_init=1
gbmc_br_ula_update
elif [[ $change == link && $intf == gbmcbr ]]; then
if [[ $action == add && $mac != "$gbmc_br_ula_mac" ]]; then
gbmc_br_ula_mac="$mac"
gbmc_br_ula_update
fi
if [[ $action == del && $mac == "$gbmc_br_ula_mac" ]]; then
gbmc_br_ula_mac=
gbmc_br_ula_update
fi
fi
}
GBMC_IP_MONITOR_HOOKS+=(gbmc_br_ula_hook)
gbmc_br_ula_lib=1
@@ -0,0 +1,20 @@
#!/bin/bash
# Copyright 2023 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.
# stop dhcp term service to prevent race condition
systemctl is-active --quiet gbmc-br-dhcp-term && systemctl stop gbmc-br-dhcp-term
# start the dhcp service
systemctl start gbmc-br-dhcp
@@ -0,0 +1,11 @@
<?xml version="1.0" ?>
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
<service-group>
<name>@NAME@</name>
<service>
<type>_ipmi._udp</type>
<port>623</port>
<txt-record>Machine=@MACHINE@</txt-record>
@EXTRA_ATTRS@
</service>
</service-group>
@@ -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}/
}
@@ -0,0 +1,30 @@
PR = "r1"
LICENSE = "Apache-2.0"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/Apache-2.0;md5=89aea4e17d99a7cacdbeed46a0096b10"
inherit systemd
RDEPENDS:${PN} += "iperf3"
SRC_URI += "file://iperf3.service"
SYSTEMD_SERVICE:${PN} += "iperf3.service"
do_install() {
# Install service definitions
install -d -m 0755 ${D}${systemd_system_unitdir}
install -m 0644 ${WORKDIR}/iperf3.service ${D}${systemd_system_unitdir}
}
# Allow IPERF3 to run on the gbmcbr node on DEV builds
do_install:append:dev() {
nftables_dir=${D}${sysconfdir}/nftables
rules=$nftables_dir/50-gbmc-iperf3-dev.rules
install -d -m0755 $nftables_dir
echo 'table inet filter {' >"$rules"
echo ' chain gbmc_br_pub_input {' >>"$rules"
echo ' tcp dport 5201 accept' >>"$rules"
echo ' }' >>"$rules"
echo '}' >>"$rules"
}
@@ -0,0 +1,50 @@
SUMMARY = "Configures MAC addresses on a gBMC system"
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-mac-config.service \
file://gbmc-mac-config.sh.in \
"
S = "${WORKDIR}"
RDEPENDS:${PN} += " \
bash \
ipmi-fru-sh \
"
FILES:${PN} += "${systemd_unitdir}"
SYSTEMD_SERVICE:${PN} += "gbmc-mac-config.service"
GBMC_MAC_EEPROM_OF_NAME ?= ""
# Maps the MAC address offset from the base address to an interface name
# in bash associative array syntax.
# Ex. "[0]=eth0 [2]=eth2"
GBMC_MAC_IF_MAP ?= ""
do_install:append() {
if [ -z '${GBMC_MAC_EEPROM_OF_NAME}' ]; then
echo 'Missing GBMC_MAC_EEPROM_OF_NAME' >&2
exit 1
fi
# Build time dictionary sanity check
bash -c "declare -A dict=(${GBMC_MAC_IF_MAP})"
sed gbmc-mac-config.sh.in \
-e 's#@EEPROM@#${GBMC_MAC_EEPROM_OF_NAME}#' \
-e "s#@NUM_TO_INTFS@#${GBMC_MAC_IF_MAP}#" \
>gbmc-mac-config.sh
install -d -m0755 ${D}${libexecdir}
install -m0755 gbmc-mac-config.sh ${D}${libexecdir}/
install -d -m0755 ${D}${systemd_system_unitdir}
install -m0644 gbmc-mac-config.service ${D}${systemd_system_unitdir}/
}
@@ -0,0 +1,34 @@
SUMMARY = "Rename the network device name"
PR = "r1"
LICENSE = "Apache-2.0"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/Apache-2.0;md5=89aea4e17d99a7cacdbeed46a0096b10"
GBMC_ETHER_MAP ?= ""
inherit systemd
S = "${WORKDIR}"
FILES:${PN} += "${systemd_unitdir}"
do_install() {
netdir=${D}${systemd_unitdir}/network
install -d -m0755 $netdir
# install dev renaming files if any
if [ -z "${GBMC_ETHER_MAP}"]; then
return
fi
devmap="${GBMC_ETHER_MAP}"
for str in $devmap
do
devaddr="$(echo "${str}" | cut -d'|' -f1)"
devname="$(echo "${str}" | cut -d'|' -f2)"
echo "[Match]" > ${WORKDIR}/30-netdev-${devname}.link
echo "Path=*-${devaddr}" >> ${WORKDIR}/30-netdev-${devname}.link
echo "[Link]" >> ${WORKDIR}/30-netdev-${devname}.link
echo "Name=${devname}" >> ${WORKDIR}/30-netdev-${devname}.link
install -m0644 ${WORKDIR}/30-netdev-${devname}.link ${netdir}
done
}
@@ -0,0 +1,31 @@
divert(-1)
define(`HOST_MAC_ARG', `ifelse($1, `invalid', `',
ifelse($1, `', `',
` --host-mac "$1"'))')
define(`DEV_MAC_ARG', `ifelse($1, `invalid', `',
ifelse($1, `', `',
` --dev-mac "$1"'))')
divert(0)dnl
dnl
[Unit]
Description=USB Gadget
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=M_SCRIPT_INSTALL_DIR/usb_network.sh \
--product-id "M_BMC_USB_PRODUCT_ID" \
--product-name "M_BMC_USB_PRODUCT_NAME" \
--dev-type "M_BMC_USB_TYPE" \
HOST_MAC_ARG(M_BMC_USB_HOST_MAC) \
DEV_MAC_ARG(M_BMC_USB_DEV_MAC) \
--iface-name "M_BMC_USB_IFACE" \
--bind-device "M_BMC_USB_BIND_DEV"
ExecStop=M_SCRIPT_INSTALL_DIR/usb_network.sh stop \
--dev-type "M_BMC_USB_TYPE" \
--iface-name "M_BMC_USB_IFACE"
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,231 @@
#!/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.
# List of options the script accepts. Trailing column means that the option
# requires an argument.
ARGUMENT_LIST=(
"help"
"product-id:"
"product-name:"
"host-mac:"
"bind-device:"
"dev-mac:"
"dev-type:"
"gadget-dir-name:"
"iface-name:"
)
print_usage() {
cat <<HELP
$0 [OPTIONS] [stop|start]
Create USB Gadget Configuration
--product-id USB Product Id for the gadget.
--product-name Product name string (en) for the gadget.
--host-mac MAC address of the host part of the connection. Optional.
--dev-mac MAC address of the device (gadget) part of the connection. Optional.
--dev-type Type of gadget to instantiate. Default: "eem"
--bind-device Name of the device to bind, as listed in /sys/class/udc/
--gadget-dir-name Optional base name for gadget directory. Default: iface-name
--iface-name name of the network interface.
--help Print this help and exit.
HELP
}
gadget_start() {
# Always provide a basic network configuration
mkdir -p /run/systemd/network || return
cat >/run/systemd/network/+-bmc-"${IFACE_NAME}".network <<EOF
[Match]
Name=${IFACE_NAME}
EOF
# Add the gbmcbr configuration if this is a relevant device
if (( ID_VENDOR == 0x18d1 && ID_PRODUCT == 0x22b )); then
cat >>/run/systemd/network/+-bmc-"${IFACE_NAME}".network <<EOF
[Network]
Bridge=gbmcbr
[Bridge]
Cost=85
EOF
fi
# Ignore any failures due to systemd being unavailable at boot
networkctl reload || true
local gadget_dir="${CONFIGFS_HOME}/usb_gadget/${GADGET_DIR_NAME}"
mkdir -p "${gadget_dir}" || return
echo "${ID_VENDOR}" >"${gadget_dir}/idVendor" || return
echo "${ID_PRODUCT}" >"${gadget_dir}/idProduct" || return
local str_en_dir="${gadget_dir}/strings/0x409"
mkdir -p "${str_en_dir}" || return
echo "${STR_EN_VENDOR}" >"${str_en_dir}/manufacturer" || return
echo "${STR_EN_PRODUCT}" >"${str_en_dir}/product" || return
local config_dir="${gadget_dir}/configs/c.1"
mkdir -p "${config_dir}" || return
echo 100 > "${config_dir}/MaxPower" || return
mkdir -p "${config_dir}/strings/0x409" || return
echo "${DEV_TYPE^^}" > "${config_dir}/strings/0x409/configuration" || return
local func_dir="${gadget_dir}/functions/${DEV_TYPE}.${IFACE_NAME}"
mkdir -p "${func_dir}" || return
if [[ -n $HOST_MAC_ADDR ]]; then
echo "${HOST_MAC_ADDR}" >"${func_dir}"/host_addr || return
fi
if [[ -n $DEV_MAC_ADDR ]]; then
echo "${DEV_MAC_ADDR}" >"${func_dir}"/dev_addr || return
fi
ln -s "${func_dir}" "${config_dir}" || return
# This only works on kernel 5.12+, we have to ignore failures for now
echo "$IFACE_NAME" >"${func_dir}"/ifname || true
echo "${BIND_DEVICE}" >"${gadget_dir}"/UDC || return
# Try to reconfigure a few times in case we race with systemd-networkd
local start=$SECONDS
while (( SECONDS - start < 5 )); do
local ifname
ifname="$(<"${func_dir}"/ifname)" || return
[ "${IFACE_NAME}" = "$ifname" ] && break
ip link set dev "$ifname" down && \
ip link set dev "$ifname" name "${IFACE_NAME}" && break
sleep 1
done
ip link set dev "$IFACE_NAME" up || return
}
gadget_stop() {
local gadget_dir="${CONFIGFS_HOME}/usb_gadget/${GADGET_DIR_NAME}"
rm -f "${gadget_dir}/configs/c.1/${DEV_TYPE}.${IFACE_NAME}"
rmdir "${gadget_dir}/functions/${DEV_TYPE}.${IFACE_NAME}" \
"${gadget_dir}/configs/c.1/strings/0x409" \
"${gadget_dir}/configs/c.1" \
"${gadget_dir}/strings/0x409" \
"${gadget_dir}" || true
rm -f /run/systemd/network/+-bmc-"${IFACE_NAME}".network
networkctl reload || true
}
opts="$(getopt \
--longoptions "$(printf "%s," "${ARGUMENT_LIST[@]}")" \
--name "$(basename "$0")" \
--options "" \
-- "$@"
)"
eval set -- "$opts"
CONFIGFS_HOME=${CONFIGFS_HOME:-/sys/kernel/config}
ID_VENDOR="0x18d1" # Google
ID_PRODUCT=""
STR_EN_VENDOR="Google"
STR_EN_PRODUCT=""
DEV_MAC_ADDR=""
DEV_TYPE="eem"
HOST_MAC_ADDR=""
BIND_DEVICE=""
ACTION="start"
GADGET_DIR_NAME=""
IFACE_NAME=""
while [[ $# -gt 0 ]]; do
case "$1" in
--product-id)
ID_PRODUCT=$2
shift 2
;;
--product-name)
STR_EN_PRODUCT=$2
shift 2
;;
--host-mac)
HOST_MAC_ADDR=$2
shift 2
;;
--dev-mac)
DEV_MAC_ADDR=$2
shift 2
;;
--dev-type)
DEV_TYPE=$2
shift 2
;;
--bind-device)
BIND_DEVICE=$2
shift 2
;;
--gadget-dir-name)
GADGET_DIR_NAME=$2
shift 2
;;
--iface-name)
IFACE_NAME=$2
shift 2
;;
--help)
print_usage
exit 0
;;
start)
ACTION="start"
shift 1
break
;;
stop)
ACTION="stop"
shift 1
break
;;
--)
shift 1
;;
*)
break
;;
esac
done
if [ -z "$GADGET_DIR_NAME" ]; then
GADGET_DIR_NAME="$IFACE_NAME"
fi
if [[ $ACTION == "stop" ]]; then
gadget_stop
else
if [ -z "$ID_PRODUCT" ]; then
echo "Product ID is missing" >&2
exit 1
fi
if [ -z "$IFACE_NAME" ]; then
echo "Interface name is missing" >&2
exit 1
fi
if [ -z "$BIND_DEVICE" ]; then
echo "Bind device is missing" >&2
exit 1
fi
rc=0
gadget_start || rc=$?
(( rc == 0 )) || gadget_stop || true
exit $rc
fi
@@ -0,0 +1,290 @@
#!/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.
TEMPDIRS=()
# Script under test
SUT=$PWD/usb_network.sh
TEST_STATUS="OK"
test_setup() {
echo -n "Testing $1 ..."
FAKE_CONFIGFS="$(mktemp -d)"
TEMPDIRS+=("${FAKE_CONFIGFS}")
FAKE_GADGETFS="$FAKE_CONFIGFS"/usb_gadget
mkdir -p "$FAKE_GADGETFS"
}
test_teardown() {
echo ${TEST_STATUS}
rm -rf -- "${TEMPDIRS[@]}"
TEMPDIRS=()
}
test_fail() {
echo -n " $* " >&2
TEST_STATUS=FAIL
test_teardown
exit 1
}
check_file_content() {
local filename="$1"
local expected_content="$2"
if [[ ! -f ${filename} ]]; then
test_fail "File ${filename} does not exist!"
fi
local actual_content
actual_content=$(<"${filename}")
if [[ $expected_content != "$actual_content" ]]; then
test_fail "Expected ${expected_content}, got ${actual_content}"
fi
}
test_gadget_creation_with_defaults() {
local extra_args=()
local gadget_dir="$1"
if [[ $gadget_dir == "" ]]; then
gadget_dir="g1";
else
extra_args+=(--gadget-dir-name "${gadget_dir}")
fi
local product_name="Souvenier BMC"
local product_id="0xcafe"
local host_mac="ab:cd:ef:10:11:12"
local dev_mac="12:11:10:ef:cd:ab"
local bind_device="f80002000.udc"
if ! CONFIGFS_HOME="${FAKE_CONFIGFS}" "${SUT}" --product-id "${product_id}" \
--product-name "${product_name}" \
--host-mac "${host_mac}" \
--dev-mac "${dev_mac}" \
--bind-device "${bind_device}" \
"${extra_args[@]}"; then
test_fail "${SUT} failed"
fi
if [[ ! -d ${FAKE_GADGETFS}/${gadget_dir} ]]; then
test_fail "Gadget was not created!"
fi
check_file_content "${FAKE_GADGETFS}/${gadget_dir}/idVendor" "0x18d1"
check_file_content "${FAKE_GADGETFS}/${gadget_dir}/idProduct" "${product_id}"
check_file_content "${FAKE_GADGETFS}/${gadget_dir}/strings/0x409/manufacturer" "Google"
check_file_content "${FAKE_GADGETFS}/${gadget_dir}/strings/0x409/product" "${product_name}"
check_file_content "${FAKE_GADGETFS}/${gadget_dir}/configs/c.1/MaxPower" "100"
check_file_content "${FAKE_GADGETFS}/${gadget_dir}/configs/c.1/strings/0x409/configuration" "EEM"
check_file_content "${FAKE_GADGETFS}/${gadget_dir}/functions/eem.usb0/dev_addr" "${dev_mac}"
check_file_content "${FAKE_GADGETFS}/${gadget_dir}/functions/eem.usb0/host_addr" "${host_mac}"
if [[ ! -d ${FAKE_GADGETFS}/${gadget_dir}/functions/eem.usb0 ]]; then
test_fail "Function directory was not created"
fi
local func_link="${FAKE_GADGETFS}/${gadget_dir}/configs/c.1/ee.usb0"
if [[ ! -L ${func_link} ]]; then
test_fail "Symlink to the function was not created in the config"
fi
local link_dest
link_dest="$(realpath "${func_link}")"
if [[ $link_dest != "${FAKE_GADGETFS}/${gadget_dir}/functions/eem.usb0" ]]; then
test_fail "Symlink points to the wrong file/dir"
fi
check_file_content "${FAKE_GADGETFS}/${gadget_dir}/UDC" "${bind_device}"
}
test_gadget_creation_with_override() {
mkdir -p "${FAKE_GADGETFS}"/g1/{strings,configs,functions}
touch "${FAKE_GADGETFS}"/g1/{idVendor,idProduct}
test_gadget_creation_with_defaults
}
test_gadget_stopping() {
local extra_args=()
local gadget_dir="$1"
local iface_name="$2"
if [[ $gadget_dir == "" ]]; then
gadget_dir="g1";
else
extra_args+=(--gadget-dir-name "${gadget_dir}")
fi
if [[ $iface_name == "" ]]; then
iface_name="usb0";
else
extra_args+=(--iface-name "${iface_name}")
fi
CONFIGFS_HOME=${FAKE_CONFIGFS} ${SUT} "${extra_args[@]}" stop
if test -d "${FAKE_GADGETFS}/${gadget_dir}"; then
test_fail "Gadget was not removed!"
fi
}
test_gadget_creation_no_macs() {
local gadget_dir="g1";
local product_name="Souvenier BMC"
local product_id="0xcafe"
local bind_device="f80002000.udc"
CONFIGFS_HOME=${FAKE_CONFIGFS} ${SUT} --product-id "${product_id}" \
--product-name "${product_name}" \
--bind-device "${bind_device}"
if test $? -ne 0; then
test_fail "${SUT} failed"
fi
if ! test -d "${FAKE_GADGETFS}/${gadget_dir}"; then
test_fail "Gadget was not created!"
fi
check_file_content "${FAKE_GADGETFS}/${gadget_dir}/idVendor" "0x18d1"
check_file_content "${FAKE_GADGETFS}/${gadget_dir}/idProduct" "${product_id}"
check_file_content "${FAKE_GADGETFS}/${gadget_dir}/strings/0x409/manufacturer" "Google"
check_file_content "${FAKE_GADGETFS}/${gadget_dir}/strings/0x409/product" "${product_name}"
check_file_content "${FAKE_GADGETFS}/${gadget_dir}/configs/c.1/MaxPower" "100"
check_file_content "${FAKE_GADGETFS}/${gadget_dir}/configs/c.1/strings/0x409/configuration" "EEM"
if [[ -e ${FAKE_GADGETFS}/${gadget_dir}/functions/eem.usb0/dev_addr ]]; then
test_fail "dev_addr should not be set"
fi
if [[ -e ${FAKE_GADGETFS}/${gadget_dir}/functions/eem.usb0/host_addr ]]; then
test_fail "host_addr should not be set"
fi
local func_link="${FAKE_GADGETFS}/${gadget_dir}/configs/c.1/eem.usb0"
if [[ ! -L ${func_link} ]]; then
test_fail "Symlink to the function was not created in the config"
fi
local link_dest
link_dest="$(realpath "${func_link}")"
if [[ $link_dest != ${FAKE_GADGETFS}/${gadget_dir}/functions/eem.usb0 ]]; then
test_fail "Symlink points to the wrong file/dir"
fi
check_file_content "${FAKE_GADGETFS}/${gadget_dir}/UDC" "${bind_device}"
}
test_gadget_creation_alt_iface() {
local gadget_dir="g1";
local product_name="Souvenier BMC"
local product_id="0xcafe"
local bind_device="f80002000.udc"
local iface_name="iface0"
if ! CONFIGFS_HOME=${FAKE_CONFIGFS} ${SUT} --product-id "${product_id}" \
--product-name "${product_name}" \
--bind-device "${bind_device}" \
--iface-name "${iface_name}"; then
test_fail "${SUT} failed"
fi
if [[ ! -d "${FAKE_GADGETFS}/${gadget_dir}" ]]; then
test_fail "Gadget was not created!"
fi
check_file_content "${FAKE_GADGETFS}/${gadget_dir}/idVendor" "0x18d1"
check_file_content "${FAKE_GADGETFS}/${gadget_dir}/idProduct" "${product_id}"
check_file_content "${FAKE_GADGETFS}/${gadget_dir}/strings/0x409/manufacturer" "Google"
check_file_content "${FAKE_GADGETFS}/${gadget_dir}/strings/0x409/product" "${product_name}"
check_file_content "${FAKE_GADGETFS}/${gadget_dir}/configs/c.1/MaxPower" "100"
check_file_content "${FAKE_GADGETFS}/${gadget_dir}/configs/c.1/strings/0x409/configuration" "EEM"
if [[ ! -d ${FAKE_GADGETFS}/${gadget_dir}/functions/eem.${iface_name} ]]; then
test_fail "Function directory was not created"
fi
if [[ -e ${FAKE_GADGETFS}/${gadget_dir}/functions/eem.${iface_name}/dev_addr ]]; then
test_fail "dev_addr should not be set"
fi
if [[ -e ${FAKE_GADGETFS}/${gadget_dir}/functions/eem.${iface_name}/host_addr ]]; then
test_fail "host_addr should not be set"
fi
local func_link="${FAKE_GADGETFS}/${gadget_dir}/configs/c.1/eem.${iface_name}"
if [[ ! -L ${func_link} ]]; then
test_fail "Symlink to the function was not created in the config"
fi
local link_dest
link_dest="$(realpath "${func_link}")"
if [[ $link_dest != "${FAKE_GADGETFS}/${gadget_dir}/functions/eem.${iface_name}" ]]; then
test_fail "Symlink points to the wrong file/dir"
fi
check_file_content "${FAKE_GADGETFS}/${gadget_dir}/UDC" "${bind_device}"
}
# -------------------------------------------------------------------
test_setup "Device Creation"
test_gadget_creation_with_defaults
test_teardown
# -------------------------------------------------------------------
# -------------------------------------------------------------------
test_setup "Device Creation With Override"
test_gadget_creation_with_override
test_teardown
# -------------------------------------------------------------------
# -------------------------------------------------------------------
test_setup "Test Device Stop"
test_gadget_creation_with_defaults
test_gadget_stopping
test_teardown
# -------------------------------------------------------------------
# -------------------------------------------------------------------
test_setup "Device Creation/Stopping, Alternative Name"
test_gadget_creation_with_defaults "gAlt"
test_gadget_stopping "gAlt"
test_teardown
# -------------------------------------------------------------------
# -------------------------------------------------------------------
test_setup "Device Creation without MAC Addrs"
test_gadget_creation_no_macs
test_teardown
# -------------------------------------------------------------------
# -------------------------------------------------------------------
test_setup "Device Creation/Stopping, Alternative Interface"
test_gadget_creation_alt_iface
test_teardown
# -------------------------------------------------------------------
echo "SUCCESS!"
@@ -0,0 +1,57 @@
SUMMARY = "Google USB EEM Gadget Configuration Script"
DESCRIPTION = "Google USB EEM Gadget Configuration Script"
PR = "r1"
PV = "0.2"
LICENSE = "Apache-2.0"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/Apache-2.0;md5=89aea4e17d99a7cacdbeed46a0096b10"
FILESEXTRAPATHS:prepend = "${THISDIR}/${PN}:"
inherit systemd
DEPENDS += "m4-native"
DEPENDS += "systemd"
RDEPENDS:${PN} += "bash"
BMC_USB_ECM_PRODUCT_ID ??= ""
BMC_USB_ECM_PRODUCT_NAME ??= "${MACHINE} BMC"
BMC_USB_ECM_HOST_MAC ??= "invalid"
BMC_USB_ECM_DEV_MAC ??= "invalid"
BMC_USB_ECM_BIND_DEV ??= ""
BMC_USB_TYPE ??= "eem"
BMC_USB_IFACE ??= "gusb0"
SRC_URI += "file://usb_network.service.m4"
SRC_URI += "file://usb_network.sh"
SYSTEMD_PACKAGES = "${PN}"
SYSTEMD_SERVICE:${PN} = "${@'usb_network.service' if d.getVar('BMC_USB_ECM_PRODUCT_ID') else ''}"
do_compile:append() {
if [ -n "${BMC_USB_ECM_PRODUCT_ID}" ]; then
test "X${BMC_USB_ECM_PRODUCT_NAME}" != "X" || bberror "Please define BMC_USB_ECM_PRODUCT_NAME"
test "X${BMC_USB_ECM_BIND_DEV}" != "X" || bberror "Please define BMC_USB_ECM_BIND_DEV"
m4 \
-DM_BMC_USB_PRODUCT_ID="${BMC_USB_ECM_PRODUCT_ID}" \
-DM_BMC_USB_PRODUCT_NAME="${BMC_USB_ECM_PRODUCT_NAME}" \
-DM_BMC_USB_TYPE="${BMC_USB_TYPE}" \
-DM_BMC_USB_HOST_MAC="${BMC_USB_ECM_HOST_MAC}" \
-DM_BMC_USB_DEV_MAC="${BMC_USB_ECM_DEV_MAC}" \
-DM_BMC_USB_IFACE="${BMC_USB_IFACE}" \
-DM_BMC_USB_BIND_DEV="${BMC_USB_ECM_BIND_DEV}" \
-DM_SCRIPT_INSTALL_DIR="${bindir}" \
${WORKDIR}/usb_network.service.m4 > ${WORKDIR}/usb_network.service
fi
}
do_install:append() {
install -d ${D}/${bindir}
install -m 0755 ${WORKDIR}/usb_network.sh ${D}/${bindir}
if [ -n "${BMC_USB_ECM_PRODUCT_ID}" ]; then
install -d ${D}${systemd_system_unitdir}
install -m 0644 ${WORKDIR}/usb_network.service ${D}${systemd_system_unitdir}
fi
}
@@ -0,0 +1,23 @@
SUMMARY = "Shell functions for manipulating network addresses"
PR = "r1"
LICENSE = "Apache-2.0"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/Apache-2.0;md5=89aea4e17d99a7cacdbeed46a0096b10"
FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"
SRC_URI += "file://lib.sh"
SRC_URI += "file://test.sh"
S = "${WORKDIR}"
DATA = "${datadir}/network"
FILES:${PN} += "${DATA}"
DEPENDS += "test-sh"
do_compile() {
SYSROOT="$PKG_CONFIG_SYSROOT_DIR" bash test.sh || exit
}
do_install:append() {
install -d -m0755 ${D}${DATA}
install -m0644 lib.sh ${D}${DATA}/
}
@@ -0,0 +1,295 @@
#!/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.
[ -n "${network_init-}" ] && return
mac_to_bytes() {
local -n bytes="$1"
local str="$2"
# Verify that the MAC is Valid
[[ "$str" =~ ^[[:xdigit:]]{1,2}(:[[:xdigit:]]{1,2}){5}$ ]] || return
# Split the mac into hex bytes
local oldifs="$IFS"
IFS=:
local byte
for byte in $str; do
bytes+=("0x$byte")
done
IFS="$oldifs"
}
mac_to_eui48() {
local mac_bytes=(0 0 0 0 0 0 0 0 0 0)
mac_to_bytes mac_bytes "$1" || return
# Return the EUI-64 bytes in the IPv6 format
ip_bytes_to_str mac_bytes
}
mac_to_eui64() {
local mac_bytes=()
mac_to_bytes mac_bytes "$1" || return
# Using EUI-64 conversion rules, create the suffix bytes from MAC bytes
# Invert bit-1 of the first byte, and insert 0xfffe in the middle.
# shellcheck disable=SC2034
local suffix_bytes=(
0 0 0 0 0 0 0 0
$((mac_bytes[0] ^ 2))
"${mac_bytes[@]:1:2}"
$((0xff)) $((0xfe))
"${mac_bytes[@]:3:3}"
)
# Return the EUI-64 bytes in the IPv6 format
ip_bytes_to_str suffix_bytes
}
ip_to_bytes() {
local -n bytes_out="$1"
local str="$2"
local bytes=()
local oldifs="$IFS"
# Heuristic for V4 / V6, validity will be checked as it is parsed
if [[ "$str" == *.* ]]; then
# Ensure we don't start or end with IFS
[ "${str:0:1}" != '.' ] || return 1
[ "${str: -1}" != '.' ] || return 1
local v
# Split IPv4 address into octets
IFS=.
for v in $str; do
# IPv4 digits are always decimal numbers
if ! [[ "$v" =~ ^[0-9]+$ ]]; then
IFS="$oldifs"
return 1
fi
# Each octet is a single byte, make sure the number isn't larger
if (( v > 0xff )); then
IFS="$oldifs"
return 1
fi
bytes+=("$v")
done
# IPv4 addresses must have all 4 bytes present
if (( "${#bytes[@]}" != 4 )); then
IFS="$oldifs"
return 1
fi
else
# Ensure we bound the padding in an outer byte for
# IFS splitting to work correctly
[ "${str:0:2}" = '::' ] && str="0$str"
[ "${str: -2}" = '::' ] && str="${str}0"
# Ensure we don't start or end with IFS
[ "${str:0:1}" != ':' ] || return 1
[ "${str: -1}" != ':' ] || return 1
# Stores the bytes that come before ::, if it exists
local bytesBeforePad=()
local v
# Split the Address into hextets
IFS=:
for v in $str; do
# Handle ::, which translates to an empty string
if [ -z "$v" ]; then
# Only allow a single :: sequence in an address
if (( "${#bytesBeforePad[@]}" > 0 )); then
IFS="$oldifs"
return 1
fi
# Store the already parsed upper bytes separately
# This allows us to calculate and insert padding
bytesBeforePad=("${bytes[@]}")
bytes=()
continue
fi
# IPv6 digits are always hex
if ! [[ "$v" =~ ^[[:xdigit:]]+$ ]]; then
IFS="$oldifs"
return 1
fi
# Ensure the number is no larger than a hextet
v="0x$v"
if (( v > 0xffff )); then
IFS="$oldifs"
return 1
fi
# Split the hextet into 2 bytes
bytes+=($(( v >> 8 )))
bytes+=($(( v & 0xff )))
done
# If we have ::, add padding
if (( "${#bytesBeforePad[@]}" > 0 )); then
# Fill the middle bytes with padding and store in `bytes`
while (( "${#bytes[@]}" + "${#bytesBeforePad[@]}" < 16 )); do
bytesBeforePad+=(0)
done
bytes=("${bytesBeforePad[@]}" "${bytes[@]}")
fi
# IPv6 addresses must have all 16 bytes present
if (( "${#bytes[@]}" != 16 )); then
IFS="$oldifs"
return 1
fi
fi
IFS="$oldifs"
# shellcheck disable=SC2034
bytes_out=("${bytes[@]}")
}
ip_bytes_to_str() {
# shellcheck disable=SC2178
local -n bytes="$1"
if (( "${#bytes[@]}" == 4 )); then
printf '%d.%d.%d.%d\n' "${bytes[@]}"
elif (( "${#bytes[@]}" == 16 )); then
# Track the starting position of the longest run of 0 hextets (2 bytes)
local longest_i=0
# Track the size of the longest run of 0 hextets
local longest_s=0
# The index of the first 0 byte in the current run of zeros
local first_zero=0
local i
# Find the location of the longest run of zero hextets, preferring same
# size runs later in the address.
for (( i=0; i<=16; i+=2 )); do
# Terminate the run of zeros if we are at the end of the array or
# have a non-zero hextet
if (( i == 16 || bytes[i] != 0 || bytes[$((i+1))] != 0 )); then
local s=$((i - first_zero))
if (( s >= longest_s )); then
longest_i=$first_zero
longest_s=$s
fi
first_zero=$((i+2))
fi
done
# Build the address string by each hextet
for (( i=0; i<16; i+=2 )); do
# If we encountered a run of zeros, add the necessary :: at the end
# of the string. If not at the end, a single : is added since : is
# printed to subsequent hextets already.
if (( i == longest_i )); then
(( i += longest_s-2 ))
printf ':'
# End of string needs to be ::
if (( i == 14 )); then
printf ':'
fi
else
# Prepend : to all hextets except the first for separation
if (( i != 0 )); then
printf ':'
fi
printf '%x' $(( (bytes[i]<<8) | bytes[$((i+1))]))
fi
done
printf '\n'
else
echo "Invalid IP Bytes: ${bytes[*]}" >&2
return 1
fi
}
ip_pfx_concat() {
local pfx="$1"
local sfx="$2"
# Parse the prefix
if ! [[ "$pfx" =~ ^([0-9a-fA-F:.]+)/([0-9]+)$ ]]; then
echo "Invalid IP prefix: $pfx" >&2
return 1
fi
local addr="${BASH_REMATCH[1]}"
local cidr="${BASH_REMATCH[2]}"
# Ensure prefix doesn't have too many bytes
local pfx_bytes=()
if ! ip_to_bytes pfx_bytes "$addr"; then
echo "Invalid IP prefix: $pfx" >&2
return 1
fi
if (( ${#pfx_bytes[@]}*8 < cidr )); then
echo "Prefix CIDR too large" >&2
return 1
fi
# CIDR values might partially divide a byte so we need to mask out
# only the part of the byte we want to check for emptiness
if (( (pfx_bytes[cidr/8] & ~(~0 << (8-cidr%8))) != 0 )); then
echo "Invalid byte $((cidr/8)): $pfx" >&2
return 1
fi
local i
# Check the rest of the whole bytes to make sure they are empty
for (( i=cidr/8+1; i<${#pfx_bytes[@]}; i++ )); do
if (( pfx_bytes[i] != 0 )); then
echo "Byte $i not 0: $pfx" >&2
return 1
fi
done
# Validate the suffix
local sfx_bytes=()
if ! ip_to_bytes sfx_bytes "$sfx"; then
echo "Invalid IPv6 suffix: $sfx" >&2
return 1
fi
if (( "${#sfx_bytes[@]}" != "${#pfx_bytes[@]}" )); then
echo "Suffix not the same family as prefix: $pfx $sfx" >&2
return 1
fi
# Check potential partially divided bytes for emptiness in the upper part
# based on the division specified in CIDR.
if (( (sfx_bytes[cidr/8] & (~0 << (8-cidr%8))) != 0 )); then
echo "Invalid byte $((cidr/8)): $sfx" >&2
return 1
fi
local i
# Check the bytes before the CIDR for emptiness to ensure they don't overlap
for (( i=0; i<cidr/8; i++ )); do
if (( sfx_bytes[i] != 0 )); then
echo "Byte $i not 0: $sfx" >&2
return 1
fi
done
out_bytes=()
for (( i=0; i<${#pfx_bytes[@]}; i++ )); do
out_bytes+=($(( pfx_bytes[i] | sfx_bytes[i] )))
done
echo "$(ip_bytes_to_str out_bytes)/$cidr"
}
ip_pfx_to_cidr() {
[[ "$1" =~ ^[0-9a-fA-F:.]+/([0-9]+)$ ]] || return
echo "${BASH_REMATCH[1]}"
}
normalize_ip() {
# shellcheck disable=SC2034
local ip_bytes=()
ip_to_bytes ip_bytes "$1" || return
ip_bytes_to_str ip_bytes
}
network_init=1
+205
View File
@@ -0,0 +1,205 @@
#!/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")" || exit
if [ -e ../network-sh.bb ]; then
# shellcheck source=meta-google/recipes-google/test/test-sh/lib.sh
source '../../test/test-sh/lib.sh'
else
# shellcheck source=meta-google/recipes-google/test/test-sh/lib.sh
source "$SYSROOT/usr/share/test/lib.sh"
fi
# shellcheck source=meta-google/recipes-google/networking/network-sh/lib.sh
source lib.sh
expect_array_numeq() {
local -n a1="$1"
local -n a2="$2"
if (( "${#a1[@]}" != "${#a2[@]}" )); then
echo " Line ${BASH_LINENO[0]} Array Size ${#a1[@]} != ${#a2[@]}" >&2
test_err=1
else
local i
for (( i=0; i < ${#a1[@]}; ++i )); do
expect_numeq "${a1[$i]}" "${a2[$i]}"
done
fi
}
test_mac_to_bytes() {
out=()
expect_err 1 mac_to_bytes out ''
expect_err 1 mac_to_bytes out '00'
expect_err 1 mac_to_bytes out '12:34:56:78:90:'
expect_err 1 mac_to_bytes out ':12:34:56:78:90'
expect_err 1 mac_to_bytes out '12:34:56:78:90:0:'
expect_err 1 mac_to_bytes out '12:34:56:78:90:0:2'
expect_err 0 mac_to_bytes out 'a2:0:f:de:0:29'
expected=(0xa2 0 0xf 0xde 0 0x29)
expect_array_numeq out expected
}
test_mac_to_eui48() {
str="$(mac_to_eui48 '12:34:56:78:90:af')" || fail
expect_streq "$str" '::1234:5678:90af'
}
test_mac_to_eui64() {
str="$(mac_to_eui64 '12:34:56:78:90:af')" || fail
expect_streq "$str" '::1034:56ff:fe78:90af'
}
test_ip4_to_bytes() {
out=()
expect_err 1 ip_to_bytes out ''
expect_err 1 ip_to_bytes out '10.0.0.'
expect_err 1 ip_to_bytes out '.0.1.1'
expect_err 1 ip_to_bytes out '10.0.0'
expect_err 1 ip_to_bytes out '10.0..0'
expect_err 1 ip_to_bytes out '.10.0.0.0'
expect_err 1 ip_to_bytes out '10.0.0.0.'
expect_err 1 ip_to_bytes out '10.0.0.256'
expect_err 1 ip_to_bytes out '10.0.0.0.256'
expect_err 1 ip_to_bytes out '10.0.0.0.1'
expect_err 0 ip_to_bytes out '10.0.0.1'
expected=(10 0 0 1)
expect_array_numeq out expected
}
test_ip6_to_bytes() {
out=()
expect_err 1 ip_to_bytes out ''
expect_err 1 ip_to_bytes out ':::'
expect_err 1 ip_to_bytes out '::z'
expect_err 1 ip_to_bytes out '1::1::1'
expect_err 1 ip_to_bytes out '1:1:1'
expect_err 1 ip_to_bytes out ':1::1'
expect_err 1 ip_to_bytes out '1::1:'
expect_err 0 ip_to_bytes out '::'
expected=(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
expect_array_numeq out expected
out=()
expect_err 0 ip_to_bytes out '::1'
expected=(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1)
expect_array_numeq out expected
out=()
expect_err 0 ip_to_bytes out 'fd00::'
expected=(0xfd 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
expect_array_numeq out expected
out=()
expect_err 0 ip_to_bytes out 'fd00:ffee::ddff:22'
expected=(0xfd 0 0xff 0xee 0 0 0 0 0 0 0 0 0xdd 0xff 0 0x22)
expect_array_numeq out expected
out=()
expect_err 0 ip_to_bytes out '1:2:3:4:5:6:7:8'
expected=(0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8)
expect_array_numeq out expected
# shellcheck disable=SC2034
out=()
}
test_ip4_bytes_str() {
in=(10 0 255 1)
str="$(ip_bytes_to_str in)" || fail
expect_streq "$str" '10.0.255.1'
}
test_ip6_bytes_str() {
in=(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
str="$(ip_bytes_to_str in)" || fail
expect_streq "$str" '::'
in=(0xfd 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
str="$(ip_bytes_to_str in)" || fail
expect_streq "$str" 'fd00::'
in=(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0xfd)
str="$(ip_bytes_to_str in)" || fail
expect_streq "$str" '::fd'
in=(0xfd 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1)
str="$(ip_bytes_to_str in)" || fail
expect_streq "$str" 'fd01::1'
in=(0xfd 1 0 0 0 0 0 0 0 1 0 0 0 0 0 1)
str="$(ip_bytes_to_str in)" || fail
expect_streq "$str" 'fd01::1:0:0:1'
in=(0xfd 1 0 0 0 0 0 1 0 1 0 0 0 0 0 1)
str="$(ip_bytes_to_str in)" || fail
expect_streq "$str" 'fd01:0:0:1:1::1'
# shellcheck disable=SC2034
in=(0 1 0 1 0xdd 0xdd 0 1 0 1 0 1 0 1 0 1)
str="$(ip_bytes_to_str in)" || fail
expect_streq "$str" '1:1:dddd:1:1:1:1:1'
}
test_ip_pfx_concat() {
# Invalid inputs
expect_err 1 ip_pfx_concat 'fd/64' '::1234:5678:90af'
expect_err 1 ip_pfx_concat 'fd01::' '::1234:5678:90af'
expect_err 1 ip_pfx_concat 'fd01:' '::1234:5678:90af'
expect_err 1 ip_pfx_concat 'fd01::/a0' '::1234:5678:90af'
expect_err 1 ip_pfx_concat 'fd01::/64' ':1234:5678:90af'
expect_err 1 ip_pfx_concat 'fd01::/64' ''
expect_err 1 ip_pfx_concat 'fd01::/129' '::1'
# Too many address bits
expect_err 1 ip_pfx_concat 'fd01:1:1:1:1::/64' '::1234:5678:90af'
expect_err 1 ip_pfx_concat 'fd01::/64' '::1:0:1234:5678:90af'
expect_err 1 ip_pfx_concat 'fd01::/79' '::3:1234:5678:90af'
expect_err 1 ip_pfx_concat 'fd01::/15' '::3:1234:5678:90af'
expect_err 1 ip_pfx_concat '10.0.0.1/31' '0.0.0.0'
str="$(ip_pfx_concat '::1/128' '::0')" || fail
expect_streq "$str" '::1/128'
str="$(ip_pfx_concat 'fd01::/64' '::1')" || fail
expect_streq "$str" 'fd01::1/64'
str="$(ip_pfx_concat 'fd01::/127' '::1')" || fail
expect_streq "$str" 'fd01::1/127'
str="$(ip_pfx_concat 'fd02::/15' '::1')" || fail
expect_streq "$str" 'fd02::1/15'
str="$(ip_pfx_concat 'fd01::/72' '::1234:5678:90af')" || fail
expect_streq "$str" 'fd01::1234:5678:90af/72'
str="$(ip_pfx_concat 'fd01:eeee:aaaa:cccc::/64' '::a:1234:5678:90af')" || fail
expect_streq "$str" 'fd01:eeee:aaaa:cccc:a:1234:5678:90af/64'
str="$(ip_pfx_concat 'fd01::fd00:0:0:0/80' '::1')" || fail
expect_streq "$str" 'fd01::fd00:0:0:1/80'
str="$(ip_pfx_concat '10.0.0.0/24' '0.0.0.1')" || fail
expect_streq "$str" '10.0.0.1/24'
}
test_ip_pfx_to_cidr() {
expect_err 1 ip_pfx_to_cidr 'z/64'
expect_err 1 ip_pfx_to_cidr '64'
cidr="$(ip_pfx_to_cidr 'fd01::/64')" || fail
expect_numeq "$cidr" 64
cidr="$(ip_pfx_to_cidr 'fd01:eeee:aaaa:cccc:a:1234:5678:90af/128')" || fail
expect_numeq "$cidr" 128
cidr="$(ip_pfx_to_cidr '10.0.0.1/24')" || fail
expect_numeq "$cidr" 24
}
test_normalize_ip() {
ip="$(normalize_ip 'fd01:1::0:0:1')" || fail
expect_streq "$ip" 'fd01:1::1'
}
main