From b4f1e9426b75bf1c10d6e0e109679266f8d4d17a Mon Sep 17 00:00:00 2001 From: Timothe Litt Date: Sat, 15 Feb 2020 15:02:50 -0500 Subject: [PATCH] Add autodetection of MAC address, NTP improvements, oui.txt cleanup 1) use ip link or ifconfig to select a MAC address if not user-specified 2) handle upper-case hex input 3) allow user to specify an NTP server 4) if ntpq fails, try fallback to ntpdate. (Current ntp servers often block 'rv'.commands.) 5) ntp utilities will try all IP addresses assigned to a host. Deal with multiple responses. 6) If oui.txt is downloaded, place it in /tmp and remove it on exit. 7) Fix use of undefined 'date' (instead of 'clock') in GID, use LSB of SHA per RFC 8) Use variables for defaults to simplify customization 9) Default to not prompting; use -i for prompts. 10) Add usage and command line options. 11) Ensure all errors reported to stderr. Fully validate input. 12) Suppress leading zeros in ULA 13) Don't depend on GNU sed's extended REs or GNU echo -ne 14) By default, only output generated ULA. -v for verbose mode. 15) Add option to get (download) a copy of oui.txt Note that ntpq/ntpdate can take a while to get a timestamp. ntpq may timeout, but the script will try to recover using ntpdate. Manually providing a MAC address and/or timestamp should not be necessary. Doing so without understanding the RFC is likely to reduce the uniqueness of the generated address. -i is provided for advanced users. The MAC autoselection uses the first interface with an ethernet address. The address of another interface can be used safely. However, since the purpose is only to seed a hash to identify the network, there is no reason to prefer one over another. The timestamp ensures that a given interface can generate more than one ULA. The only reason to set it manually is deterministic debugging . --- .gitignore | 3 + README.md | 12 +- gen-ula.sh | 390 ++++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 357 insertions(+), 48 deletions(-) mode change 100644 => 100755 gen-ula.sh diff --git a/.gitignore b/.gitignore index 23549e1..c51b6d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ oui.txt +*.bak +*~ +\#* \ No newline at end of file diff --git a/README.md b/README.md index debff8f..d691683 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # Bash ULA Generator Generate an IPv6 Unique Local Address prefix +Timothe Litt - bugfixes and simplified usage +2020-02-03 + Alexandre de Verteuil 2017-06-20 @@ -8,11 +11,15 @@ Based on scripts from **Shinsuke Suzuki** and **Holger Zuleger**, available unde ## Usage -Simply run in a bash shell. You will be prompted for your physical MAC address (guessing "eth0" is no longer relevant in 2017). +Simply run in a bash shell. For additional options, use `-h` ## Requirements -`wget`, `ntpq` +`wget`, `cut`, `tr`, `sed`, `grep`, `tail`, `head`, `sort`, `which` + +## Optional + +`ntpq`, `ntpdate`, `ip`, `ifconfig` ## Improvements over other scripts @@ -23,4 +30,5 @@ Simply run in a bash shell. You will be prompted for your physical MAC address ( ## References - [RFC 4193 — Unique Local IPv6 Unicast Addresses](https://tools.ietf.org/html/rfc4193) + - [RFC 3513 - Internet Protocol Version 6 (IPv6) Addressing Architecture](https://tools.ietf.org/html/rfc3513) - [Unique local address](https://en.wikipedia.org/wiki/Unique_local_address) on Wikipedia diff --git a/gen-ula.sh b/gen-ula.sh old mode 100644 new mode 100755 index 05efbc0..a95c833 --- a/gen-ula.sh +++ b/gen-ula.sh @@ -4,13 +4,32 @@ # Alexandre de Verteuil # 2017-06-20 # +# Timothe Litt +# 2020-02-03 +# - Autodetect hardware address from an interface if not specified +# - Handle upper-case hex input +# - Allow user-specified NTP server +# - If ntpq fails, try ntpdate (many NTP servers block rv commands with restrict noquery). +# - Handle NTP servers with more than one address. +# - Fix use of undefined 'date' (instead of 'clock') in GID, use LSB of SHA per RFC +# - If oui.txt is downloaded, put it in /tmp & remove it on exit +# - Use variables for defaults +# - Default to autodetection, add a -i option to prompt user +# - Add usage and command line options. +# - Ensure all errors reported to stderr. Fully validate input. +# - Suppress leading zeros in ULA +# - Don't depend on GNU sed's extended REs or GNU echo -ne +# - By default, only output generated ULA. -v for verbose mode. +# - Option to get (download) a copy of oui.txt +# # Based on scripts from Shinsuke Suzuki and Holger Zuleger, # available under the prior_art directory. # -# Usage: simply run in a bash shell. You will be prompted for your -# physical MAC address. Guessing eth0 is no longer relevant in 2017. +# Usage: simply run in a bash shell. +# use -h for complete usage information. # -# Requirements: wget, ntpq +# Requirements: wget, cut, tr, sed, grep, tail, head, sort, which +# Optional: ntpq, ntpdate, ip, ifconfig # # Improvements over other scripts: # - Not a CGI script @@ -22,48 +41,315 @@ # RFC 4193 -- Unique Local IPv6 Unicast Addresses # https://tools.ietf.org/html/rfc4193 # +# RFC 3513 -- Internet Protocol Version 6 (IPv6) Addressing Architecture +# https://tools.ietf.org/html/3513 +# # Unique local address # https://en.wikipedia.org/wiki/Unique_local_address +LC_ALL=C +export LC_ALL + +# Default NTP server +NTP_server="0.pool.ntp.org" + +# Source for oui.txt (MAC issuers) +[ -z "$OUI_URI" ] && OUI_URI="http://standards-oui.ieee.org/oui.txt" +# +# Default placement of oui.txt +# - Will look for it here first +OUI_TXT_dir="." +# - Will put a temporary copy here if not found in OUI_TXT_dir +[ -z "$TMPDIR" ] && TMPDIR="/tmp" + +# Prevent inherited variables from influencing command + +GET_oui= +INTER= +mac= +clock= +VERB= +OUI_UID= + +# Report error and exit function die() { - echo - echo "== Error ==" - echo "$@" + cat <&2 + +== Error == +$@ +EOF exit 1 } -read -p "MAC address: " mac -mac=$(echo $mac | tr -d :-) -if [ ${#mac} -ne 12 ]; then - die "MAC address \"${mac}\" is invalid" +# ifconfig, ip are usually in /usr/sbin. Ensure that at least one of +# the various sbins is in PATH. If not, put at end so as not to +# replace anything in the current PATH. + +if ! echo "$PATH" | grep -q '/sbin'; then + PATH="$PATH:/usr/local/sbin:/usr/sbin:/sbin" + export PATH fi -echo "For a deterministic calculation, you may enter the ntp clock time." -echo "Leave empty to query an NTP server." -read -p "Clock: " clock +# Produce usage & exit + +function usage() { + local prog="`basename $0 .sh`" + + cat < to detect): " mac +if [ -z "$mac" ]; then + # Use first interface with a hardware address + + if which ip >/dev/null 2>&1 ; then + mac="`ip link show | sed -n -e'/link\/ether /!d;s|^.*link/ether \([0-9a-f:-]*\).*$|\1|p' | head -n1 | tr -d '\n:'`" + elif which ifconfig >/dev/null 2>&1 ; then + # ifconfig output varies + # Caution: [] contains space && tab + mac="`ifconfig | sed -n -e'/[ ]\(ether\|HWaddr\) /!d;s,^.*\(ether\|HWaddr\) \([0-9a-fA-F:-]*\).*$,\2,p' | head -n1 | tr -d '\n:'`" + else + die "Neither 'ip' nor 'ifconfig' found on $PATH." + fi +fi + +# Remove any delimiters from MAC address and force lower-case hex + +mac="$(echo "$mac" | tr 'A-F' 'a-f' | tr -d '\n:.-')" +checkhex "$mac" 12 "MAC address" + +# Prompt for time source (or time) if interactive and not specified + +if [ -n "$INTER" -a -z "$clock" ]; then + cat < to use $NTP_server, or + - Type an NTP server name, or + - For a deterministic result, type "#high.low", where "high" and "low" are 8 hex digits. +EOF + read -p "Clock: " clock +fi +if [ "${clock:0:1}" = "#" ]; then + clock="${clock:1}" +else + [ -n "$clock" ] && NTP_server="$clock" + clock= +fi if [ -z "${clock}" ]; then - clock=$(ntpq -c "rv 0 clock" 0.pool.ntp.org | cut -c7-23) - # Input: "clock=dcf4268b.208dd000 Tue, Jun 20 2017 18:56:11.127" - # Output: "dcf4268b.208dd000" + if [ -n "$VERB" ]; then + echo "Obtaining time from $NTP_server. This may take a while." + echo "Note: \"timed-out\" messages are non-fatal." + exec 99>&2 + else + exec 99>/dev/null + fi + # A server with multiple addresses may return multiple results. Use only the last. + # ntpq "rv" is most efficient, but many servers now block. Fall back to ntpdate. + + if now="$(2>&99 ntpq -c 'rv 0 clock' "$NTP_server" | grep 'clock=')"; then + clock="$(echo "$now" | tail -n 1 | cut -c7-23)" + # Input: "clock=dcf4268b.208dd000 Tue, Jun 20 2017 18:56:11.127" + # Output: "dcf4268b.208dd000" + elif now="$(ntpdate -d -q "$NTP_server" 2>&99 )" ; then + clock="$(echo "$now" | sed -n -e'/^transmit timestamp:/!d;s/^transmit timestamp: *\([0-9a-fA-F][0-9a-fA-F.]*\) .*/\1/p' | tail -n 1 | tr -d '.')" + else + 99>&- + die "Unable to contact $NTP_server" + fi + 99>&- fi clock=$(tr -d . <<< "${clock}") -if [ "${#clock}" -ne 16 ]; then - die "Time in NTP format is 64 bits, "\ - "or 16 characters in hex representation. "\ - "You entered: \"${clock}\"." -fi +checkhex "$clock" 16 "NTP format time (hex)" +# OUI check. If oui.txt exists, leave it alone. Otherwise fetch a temporary copy -if [ ! -r "oui.txt" ]; then - wget "http://standards-oui.ieee.org/oui.txt" +if [ ! -r "$OUI_TXT_dir/oui.txt" ]; then + OUI_TXT_dir="$TMPDIR" + OUI_UID=".$$" + trap "rm -f $OUI_TXT_dir/oui${OUI_UID}.txt" INT TERM EXIT + if [ -n "$VERB" ]; then + echo "Fetching OUI data from $OUI_URI" + fi + if ! wget -O "$OUI_TXT_dir/oui${OUI_UID}.txt" -q "$OUI_URI" || [ ! -r "$OUI_TXT_dir/oui.txt" ]; then + die "Failed to download $OUI_URI" + fi fi # MAC Vendor check -machexvendor=$(tr a-f A-F <<< "${mac:0:6}") -macvendor=$(grep "^$machexvendor" oui.txt | sed -r "s/.*\t([^\r\n]*).*/\1/") + +machexvendor="$(tr 'a-f' 'A-F' <<< "${mac:0:6}")" +macvendor="$(grep "^$machexvendor" "$OUI_TXT_dir/oui${OUI_UID}.txt" | sed -e "s/.*\t\([^\r\n]*\).*/\1/")" if [ -z "$macvendor" ]; then - die "MAC address \"${mac}\" is not registered to IEEE. "\ + die "MAC address \"${mac}\" is not registered by IEEE. "\ "Please use a REAL MAC address." fi @@ -71,15 +357,15 @@ fi # as described in RFC 3513 # https://tools.ietf.org/html/rfc3513 -first=`echo $mac | cut -c1-1` -second=`echo $mac | cut -c2-2` -macu=`echo $mac | cut -c3-6` -macl=`echo $mac | cut -c7-12` +first="`echo "$mac" | cut -c1-1`" +second="`echo "$mac" | cut -c2-2`" +macu="`echo "$mac" | cut -c3-6`" +macl="`echo "$mac" | cut -c7-12`" # reversing u/l bit case $second in [13579bdf]) - die "MAC-address \"${mac}\" is a group MAC address" + die "MAC-address \"${mac}\" is a group address" ;; 0) second_rev=2 @@ -106,8 +392,8 @@ case $second in second_rev=c; ;; *) - #impossible - die "MAC address \"${mac}\" is registered to the IEEE database, "\ + # impossible - non-hex, found in oui.txt + die "MAC address \"${mac}\" is registered to \"${macvendor}\" in the IEEE database, "\ "but the first octet (${first}${second}) is regarded as invalid. "\ "(probably a bug in this script...)" esac @@ -115,17 +401,29 @@ eui64="${first}${second_rev}${macu}fffe${macl}" # Convert from hex string representation to bytes before sha1sum # https://unix.stackexchange.com/a/82766 -globalid=$(echo -ne $(sed "s/../\\x&/g" <<< ${date}${eui64}) | sha1sum | cut -c23-32) -ula=$(echo fd${globalid} | sed "s|\(....\)\(....\)\(....\)|\1:\2:\3::/48|") - -echo -echo "## Inputs ##" -echo "MAC address = ${mac} (${macvendor})" -echo "NTP time = ${clock}" -echo -echo "## Intermediary values ##" -echo "EUI64 address = ${eui64}" -echo -echo "## Generated ULA ##" +globalid="$(printf "$(sed -e's/../\\x&/g' <<< "${clock}${eui64}")" | sha1sum -b | cut -c31-40)" + +# Format resulting ULA as an IPv6 address. fd00::/8 designates a ULA (prefix fc00/7) with L=1.. +# GlobalID is 40 bits. (last 10 (hex) chars of SHA). Total of 48 bits assigned here. +# Subnet(16) and interface(64) bits are 0 (for user to assign). +ula="$(echo "fd${globalid}" | sed -e "s|\(....\)\(....\)\(....\)|\1:\2:\3:|; s/:00*/:/g; s/::/:0:/; s/::/:0:/")" +ula="$ula:/48" + +# Results + +if [ -n "$VERB" ]; then + cat <