Created
January 2, 2021 01:46
-
-
Save iam-TJ/ff78bdce304c7e3ad86812e0cfac50bf to your computer and use it in GitHub Desktop.
Deterministically calculates an IPv6 Unique Local Address (ULA) prefix
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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