blob: 04e1480d731ac5e8df67a157b97529324ae4b48e [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() {
36 local mac_bytes=()
37 mac_to_bytes mac_bytes "$1" || return
38
39 # Return the EUI-64 bytes in the IPv6 format
40 printf '%02x%02x:%02x%02x:%02x%02x\n' "${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 $((mac_bytes[0] ^ 1))
51 ${mac_bytes[@]:1:2}
52 $((0xff)) $((0xfe))
53 ${mac_bytes[@]:3:3}
54 )
55
56 # Return the EUI-64 bytes in the IPv6 format
57 printf '%02x%02x:%02x%02x:%02x%02x:%02x%02x\n' "${suffix_bytes[@]}"
58}
59
William A. Kennington III70264b92021-05-07 03:07:31 -070060ip_to_bytes() {
61 local -n bytes_out="$1"
62 local str="$2"
63
64 local bytes=()
65 local oldifs="$IFS"
66 # Heuristic for V4 / V6, validity will be checked as it is parsed
67 if [[ "$str" == *.* ]]; then
68 # Ensure we don't start or end with IFS
69 [ "${str:0:1}" != '.' ] || return 1
70 [ "${str: -1}" != '.' ] || return 1
71
72 local v
73 # Split IPv4 address into octets
74 IFS=.
75 for v in $str; do
76 # IPv4 digits are always decimal numbers
77 if ! [[ "$v" =~ ^[0-9]+$ ]]; then
78 IFS="$oldifs"
79 return 1
80 fi
81 # Each octet is a single byte, make sure the number isn't larger
82 if (( v > 0xff )); then
83 IFS="$oldifs"
84 return 1
85 fi
86 bytes+=($v)
87 done
88 # IPv4 addresses must have all 4 bytes present
89 if (( "${#bytes[@]}" != 4 )); then
90 IFS="$oldifs"
91 return 1
92 fi
93 else
94 # Ensure we bound the padding in an outer byte for
95 # IFS splitting to work correctly
96 [ "${str:0:2}" = '::' ] && str="0$str"
97 [ "${str: -2}" = '::' ] && str="${str}0"
98
99 # Ensure we don't start or end with IFS
100 [ "${str:0:1}" != ':' ] || return 1
101 [ "${str: -1}" != ':' ] || return 1
102
103 # Stores the bytes that come before ::, if it exists
104 local bytesBeforePad=()
105 local v
106 # Split the Address into hextets
107 IFS=:
108 for v in $str; do
109 # Handle ::, which translates to an empty string
110 if [ -z "$v" ]; then
111 # Only allow a single :: sequence in an address
112 if (( "${#bytesBeforePad[@]}" > 0 )); then
113 IFS="$oldifs"
114 return 1
115 fi
116 # Store the already parsed upper bytes separately
117 # This allows us to calculate and insert padding
118 bytesBeforePad=("${bytes[@]}")
119 bytes=()
120 continue
121 fi
122 # IPv6 digits are always hex
123 if ! [[ "$v" =~ ^[[:xdigit:]]+$ ]]; then
124 IFS="$oldifs"
125 return 1
126 fi
127 # Ensure the number is no larger than a hextet
128 v="0x$v"
129 if (( v > 0xffff )); then
130 IFS="$oldifs"
131 return 1
132 fi
133 # Split the hextet into 2 bytes
134 bytes+=($(( v >> 8 )))
135 bytes+=($(( v & 0xff )))
136 done
137 # If we have ::, add padding
138 if (( "${#bytesBeforePad[@]}" > 0 )); then
139 # Fill the middle bytes with padding and store in `bytes`
140 while (( "${#bytes[@]}" + "${#bytesBeforePad[@]}" < 16 )); do
141 bytesBeforePad+=(0)
142 done
143 bytes=("${bytesBeforePad[@]}" "${bytes[@]}")
144 fi
145 # IPv6 addresses must have all 16 bytes present
146 if (( "${#bytes[@]}" != 16 )); then
147 IFS="$oldifs"
148 return 1
149 fi
150 fi
151
152 IFS="$oldifs"
153 bytes_out=("${bytes[@]}")
154}
155
William A. Kennington III80776782021-05-10 03:07:14 -0700156ip_bytes_to_str() {
157 local -n bytes="$1"
158
159 if (( "${#bytes[@]}" == 4 )); then
160 printf '%d.%d.%d.%d\n' "${bytes[@]}"
161 elif (( "${#bytes[@]}" == 16 )); then
162 # Track the starting position of the longest run of 0 hextets (2 bytes)
163 local longest_i=0
164 # Track the size of the longest run of 0 hextets
165 local longest_s=0
166 # The index of the first 0 byte in the current run of zeros
167 local first_zero=0
168 local i
169 # Find the location of the longest run of zero hextets, preferring same
170 # size runs later in the address.
171 for (( i=0; i<=16; i+=2 )); do
172 # Terminate the run of zeros if we are at the end of the array or
173 # have a non-zero hextet
174 if (( i == 16 || bytes[$i] != 0 || bytes[$((i+1))] != 0 )); then
175 local s=$((i - first_zero))
176 if (( s >= longest_s )); then
177 longest_i=$first_zero
178 longest_s=$s
179 fi
180 first_zero=$((i+2))
181 fi
182 done
183 # Build the address string by each hextet
184 for (( i=0; i<16; i+=2 )); do
185 # If we encountered a run of zeros, add the necessary :: at the end
186 # of the string. If not at the end, a single : is added since : is
187 # printed to subsequent hextets already.
188 if (( i == longest_i )); then
189 (( i += longest_s-2 ))
190 printf ':'
191 # End of string needs to be ::
192 if (( i == 14 )); then
193 printf ':'
194 fi
195 else
196 # Prepend : to all hextets except the first for separation
197 if (( i != 0 )); then
198 printf ':'
199 fi
200 printf '%x' $(( (bytes[$i]<<8) | bytes[$(($i+1))]))
201 fi
202 done
203 printf '\n'
204 else
205 echo "Invalid IP Bytes: ${bytes[*]}" >&2
206 return 1
207 fi
208}
209
William A. Kennington III1e268102021-03-08 13:00:12 -0800210ipv6_pfx_concat() {
211 local pfx="$1"
212 local sfx="$2"
213
214 # Validate the prefix
215 if ! [[ "$pfx" =~ ^(([0-9a-fA-F]{1,4}:)+):/([0-9]+)$ ]]; then
216 echo "Invalid IPv6 prefix: $pfx" >&2
217 return 1
218 fi
219 local addr="${BASH_REMATCH[1]}"
220 local cidr="${BASH_REMATCH[3]}"
221 # Ensure prefix doesn't have too many bytes
222 local nos="${addr//:/}"
223 if (( ${#addr} - ${#nos} > (cidr+7)/16 )); then
224 echo "Too many prefix bytes: $pfx" >&2
225 return 1
226 fi
227
228 # Validate the suffix
229 if ! [[ "$sfx" =~ ^[0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4})*$ ]]; then
230 echo "Invalid IPv6 suffix: $sfx" >&2
231 return 1
232 fi
233 # Ensure suffix doesn't have too many bytes
234 local nos="${sfx//:/}"
235 if (( ${#sfx} - ${#nos} >= (128-cidr)/16 )); then
236 echo "Too many suffix bytes: $sfx" >&2
237 return 1
238 fi
239
240 local comb="$addr:$sfx"
241 local nos="${comb//:/}"
242 if (( ${#comb} - ${#nos} == 8 )); then
243 comb="$addr$sfx"
244 fi
245 echo "$comb/$cidr"
246}
247
248ipv6_pfx_to_cidr() {
249 [[ "$1" =~ ^[0-9a-fA-F:]+/([0-9]+)$ ]] || return
250 echo "${BASH_REMATCH[1]}"
251}
252
253network_init=1
254return 0 2>/dev/null
255echo "network is a library, not executed directly" >&2
256exit 1