blob: 3e32db0833f2b284bab40124dd2d3e694d03c3c7 [file] [log] [blame]
William A. Kennington III1e268102021-03-08 13:00:12 -08001#!/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
William A. Kennington III03206952023-06-05 14:16:02 -070030 bytes+=("0x$byte")
William A. Kennington III1e268102021-03-08 13:00:12 -080031 done
32 IFS="$oldifs"
33}
34
35mac_to_eui48() {
William A. Kennington III6ca70332021-05-10 03:14:42 -070036 local mac_bytes=(0 0 0 0 0 0 0 0 0 0)
William A. Kennington III1e268102021-03-08 13:00:12 -080037 mac_to_bytes mac_bytes "$1" || return
38
39 # Return the EUI-64 bytes in the IPv6 format
William A. Kennington III6ca70332021-05-10 03:14:42 -070040 ip_bytes_to_str mac_bytes
William A. Kennington III1e268102021-03-08 13:00:12 -080041}
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
William A. Kennington IIIfc8c4362022-07-20 12:35:50 -070048 # Invert bit-1 of the first byte, and insert 0xfffe in the middle.
William A. Kennington III03206952023-06-05 14:16:02 -070049 # shellcheck disable=SC2034
William A. Kennington III1e268102021-03-08 13:00:12 -080050 local suffix_bytes=(
William A. Kennington III6ca70332021-05-10 03:14:42 -070051 0 0 0 0 0 0 0 0
William A. Kennington IIIfc8c4362022-07-20 12:35:50 -070052 $((mac_bytes[0] ^ 2))
William A. Kennington III03206952023-06-05 14:16:02 -070053 "${mac_bytes[@]:1:2}"
William A. Kennington III1e268102021-03-08 13:00:12 -080054 $((0xff)) $((0xfe))
William A. Kennington III03206952023-06-05 14:16:02 -070055 "${mac_bytes[@]:3:3}"
William A. Kennington III1e268102021-03-08 13:00:12 -080056 )
57
58 # Return the EUI-64 bytes in the IPv6 format
William A. Kennington III6ca70332021-05-10 03:14:42 -070059 ip_bytes_to_str suffix_bytes
William A. Kennington III1e268102021-03-08 13:00:12 -080060}
61
William A. Kennington III70264b92021-05-07 03:07:31 -070062ip_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
William A. Kennington III03206952023-06-05 14:16:02 -070088 bytes+=("$v")
William A. Kennington III70264b92021-05-07 03:07:31 -070089 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"
William A. Kennington III03206952023-06-05 14:16:02 -0700155 # shellcheck disable=SC2034
William A. Kennington III70264b92021-05-07 03:07:31 -0700156 bytes_out=("${bytes[@]}")
157}
158
William A. Kennington III80776782021-05-10 03:07:14 -0700159ip_bytes_to_str() {
William A. Kennington III03206952023-06-05 14:16:02 -0700160 # shellcheck disable=SC2178
William A. Kennington III80776782021-05-10 03:07:14 -0700161 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
William A. Kennington III03206952023-06-05 14:16:02 -0700178 if (( i == 16 || bytes[i] != 0 || bytes[$((i+1))] != 0 )); then
William A. Kennington III80776782021-05-10 03:07:14 -0700179 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
William A. Kennington III03206952023-06-05 14:16:02 -0700204 printf '%x' $(( (bytes[i]<<8) | bytes[$((i+1))]))
William A. Kennington III80776782021-05-10 03:07:14 -0700205 fi
206 done
207 printf '\n'
208 else
209 echo "Invalid IP Bytes: ${bytes[*]}" >&2
210 return 1
211 fi
212}
213
William A. Kennington III6ca70332021-05-10 03:14:42 -0700214ip_pfx_concat() {
William A. Kennington III1e268102021-03-08 13:00:12 -0800215 local pfx="$1"
216 local sfx="$2"
217
William A. Kennington III6ca70332021-05-10 03:14:42 -0700218 # Parse the prefix
219 if ! [[ "$pfx" =~ ^([0-9a-fA-F:.]+)/([0-9]+)$ ]]; then
220 echo "Invalid IP prefix: $pfx" >&2
William A. Kennington III1e268102021-03-08 13:00:12 -0800221 return 1
222 fi
223 local addr="${BASH_REMATCH[1]}"
William A. Kennington III6ca70332021-05-10 03:14:42 -0700224 local cidr="${BASH_REMATCH[2]}"
225
William A. Kennington III1e268102021-03-08 13:00:12 -0800226 # Ensure prefix doesn't have too many bytes
William A. Kennington III6ca70332021-05-10 03:14:42 -0700227 local pfx_bytes=()
228 if ! ip_to_bytes pfx_bytes "$addr"; then
229 echo "Invalid IP prefix: $pfx" >&2
William A. Kennington III1e268102021-03-08 13:00:12 -0800230 return 1
231 fi
William A. Kennington III6ca70332021-05-10 03:14:42 -0700232 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
William A. Kennington III03206952023-06-05 14:16:02 -0700245 if (( pfx_bytes[i] != 0 )); then
William A. Kennington III6ca70332021-05-10 03:14:42 -0700246 echo "Byte $i not 0: $pfx" >&2
247 return 1
248 fi
249 done
William A. Kennington III1e268102021-03-08 13:00:12 -0800250
251 # Validate the suffix
William A. Kennington III6ca70332021-05-10 03:14:42 -0700252 local sfx_bytes=()
253 if ! ip_to_bytes sfx_bytes "$sfx"; then
William A. Kennington III1e268102021-03-08 13:00:12 -0800254 echo "Invalid IPv6 suffix: $sfx" >&2
255 return 1
256 fi
William A. Kennington III6ca70332021-05-10 03:14:42 -0700257 if (( "${#sfx_bytes[@]}" != "${#pfx_bytes[@]}" )); then
258 echo "Suffix not the same family as prefix: $pfx $sfx" >&2
William A. Kennington III1e268102021-03-08 13:00:12 -0800259 return 1
260 fi
William A. Kennington III6ca70332021-05-10 03:14:42 -0700261 # 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
William A. Kennington III1e268102021-03-08 13:00:12 -0800266 fi
William A. Kennington III6ca70332021-05-10 03:14:42 -0700267 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
William A. Kennington III03206952023-06-05 14:16:02 -0700270 if (( sfx_bytes[i] != 0 )); then
William A. Kennington III6ca70332021-05-10 03:14:42 -0700271 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
William A. Kennington III03206952023-06-05 14:16:02 -0700278 out_bytes+=($(( pfx_bytes[i] | sfx_bytes[i] )))
William A. Kennington III6ca70332021-05-10 03:14:42 -0700279 done
280 echo "$(ip_bytes_to_str out_bytes)/$cidr"
William A. Kennington III1e268102021-03-08 13:00:12 -0800281}
282
William A. Kennington III6ca70332021-05-10 03:14:42 -0700283ip_pfx_to_cidr() {
284 [[ "$1" =~ ^[0-9a-fA-F:.]+/([0-9]+)$ ]] || return
William A. Kennington III1e268102021-03-08 13:00:12 -0800285 echo "${BASH_REMATCH[1]}"
286}
287
William A. Kennington III6ca70332021-05-10 03:14:42 -0700288normalize_ip() {
William A. Kennington III03206952023-06-05 14:16:02 -0700289 # shellcheck disable=SC2034
William A. Kennington III6ca70332021-05-10 03:14:42 -0700290 local ip_bytes=()
291 ip_to_bytes ip_bytes "$1" || return
292 ip_bytes_to_str ip_bytes
293}
294
William A. Kennington III1e268102021-03-08 13:00:12 -0800295network_init=1