diff options
Diffstat (limited to 'meta-google/recipes-google/networking/network-sh/lib.sh')
-rw-r--r-- | meta-google/recipes-google/networking/network-sh/lib.sh | 236 |
1 files changed, 212 insertions, 24 deletions
diff --git a/meta-google/recipes-google/networking/network-sh/lib.sh b/meta-google/recipes-google/networking/network-sh/lib.sh index f37f7196d..b5d9382fc 100644 --- a/meta-google/recipes-google/networking/network-sh/lib.sh +++ b/meta-google/recipes-google/networking/network-sh/lib.sh @@ -33,11 +33,11 @@ mac_to_bytes() { } mac_to_eui48() { - local mac_bytes=() + 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 - printf '%02x%02x:%02x%02x:%02x%02x\n' "${mac_bytes[@]}" + ip_bytes_to_str mac_bytes } mac_to_eui64() { @@ -47,6 +47,7 @@ mac_to_eui64() { # Using EUI-64 conversion rules, create the suffix bytes from MAC bytes # Invert bit-0 of the first byte, and insert 0xfffe in the middle. local suffix_bytes=( + 0 0 0 0 0 0 0 0 $((mac_bytes[0] ^ 1)) ${mac_bytes[@]:1:2} $((0xff)) $((0xfe)) @@ -54,52 +55,239 @@ mac_to_eui64() { ) # Return the EUI-64 bytes in the IPv6 format - printf '%02x%02x:%02x%02x:%02x%02x:%02x%02x\n' "${suffix_bytes[@]}" + 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" + bytes_out=("${bytes[@]}") } -ipv6_pfx_concat() { +ip_bytes_to_str() { + 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" - # Validate the prefix - if ! [[ "$pfx" =~ ^(([0-9a-fA-F]{1,4}:)+):/([0-9]+)$ ]]; then - echo "Invalid IPv6 prefix: $pfx" >&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[3]}" + local cidr="${BASH_REMATCH[2]}" + # Ensure prefix doesn't have too many bytes - local nos="${addr//:/}" - if (( ${#addr} - ${#nos} > (cidr+7)/16 )); then - echo "Too many prefix bytes: $pfx" >&2 + 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 - if ! [[ "$sfx" =~ ^[0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4})*$ ]]; then + local sfx_bytes=() + if ! ip_to_bytes sfx_bytes "$sfx"; then echo "Invalid IPv6 suffix: $sfx" >&2 return 1 fi - # Ensure suffix doesn't have too many bytes - local nos="${sfx//:/}" - if (( ${#sfx} - ${#nos} >= (128-cidr)/16 )); then - echo "Too many suffix bytes: $sfx" >&2 + if (( "${#sfx_bytes[@]}" != "${#pfx_bytes[@]}" )); then + echo "Suffix not the same family as prefix: $pfx $sfx" >&2 return 1 fi - - local comb="$addr:$sfx" - local nos="${comb//:/}" - if (( ${#comb} - ${#nos} == 8 )); then - comb="$addr$sfx" + # 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 - echo "$comb/$cidr" + 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" } -ipv6_pfx_to_cidr() { - [[ "$1" =~ ^[0-9a-fA-F:]+/([0-9]+)$ ]] || return +ip_pfx_to_cidr() { + [[ "$1" =~ ^[0-9a-fA-F:.]+/([0-9]+)$ ]] || return echo "${BASH_REMATCH[1]}" } +normalize_ip() { + local ip_bytes=() + ip_to_bytes ip_bytes "$1" || return + ip_bytes_to_str ip_bytes +} + network_init=1 return 0 2>/dev/null echo "network is a library, not executed directly" >&2 |