blob: b5d9382fcb47216996e581dc1887e88d7c92fee4 [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
30 bytes+=(0x$byte)
31 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
48 # Invert bit-0 of the first byte, and insert 0xfffe in the middle.
49 local suffix_bytes=(
William A. Kennington III6ca70332021-05-10 03:14:42 -070050 0 0 0 0 0 0 0 0
William A. Kennington III1e268102021-03-08 13:00:12 -080051 $((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
William A. Kennington III6ca70332021-05-10 03:14:42 -070058 ip_bytes_to_str suffix_bytes
William A. Kennington III1e268102021-03-08 13:00:12 -080059}
60
William A. Kennington III70264b92021-05-07 03:07:31 -070061ip_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
William A. Kennington III80776782021-05-10 03:07:14 -0700157ip_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
William A. Kennington III6ca70332021-05-10 03:14:42 -0700211ip_pfx_concat() {
William A. Kennington III1e268102021-03-08 13:00:12 -0800212 local pfx="$1"
213 local sfx="$2"
214
William A. Kennington III6ca70332021-05-10 03:14:42 -0700215 # Parse the prefix
216 if ! [[ "$pfx" =~ ^([0-9a-fA-F:.]+)/([0-9]+)$ ]]; then
217 echo "Invalid IP prefix: $pfx" >&2
William A. Kennington III1e268102021-03-08 13:00:12 -0800218 return 1
219 fi
220 local addr="${BASH_REMATCH[1]}"
William A. Kennington III6ca70332021-05-10 03:14:42 -0700221 local cidr="${BASH_REMATCH[2]}"
222
William A. Kennington III1e268102021-03-08 13:00:12 -0800223 # Ensure prefix doesn't have too many bytes
William A. Kennington III6ca70332021-05-10 03:14:42 -0700224 local pfx_bytes=()
225 if ! ip_to_bytes pfx_bytes "$addr"; then
226 echo "Invalid IP prefix: $pfx" >&2
William A. Kennington III1e268102021-03-08 13:00:12 -0800227 return 1
228 fi
William A. Kennington III6ca70332021-05-10 03:14:42 -0700229 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
William A. Kennington III1e268102021-03-08 13:00:12 -0800247
248 # Validate the suffix
William A. Kennington III6ca70332021-05-10 03:14:42 -0700249 local sfx_bytes=()
250 if ! ip_to_bytes sfx_bytes "$sfx"; then
William A. Kennington III1e268102021-03-08 13:00:12 -0800251 echo "Invalid IPv6 suffix: $sfx" >&2
252 return 1
253 fi
William A. Kennington III6ca70332021-05-10 03:14:42 -0700254 if (( "${#sfx_bytes[@]}" != "${#pfx_bytes[@]}" )); then
255 echo "Suffix not the same family as prefix: $pfx $sfx" >&2
William A. Kennington III1e268102021-03-08 13:00:12 -0800256 return 1
257 fi
William A. Kennington III6ca70332021-05-10 03:14:42 -0700258 # 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
William A. Kennington III1e268102021-03-08 13:00:12 -0800263 fi
William A. Kennington III6ca70332021-05-10 03:14:42 -0700264 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"
William A. Kennington III1e268102021-03-08 13:00:12 -0800278}
279
William A. Kennington III6ca70332021-05-10 03:14:42 -0700280ip_pfx_to_cidr() {
281 [[ "$1" =~ ^[0-9a-fA-F:.]+/([0-9]+)$ ]] || return
William A. Kennington III1e268102021-03-08 13:00:12 -0800282 echo "${BASH_REMATCH[1]}"
283}
284
William A. Kennington III6ca70332021-05-10 03:14:42 -0700285normalize_ip() {
286 local ip_bytes=()
287 ip_to_bytes ip_bytes "$1" || return
288 ip_bytes_to_str ip_bytes
289}
290
William A. Kennington III1e268102021-03-08 13:00:12 -0800291network_init=1
292return 0 2>/dev/null
293echo "network is a library, not executed directly" >&2
294exit 1