1#!/bin/bash 2# Copyright 2021 Google LLC 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16[ -n "${network_init-}" ] && return 17 18mac_to_bytes() { 19 local -n bytes="$1" 20 local str="$2" 21 22 # Verify that the MAC is Valid 23 [[ "$str" =~ ^[[:xdigit:]]{1,2}(:[[:xdigit:]]{1,2}){5}$ ]] || return 24 25 # Split the mac into hex bytes 26 local oldifs="$IFS" 27 IFS=: 28 local byte 29 for byte in $str; do 30 bytes+=("0x$byte") 31 done 32 IFS="$oldifs" 33} 34 35mac_to_eui48() { 36 local mac_bytes=(0 0 0 0 0 0 0 0 0 0) 37 mac_to_bytes mac_bytes "$1" || return 38 39 # Return the EUI-64 bytes in the IPv6 format 40 ip_bytes_to_str mac_bytes 41} 42 43mac_to_eui64() { 44 local mac_bytes=() 45 mac_to_bytes mac_bytes "$1" || return 46 47 # Using EUI-64 conversion rules, create the suffix bytes from MAC bytes 48 # Invert bit-1 of the first byte, and insert 0xfffe in the middle. 49 # shellcheck disable=SC2034 50 local suffix_bytes=( 51 0 0 0 0 0 0 0 0 52 $((mac_bytes[0] ^ 2)) 53 "${mac_bytes[@]:1:2}" 54 $((0xff)) $((0xfe)) 55 "${mac_bytes[@]:3:3}" 56 ) 57 58 # Return the EUI-64 bytes in the IPv6 format 59 ip_bytes_to_str suffix_bytes 60} 61 62ip_to_bytes() { 63 local -n bytes_out="$1" 64 local str="$2" 65 66 local bytes=() 67 local oldifs="$IFS" 68 # Heuristic for V4 / V6, validity will be checked as it is parsed 69 if [[ "$str" == *.* ]]; then 70 # Ensure we don't start or end with IFS 71 [ "${str:0:1}" != '.' ] || return 1 72 [ "${str: -1}" != '.' ] || return 1 73 74 local v 75 # Split IPv4 address into octets 76 IFS=. 77 for v in $str; do 78 # IPv4 digits are always decimal numbers 79 if ! [[ "$v" =~ ^[0-9]+$ ]]; then 80 IFS="$oldifs" 81 return 1 82 fi 83 # Each octet is a single byte, make sure the number isn't larger 84 if (( v > 0xff )); then 85 IFS="$oldifs" 86 return 1 87 fi 88 bytes+=("$v") 89 done 90 # IPv4 addresses must have all 4 bytes present 91 if (( "${#bytes[@]}" != 4 )); then 92 IFS="$oldifs" 93 return 1 94 fi 95 else 96 # Ensure we bound the padding in an outer byte for 97 # IFS splitting to work correctly 98 [ "${str:0:2}" = '::' ] && str="0$str" 99 [ "${str: -2}" = '::' ] && str="${str}0" 100 101 # Ensure we don't start or end with IFS 102 [ "${str:0:1}" != ':' ] || return 1 103 [ "${str: -1}" != ':' ] || return 1 104 105 # Stores the bytes that come before ::, if it exists 106 local bytesBeforePad=() 107 local v 108 # Split the Address into hextets 109 IFS=: 110 for v in $str; do 111 # Handle ::, which translates to an empty string 112 if [ -z "$v" ]; then 113 # Only allow a single :: sequence in an address 114 if (( "${#bytesBeforePad[@]}" > 0 )); then 115 IFS="$oldifs" 116 return 1 117 fi 118 # Store the already parsed upper bytes separately 119 # This allows us to calculate and insert padding 120 bytesBeforePad=("${bytes[@]}") 121 bytes=() 122 continue 123 fi 124 # IPv6 digits are always hex 125 if ! [[ "$v" =~ ^[[:xdigit:]]+$ ]]; then 126 IFS="$oldifs" 127 return 1 128 fi 129 # Ensure the number is no larger than a hextet 130 v="0x$v" 131 if (( v > 0xffff )); then 132 IFS="$oldifs" 133 return 1 134 fi 135 # Split the hextet into 2 bytes 136 bytes+=($(( v >> 8 ))) 137 bytes+=($(( v & 0xff ))) 138 done 139 # If we have ::, add padding 140 if (( "${#bytesBeforePad[@]}" > 0 )); then 141 # Fill the middle bytes with padding and store in `bytes` 142 while (( "${#bytes[@]}" + "${#bytesBeforePad[@]}" < 16 )); do 143 bytesBeforePad+=(0) 144 done 145 bytes=("${bytesBeforePad[@]}" "${bytes[@]}") 146 fi 147 # IPv6 addresses must have all 16 bytes present 148 if (( "${#bytes[@]}" != 16 )); then 149 IFS="$oldifs" 150 return 1 151 fi 152 fi 153 154 IFS="$oldifs" 155 # shellcheck disable=SC2034 156 bytes_out=("${bytes[@]}") 157} 158 159ip_bytes_to_str() { 160 # shellcheck disable=SC2178 161 local -n bytes="$1" 162 163 if (( "${#bytes[@]}" == 4 )); then 164 printf '%d.%d.%d.%d\n' "${bytes[@]}" 165 elif (( "${#bytes[@]}" == 16 )); then 166 # Track the starting position of the longest run of 0 hextets (2 bytes) 167 local longest_i=0 168 # Track the size of the longest run of 0 hextets 169 local longest_s=0 170 # The index of the first 0 byte in the current run of zeros 171 local first_zero=0 172 local i 173 # Find the location of the longest run of zero hextets, preferring same 174 # size runs later in the address. 175 for (( i=0; i<=16; i+=2 )); do 176 # Terminate the run of zeros if we are at the end of the array or 177 # have a non-zero hextet 178 if (( i == 16 || bytes[i] != 0 || bytes[$((i+1))] != 0 )); then 179 local s=$((i - first_zero)) 180 if (( s >= longest_s )); then 181 longest_i=$first_zero 182 longest_s=$s 183 fi 184 first_zero=$((i+2)) 185 fi 186 done 187 # Build the address string by each hextet 188 for (( i=0; i<16; i+=2 )); do 189 # If we encountered a run of zeros, add the necessary :: at the end 190 # of the string. If not at the end, a single : is added since : is 191 # printed to subsequent hextets already. 192 if (( i == longest_i )); then 193 (( i += longest_s-2 )) 194 printf ':' 195 # End of string needs to be :: 196 if (( i == 14 )); then 197 printf ':' 198 fi 199 else 200 # Prepend : to all hextets except the first for separation 201 if (( i != 0 )); then 202 printf ':' 203 fi 204 printf '%x' $(( (bytes[i]<<8) | bytes[$((i+1))])) 205 fi 206 done 207 printf '\n' 208 else 209 echo "Invalid IP Bytes: ${bytes[*]}" >&2 210 return 1 211 fi 212} 213 214ip_pfx_concat() { 215 local pfx="$1" 216 local sfx="$2" 217 218 # Parse the prefix 219 if ! [[ "$pfx" =~ ^([0-9a-fA-F:.]+)/([0-9]+)$ ]]; then 220 echo "Invalid IP prefix: $pfx" >&2 221 return 1 222 fi 223 local addr="${BASH_REMATCH[1]}" 224 local cidr="${BASH_REMATCH[2]}" 225 226 # Ensure prefix doesn't have too many bytes 227 local pfx_bytes=() 228 if ! ip_to_bytes pfx_bytes "$addr"; then 229 echo "Invalid IP prefix: $pfx" >&2 230 return 1 231 fi 232 if (( ${#pfx_bytes[@]}*8 < cidr )); then 233 echo "Prefix CIDR too large" >&2 234 return 1 235 fi 236 # CIDR values might partially divide a byte so we need to mask out 237 # only the part of the byte we want to check for emptiness 238 if (( (pfx_bytes[cidr/8] & ~(~0 << (8-cidr%8))) != 0 )); then 239 echo "Invalid byte $((cidr/8)): $pfx" >&2 240 return 1 241 fi 242 local i 243 # Check the rest of the whole bytes to make sure they are empty 244 for (( i=cidr/8+1; i<${#pfx_bytes[@]}; i++ )); do 245 if (( pfx_bytes[i] != 0 )); then 246 echo "Byte $i not 0: $pfx" >&2 247 return 1 248 fi 249 done 250 251 # Validate the suffix 252 local sfx_bytes=() 253 if ! ip_to_bytes sfx_bytes "$sfx"; then 254 echo "Invalid IPv6 suffix: $sfx" >&2 255 return 1 256 fi 257 if (( "${#sfx_bytes[@]}" != "${#pfx_bytes[@]}" )); then 258 echo "Suffix not the same family as prefix: $pfx $sfx" >&2 259 return 1 260 fi 261 # Check potential partially divided bytes for emptiness in the upper part 262 # based on the division specified in CIDR. 263 if (( (sfx_bytes[cidr/8] & (~0 << (8-cidr%8))) != 0 )); then 264 echo "Invalid byte $((cidr/8)): $sfx" >&2 265 return 1 266 fi 267 local i 268 # Check the bytes before the CIDR for emptiness to ensure they don't overlap 269 for (( i=0; i<cidr/8; i++ )); do 270 if (( sfx_bytes[i] != 0 )); then 271 echo "Byte $i not 0: $sfx" >&2 272 return 1 273 fi 274 done 275 276 out_bytes=() 277 for (( i=0; i<${#pfx_bytes[@]}; i++ )); do 278 out_bytes+=($(( pfx_bytes[i] | sfx_bytes[i] ))) 279 done 280 echo "$(ip_bytes_to_str out_bytes)/$cidr" 281} 282 283ip_pfx_to_cidr() { 284 [[ "$1" =~ ^[0-9a-fA-F:.]+/([0-9]+)$ ]] || return 285 echo "${BASH_REMATCH[1]}" 286} 287 288normalize_ip() { 289 # shellcheck disable=SC2034 290 local ip_bytes=() 291 ip_to_bytes ip_bytes "$1" || return 292 ip_bytes_to_str ip_bytes 293} 294 295network_init=1 296