Skip to content

Instantly share code, notes, and snippets.

@iam-TJ
Created January 2, 2021 01:46
Show Gist options
  • Save iam-TJ/ff78bdce304c7e3ad86812e0cfac50bf to your computer and use it in GitHub Desktop.
Save iam-TJ/ff78bdce304c7e3ad86812e0cfac50bf to your computer and use it in GitHub Desktop.
Deterministically calculates an IPv6 Unique Local Address (ULA) prefix
#!/usr/bin/env bash
# ipv6-ula-gen
# Copyright January 2021 Tj <hacker@iam.tj>
# Licensed on the terms of the GNU General Public Licence version 3
#
# Deterministically calculates an IPv6 Unique Local Address (ULA) prefix
#
# Useful in test harnesses and reproducible build scenarios, and for repeatedly
# generating/recreating the same prefix at different times. The latter is useful
# in scripts that need to generate complete ULAs for hosts as in the example below.
#
# It uses the unix time epoch and either:
# 1) MAC address of the first link/ether interface
# 2) user provided MAC address "mac=01:23:45:67:89:ab ipv6-ula-gen"
#
# output is in the form of key=value which can be consumed by 'eval' to set environment variables:
#
# eval $( mac=01:23:45:67:89:ab ipv6-ula-gen )
# our_subnet="01:02:"
# ip addr add ${ULA_PREFIX}${our_subnet}${eui64}/64 dev eth0
# all intermediate values are reported to allow validation of each step
set +x
if ! which gawk >/dev/null; then
echo "error: requires 'gawk' which has patsplit() and strtonum()"
exit 11
elif ! which sha1sum >/dev/null; then
echo "error: requires 'sha1sum'"
exit 12
elif ! which sed >/dev/null; then
echo "error: requires 'sed'"
exit 13
elif ! which ip >/dev/null; then
echo "error: requires 'ip' from iproute2"
exit 14
elif ip link help 2>/dev/null; then
echo "error: requires 'ip link' from iproute2"
exit 15
fi
# use a fixed timestamp to enable reproducible builds
date_text="1970-01-01T00:00:00"
date_unix="0"
# 1970-01-01 00:00:00 NTP timestamp vint64 seconds since start of era 0 (2208988800)
unix_epoch_ntp64="83aa7e8000000000"
# inserted into an Ethernet MAC address
ether_eui_midfix="ff:fe:"
prefix="fd"
# adopt enviroment-set value if present
mac=${mac:-}
if [[ -z $mac ]]; then
# get MAC and name of first Ethernet interface that is 'up' and assign to variables 'mac' and 'ifname'
eval "$(ip link show up | gawk '/link\/ether/ {print "ifname=" IF "\n" "mac=" $2; exit} {IF=substr($2,1,length($2)-1)}')"
else
ifname="unknown"
fi
if [[ -z $mac ]] || [[ -z $ifname ]]; then
# ask the operator!
ifname="unknown"
read -p "Please enter an Ethernet MAC address (aa:bb:cc:dd:ee:ff) " mac
fi
# check format of MAC address
gawk -v mac=$mac -f - <<-EOF
BEGIN {
c=patsplit(mac,hex,/[^:]*/,s);
if(c != 6) {
print "error: octets!=6";
exit 1;
};
for(i=1;i<=6;++i) {
#print i,hex[i],s[i];
if(length(hex[i]) != 2) {
print "error: value length",i,hex[i];
exit 2;
};
if(strtonum("0x01" hex[i]) < 256) {
print "error: hex?", hex[i];
exit 3;
}
}
}
EOF
if [[ $? -ne 0 ]] || [[ ${#mac} -ne 17 ]]; then
echo "error: MAC address format incorrect"
exit 1
fi
# convert to EUI-64
eui64="${mac:0:9}${ether_eui_midfix}${mac:9}"
# combine whilst removing colons to the hexadecimal representation of a 128-bit value
rfc4193_key="${unix_epoch_ntp64}${eui64//:/}"
# calculate the SHA-1 digest
sha1="$(echo ${rfc4193_key} | sed 's/../\\x&/g' | sha1sum)"
# example sha1 content: "206b88b161bc6c60b5477867a7b5d659dbad2594 -"
# that's 160 bits (160/8 = 20 bytes = 40 hexadecimal digits)
# now extract the least significant 40 bits (40/8 = 5 bytes = 10 hexadecimal digits)
global_id="${sha1:30:10}"
ula_prefix="${prefix}${global_id}"
# print as key=value so output can be consumed by 'eval' to set env-vars
printf "date_text=%s\ndate_unix=%s\ndate_ntp64=%s\nifname=%s\nmac=%s\neui64=%s\nrfc4193_key=%s\nsha1=\"%s\"\nglobal_id=%s\nula_prefix=%s\n" \
$date_text $date_unix $unix_epoch_ntp64 $ifname $mac $eui64 $rfc4193_key "$sha1" $global_id $ula_prefix
echo "ULA_PREFIX=$(echo $ula_prefix | sed 's/../&:/g')"
echo "ULA_SUBNET=$(echo $ula_prefix | sed 's/../&:/g'):/48"
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment