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