Initial commit
This commit is contained in:
@@ -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
|
||||
+290
@@ -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
@@ -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
|
||||
Reference in New Issue
Block a user