blob: 2dc5103e088014734467c6d906c515550cc484af [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 III1e268102021-03-08 13:00:12 -0800156ipv6_pfx_concat() {
157 local pfx="$1"
158 local sfx="$2"
159
160 # Validate the prefix
161 if ! [[ "$pfx" =~ ^(([0-9a-fA-F]{1,4}:)+):/([0-9]+)$ ]]; then
162 echo "Invalid IPv6 prefix: $pfx" >&2
163 return 1
164 fi
165 local addr="${BASH_REMATCH[1]}"
166 local cidr="${BASH_REMATCH[3]}"
167 # Ensure prefix doesn't have too many bytes
168 local nos="${addr//:/}"
169 if (( ${#addr} - ${#nos} > (cidr+7)/16 )); then
170 echo "Too many prefix bytes: $pfx" >&2
171 return 1
172 fi
173
174 # Validate the suffix
175 if ! [[ "$sfx" =~ ^[0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4})*$ ]]; then
176 echo "Invalid IPv6 suffix: $sfx" >&2
177 return 1
178 fi
179 # Ensure suffix doesn't have too many bytes
180 local nos="${sfx//:/}"
181 if (( ${#sfx} - ${#nos} >= (128-cidr)/16 )); then
182 echo "Too many suffix bytes: $sfx" >&2
183 return 1
184 fi
185
186 local comb="$addr:$sfx"
187 local nos="${comb//:/}"
188 if (( ${#comb} - ${#nos} == 8 )); then
189 comb="$addr$sfx"
190 fi
191 echo "$comb/$cidr"
192}
193
194ipv6_pfx_to_cidr() {
195 [[ "$1" =~ ^[0-9a-fA-F:]+/([0-9]+)$ ]] || return
196 echo "${BASH_REMATCH[1]}"
197}
198
199network_init=1
200return 0 2>/dev/null
201echo "network is a library, not executed directly" >&2
202exit 1