udhcpc hooks for ENI IPv6 & secondary IPv4

Automatically sets up any IPv6 and secondary IPv4 on instance ENIs when DHCP leases are bound or renewed on that interface.

Resolves #70
This commit is contained in:
Jake Buchholz 2020-08-29 19:09:12 -07:00 committed by Mike Crute
parent 27491bcb20
commit a9ba2532df
3 changed files with 124 additions and 1 deletions

112
scripts/alpine-ec2-eni-hook Executable file
View File

@ -0,0 +1,112 @@
#!/usr/bin/env sh
# vim: set ft=sh noet ts=4:
# This script should be installed as symlinks in
# /etc/udhcpc/<hook>/alpine-ec2-eni-hook
# <hook> :-
# post-bound - after udhcpc binds an IP to the interface
# post-renew - after udhcpc renews the lease for the IP
#
# udhcpc provides via ENV...
# IFACE - eth0, etc.
# mask - bits in IPv4 subnet mask
set -e
HOOK="$(basename "$(dirname "$0")")"
log() {
LEV="$1"
shift
logger -s -p "daemon.$LEV" -t "udhcpc/${HOOK}[$$]" "$@"
}
IMDS="X-aws-ec2-metadata-token"
meta_token() {
echo -ne "PUT /latest/api/token HTTP/1.0\r\n$IMDS-ttl-seconds: 5\r\n\r\n" |
nc 169.254.169.254 80 | tail -n 1
}
meta() {
wget -qO - --header "$IMDS: $(meta_token)" \
"http://169.254.169.254/latest/meta-data/$1" 2>/dev/null \
|| true # when no ipv6s attached, wget will 404 error
}
iface_mac() {
cat "/sys/class/net/$IFACE/address"
}
iface_ip6s() {
# only inet6 lines, except fe80::
ip -6 addr show "$IFACE" |
sed -E -e '/inet6/!d' \
-e '/inet6 fe80::/d' \
-e 's/.*inet6 ([0-9a-f:]+).*/\1/'
}
iface_sec_ip4s() {
# only inet secondary lines
ip -4 addr show "$IFACE" |
sed -E -e '/inet .* secondary/!d' \
-e 's/.*inet ([0-9.]+).*/\1/'
}
ec2_ip6s() {
meta "$MAC_PATH/ipv6s"
}
ec2_sec_ip4s() {
# first one listed is primary
meta "$MAC_PATH/local-ipv4s" | tail +2
}
in_list() {
echo "$2" | grep -q "^$1$"
}
ip_addr() {
[ "$1" -eq 4 ] && MASK="$mask" || MASK=128
MSG="$IFACE $2 $3"
if ip -"$1" addr "$2" "$3/$MASK" dev "$IFACE"; then
log notice "$MSG - success"
else
log err "$MSG - FAILED"
fi
}
case "$HOOK" in
#pre-deconfig) ACT=del ;; # issues with 'service networking restart'
post-bound) ACT=add ;;
post-renew) ACT=sync ;;
*) log err "Unhandled udhcpc hook: '$HOOK'"; exit 1 ;;
esac
MAC_PATH="network/interfaces/macs/$(iface_mac)"
EC2_IP4S="$(ec2_sec_ip4s)"
EC2_IP6S="$(ec2_ip6s)"
IFACE_IP4S="$(iface_sec_ip4s)"
IFACE_IP6S="$(iface_ip6s)"
# add or sync IPs
if [ "$ACT" != "del" ]; then
for ip4 in $EC2_IP4S; do
in_list "$ip4" "$IFACE_IP4S" || ip_addr 4 add "$ip4"
done
for ip6 in $EC2_IP6S; do
in_list "$ip6" "$IFACE_IP6S" || ip_addr 6 add "$ip6"
done
fi
# del or sync IPs
if [ "$ACT" != "add" ]; then
for ip4 in $IFACE_IP4S; do
[ "$ACT" = "sync" ] && in_list "$ip4" "$EC2_IP4S" && continue
ip_addr 4 del "$ip4"
done
for ip6 in $IFACE_IP6S; do
[ "$ACT" = "sync" ] && in_list "$ip6" "$EC2_IP6S" && continue
ip_addr 6 del "$ip6"
done
fi

View File

@ -576,8 +576,9 @@ class ConfigBuilder:
shutil.rmtree(build_dir, ignore_errors=True)
os.makedirs(setup_dir)
# symlink nvme script
# symlink nvme and ENI scripts
self.rel_symlink("scripts/nvme-ebs-links", setup_dir, "nvme-ebs-links")
self.rel_symlink("scripts/alpine-ec2-eni-hook", setup_dir, "alpine-ec2-eni-hook")
# symlink additional setup_script
if "setup_script" in cfg.keys() and cfg["setup_script"] is not None:

View File

@ -242,6 +242,7 @@ EOF
}
setup_networking() {
# configure standard interfaces
cat > "$TARGET/etc/network/interfaces" <<EOF
auto lo
iface lo inet loopback
@ -249,6 +250,15 @@ iface lo inet loopback
auto eth0
iface eth0 inet dhcp
EOF
# install ucdhcp hooks for EC2 ENI IPv6 and secondary IPv4
install -o root -g root -Dm755 -t "$TARGET/usr/share/udhcpc" \
/tmp/setup-ami.d/alpine-ec2-eni-hook
for i in post-bound post-renew; do
mkdir -p "$TARGET/etc/udhcpc/$i"
ln -sf /usr/share/udhcpc/alpine-ec2-eni-hook \
"$TARGET/etc/udhcpc/$i"
done
}
enable_services() {