-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathdetect-local-subnets-AIO.sh
552 lines (454 loc) · 20 KB
/
detect-local-subnets-AIO.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
#!/bin/sh
# detect-local-subnets-AIO.sh
# Unix shell script which uses standard utilities to detect local area ipv4 and ipv6 subnets, regardless of the device it's running on (router or host)
# Some heuristics are employed which are likely to work on Linux but for other Unixes, testing is recommended
# by default, outputs all found local ip addresses, and aggregated subnets
# to output only aggregated subnets (and no other text), run with the '-s' argument
# to only check a specific family (inet or inet6), run with the '-f <family>' argument
# use '-d' argument for debug
#### Initial setup
export LC_ALL=C
me=$(basename "$0")
## Simple args parsing
args=""
for arg in "$@"; do
if [ "$arg" = "-s" ]; then subnets_only="true"
elif [ "$arg" = "-d" ]; then debugmode="true"
elif [ "$arg" = "-f" ]; then families_arg="check"
elif [ "$families_arg" = "check" ]; then families_arg="$arg"
else args="$args $arg"
fi
done
[ "$families_arg" = "check" ] && { echo "Specify family with '-f'." >&2; exit 1; }
set -- "$args"
## Functions
# converts given ip address into a hex number
ip_to_hex() {
ip="$1"
family="$2"
[ -z "$ip" ] && { echo "ip_to_hex(): Error: received an empty ip address." >&2; return 1; }
[ -z "$family" ] && { echo "ip_to_hex(): Error: received an empty value for ip family." >&2; return 1; }
case "$family" in
inet )
split_ip="$(printf "%s" "$ip" | tr '.' ' ')"
for octet in $split_ip; do
printf "%02x" "$octet" || { echo "ip_to_hex(): Error: failed to convert octet '$octet' to hex." >&2; return 1; }
done
;;
inet6 )
expand_ipv6 "$ip" || { echo "ip_to_hex(): Error: failed to expand ip '$ip'." >&2; return 1; }
;;
* ) echo "ip_to_hex(): Error: invalid family '$family'" >&2; return 1 ;;
esac
}
# expands given ipv6 address and converts it into a hex number
expand_ipv6() {
addr="$1"
[ -z "$addr" ] && { echo "expand_ipv6(): Error: received an empty string." >&2; return 1; }
# prepend 0 if we start with :
printf "%s" "$addr" | grep "^:" >/dev/null 2>/dev/null && addr="0${addr}"
# expand ::
if printf "%s" "$addr" | grep "::" >/dev/null 2>/dev/null; then
# count colons
colons="$(printf "%s" "$addr" | tr -cd ':')"
# repeat :0 for every missing colon
expanded_zeroes="$(for i in $(seq $((9-${#colons})) ); do printf "%s" ':0'; done)";
# replace '::'
addr=$(printf "%s" "$addr" | sed "s/::/$expanded_zeroes/")
fi
# replace colons with whitespaces
quads=$(printf "%s" "$addr" | tr ':' ' ')
# pad with 0's and merge
for quad in $quads; do
printf "%04x" "0x$quad" || \
{ echo "expand_ipv6(): Error: failed to convert quad '0x$quad'." >&2; return 1; }
done
}
# returns a compressed ipv6 address in the format recommended by RFC5952
# for input, expects a fully expanded ipv6 address represented as a hex number (no colons)
compress_ipv6() {
ip=""
quads_merged="${1}"
[ -z "$quads_merged" ] && { echo "compress_ipv6(): Error: received an empty string." >&2; return 1; }
# split into whitespace-separated quads
quads="$(printf "%s" "$quads_merged" | sed 's/.\{4\}/& /g')" || { echo "compress_ipv6(): Error: failed to process input '$quads_merged'." >&2; return 1; }
# remove extra leading 0's in each quad, remove whitespaces, add colons
for quad in $quads; do
ip="${ip}$(printf "%x:" "0x$quad")" || { echo "compress_ipv6(): Error: failed to convert quad '0x$quad'." >&2; return 1; }
done
# remove trailing colon, add leading colon
ip=":${ip%:}"
# compress 0's across neighbor chunks
for zero_chain in ":0:0:0:0:0:0:0:0" ":0:0:0:0:0:0:0" ":0:0:0:0:0:0" ":0:0:0:0:0" ":0:0:0:0" ":0:0:0" ":0:0"
do
case "$ip" in
*$zero_chain* )
ip="$(printf "%s" "$ip" | sed -e "s/$zero_chain/::/" -e 's/:::/::/')" || \
{ echo "compress_ipv6(): Error: failed to process input '$quads_merged'." >&2; return 1; }
break
esac
done
# trim leading colon if it's not a double colon
case "$ip" in
::*) ;;
:*) ip="${ip#:}"
esac
printf "%s" "$ip"
}
# converts an ip address represented as a hex number into a standard ipv4 or ipv6 address
hex_to_ip() {
ip_hex="$1"
family="$2"
[ -z "$ip_hex" ] && { echo "hex_to_ip(): Error: received empty value instead of ip_hex." >&2; return 1; }
[ -z "$family" ] && { echo "hex_to_ip(): Error: received empty value for ip family." >&2; return 1; }
case "$family" in
inet )
# split into 4 octets
octets="$(printf "%s" "$ip_hex" | sed 's/.\{2\}/&\ /g')" || { echo "hex_to_ip(): Error: failed to process input '$ip_hex'." >&2; return 1; }
# convert from hex to dec, remove spaces, add delimiting '.'
ip=""
for octet in $octets; do
ip="${ip}$(printf "%d." 0x"$octet")" || { echo "hex_to_ip(): Error: failed to convert octet '0x$octet' to decimal." >&2; return 1; }
done
# remove trailing '.'
ip="${ip%\.}"
printf "%s" "$ip"
return 0
;;
inet6 )
# convert from expanded and merged number into compressed colon-delimited ip
ip="$(compress_ipv6 "$ip_hex")" || return 1
printf "%s" "$ip"
return 0
;;
* ) echo "hex_to_ip(): Error: invalid family '$family'" >&2; return 1
esac
}
# generates a mask represented as a hex number
generate_mask()
{
# CIDR bits
maskbits="$1"
# address length (32 bits for ipv4, 128 bits for ipv6)
addr_len="$2"
[ -z "$maskbits" ] && { echo "generate_mask(): Error: received empty value instead of mask bits." >&2; return 1; }
[ -z "$addr_len" ] && { echo "generate_mask(): Error: received empty value instead of mask length." >&2; return 1; }
mask_bytes=$((addr_len/8))
mask="" bytes_done=0 i=0 sum=0 cur=128
octets='' frac=''
octets=$((maskbits / 8))
frac=$((maskbits % 8))
while [ ${octets} -gt 0 ]; do
mask="${mask}ff"
octets=$((octets - 1))
bytes_done=$((bytes_done + 1))
done
if [ $bytes_done -lt $mask_bytes ]; then
while [ $i -lt $frac ]; do
sum=$((sum + cur))
cur=$((cur / 2))
i=$((i + 1))
done
mask="$mask$(printf "%02x" $sum)" || { echo "generate_mask(): Error: failed to convert byte '$sum' to hex." >&2; return 1; }
bytes_done=$((bytes_done + 1))
while [ $bytes_done -lt $mask_bytes ]; do
mask="${mask}00"
bytes_done=$((bytes_done + 1))
done
fi
printf "%s" "$mask"
}
# validates an ipv4 or ipv6 address or multiple addresses
# if 'ip route get' command is working correctly, validates the addresses through it
# then performs regex validation
validate_ip () {
addr="$1"; addr_regex="$2"
[ -z "$addr" ] && { echo "validate_ip(): Error:- received an empty ip address." >&2; return 1; }
[ -z "$addr_regex" ] && { echo "validate_ip(): Error: address regex has not been specified." >&2; return 1; }
if [ -z "$ip_route_get_disable" ]; then
# using the 'ip route get' command to put the address through kernel's validation
# it normally returns 0 if the ip address is correct and it has a route, 1 if the address is invalid
# 2 if validation successful but for some reason it doesn't want to check the route ('permission denied')
for address in $addr; do
ip route get "$address" >/dev/null 2>/dev/null; rv=$?
[ $rv -eq 1 ] && { echo "validate_ip(): Error: ip address'$address' failed kernel validation." >&2; return 1; }
done
fi
## regex validation
# -v inverts grep output to get non-matching lines
printf "%s\n" "$addr" | grep -vE "^$addr_regex$" > /dev/null; rv=$?
[ $rv -ne 1 ] && { echo "validate_ip(): Error: one or more addresses failed regex validation: '$addr'." >&2; return 1; }
return 0
}
# tests whether 'ip route get' command works for ip validation
test_ip_route_get() {
family="$1"
case "$family" in
inet ) legal_addr="127.0.0.1"; illegal_addr="127.0.0.256" ;;
inet6 ) legal_addr="::1"; illegal_addr=":a:1" ;;
* ) echo "test_ip_route_get(): Error: invalid family '$family'" >&2; return 1
esac
rv_legal=0; rv_illegal=1
# test with a legal ip
ip route get "$legal_addr" >/dev/null 2>/dev/null; rv_legal=$?
# test with an illegal ip
ip route get "$illegal_addr" >/dev/null 2>/dev/null; [ $? -ne 1 ] && rv_illegal=0
# combine the results
rv=$(( rv_legal || ! rv_illegal ))
if [ $rv -ne 0 ]; then
echo "test_ip_route_get(): Note: command 'ip route get' is not working as expected (or at all)." >&2
echo "test_ip_route_get(): Disabling validation using the 'ip route get' command. Less reliable regex validation will be used instead." >&2
echo >&2
ip_route_get_disable=true
fi
unset legal_addr illegal_addr rv_legal rv_illegal
}
# calculates bitwise ip & mask, both represented as hex humbers, and outputs the result in the same format
# arguments:
# 1: ip_hex - ip formatted as a hex number, 2: mask_hex - mask formatted as a hex number, 3: maskbits - CIDR value,
# 4: addr_len - address length in bits (32 for ipv4, 128 for ipv6),
# 5: chunk_len - chunk size in bits used for calculation. seems to perform best with 16 bits for ipv4, 32 bits for ipv6
bitwise_and() {
ip_hex="$1"; mask_hex="$2"; maskbits="$3"; addr_len="$4"; chunk_len="$5"
out_ip=""
# characters representing each chunk
char_num=$((chunk_len / 4))
bits_processed=0; char_offset=0
# shellcheck disable=SC2086
# copy ~ $maskbits bits
while [ $((bits_processed + chunk_len)) -le $maskbits ]; do
chunk_start=$((char_offset + 1))
chunk_end=$((char_offset + char_num))
ip_chunk="$(printf "%s" "$ip_hex" | cut -c${chunk_start}-${chunk_end} )"
out_ip="$out_ip$ip_chunk"
[ "$debug" ] && echo "copied ip chunk: '$ip_chunk'" >&2
bits_processed=$((bits_processed + chunk_len))
char_offset=$((char_offset + char_num))
done
# shellcheck disable=SC2086
# calculate the next chunk if needed
if [ $bits_processed -ne $maskbits ]; then
chunk_start=$((char_offset + 1))
chunk_end=$((char_offset + char_num))
mask_chunk="$(printf "%s" "$mask_hex" | cut -c${chunk_start}-${chunk_end} )"
ip_chunk="$(printf "%s" "$ip_hex" | cut -c${chunk_start}-${chunk_end} )"
ip_chunk=$(printf "%0${char_num}x" $(( 0x$ip_chunk & 0x$mask_chunk )) ) || \
{ echo "bitwise_and(): Error: failed to calculate '0x$ip_chunk & 0x$mask_chunk'." >&2; return 1; }
out_ip="$out_ip$ip_chunk"
[ "$debugmode" ] && echo "calculated ip chunk: '$ip_chunk'" >&2
bits_processed=$((bits_processed + chunk_len))
fi
bytes_missing=$(( (addr_len - bits_processed)/8 ))
# repeat 00 for every missing byte
[ "$debugmode" ] && echo "bytes missing: '$bytes_missing'" >&2
# shellcheck disable=SC2086,SC2034
[ $bytes_missing -gt 0 ] && for b in $(seq 1 $bytes_missing); do out_ip="${out_ip}00"; done
printf '%s' "$out_ip"
return 0
}
aggregate_subnets() {
family="$1"; input_subnets="$2"; subnets_hex=""; res_subnets=""
case "$family" in
inet ) addr_len=32; chunk_len=16; addr_regex="$ipv4_regex" ;;
inet6 ) addr_len=128; chunk_len=32; addr_regex="$ipv6_regex" ;;
* ) echo "aggregate_subnets(): invalid family '$family'." >&2; return 1 ;;
esac
# characters representing each chunk
char_num=$((chunk_len / 4))
# convert to newline-delimited list, remove duplicates from input, convert to lower case
input_subnets="$(printf "%s" "$input_subnets" | tr ' ' '\n' | sort -u | awk '{print tolower($0)}')"
input_ips="$(printf '%s' "$input_subnets" | cut -s -d/ -f1)" || \
{ echo "aggregate_subnets(): Error: failed to process input '$input_subnets'." >&2; return 1; }
validate_ip "$input_ips" "$addr_regex" || \
{ echo "aggregate_subnets(): Error: failed to validate one or more of input addresses." >&2; return 1; }
unset input_ips
for subnet in $input_subnets; do
# get mask bits
maskbits="$(printf "%s" "$subnet" | cut -s -d/ -f2 )" || \
{ echo "aggregate_subnets(): Error: failed to process subnet '$subnet'." >&2; return 1; }
case "$maskbits" in ''|*[!0-9]*) echo "aggregate_subnets(): Error: input '$subnet' has no mask bits or it's not a number." >&2; return 1;; esac
# chop off mask bits
subnet="${subnet%/*}"
# shellcheck disable=SC2086
# validate mask bits
if [ "$maskbits" -lt 8 ] || [ "$maskbits" -gt $addr_len ]; then
echo "aggregate_subnets(): Error: invalid $family mask bits '$maskbits'." >&2; return 1; fi
# convert ip address to hex number
subnet_hex="$(ip_to_hex "$subnet" "$family")" || return 1
# prepend mask bits
subnets_hex="$maskbits/$subnet_hex$newline$subnets_hex"
done
# sort by mask bits, remove empty lines if any
sorted_subnets_hex="$(printf "%s\n" "$subnets_hex" | sort -n | awk -F_ '$1{print $1}')"
while [ -n "$sorted_subnets_hex" ]; do
## trim the 1st (largest) subnet on the list to its mask bits
# get first subnet from the list
subnet1="$(printf "%s" "$sorted_subnets_hex" | head -n 1)"
[ "$debugmode" ] && echo >&2
[ "$debugmode" ] && echo "processing subnet: $subnet1" >&2
# get mask bits
maskbits="${subnet1%/*}"
# chop off mask bits
ip="${subnet1#*/}"
# shellcheck disable=SC2086
# generate mask if it's not been generated yet
if eval [ -z "\$mask_${family}_${maskbits}" ]; then eval mask_${family}_${maskbits}="$(generate_mask "$maskbits" $addr_len)" || return 1; fi
eval mask=\$mask_"${family}_${maskbits}"
# shellcheck disable=SC2086
# calculate ip & mask
ip1="$(bitwise_and "$ip" "$mask" "$maskbits" $addr_len $chunk_len)" || return 1
[ "$debugmode" ] && echo "calculated '$ip' & '$mask' = '$ip1'" >&2
# remove current subnet from the list
sorted_subnets_hex="$(printf "%s" "$sorted_subnets_hex" | tail -n +2)"
remaining_subnets_hex="$sorted_subnets_hex"
remaining_lines_cnt=$(printf '%s' "$remaining_subnets_hex" | wc -l)
i=0
# shellcheck disable=SC2086
# iterate over all remaining subnets
while [ $i -le $remaining_lines_cnt ]; do
i=$((i+1))
subnet2_hex="$(printf "%s" "$remaining_subnets_hex" | awk "NR==$i")"
[ "$debugmode" ] && echo "comparing to subnet: '$subnet2_hex'" >&2
if [ -n "$subnet2_hex" ]; then
# chop off mask bits
ip2="${subnet2_hex#*/}"
ip2_differs=""; bytes_diff=0
bits_processed=0; char_offset=0
# shellcheck disable=SC2086
# compare ~ $maskbits bits of ip1 and ip2
while [ $((bits_processed + chunk_len)) -le $maskbits ]; do
chunk_start=$((char_offset + 1))
chunk_end=$((char_offset + char_num))
ip1_chunk="$(printf "%s" "$ip1" | cut -c${chunk_start}-${chunk_end} )"
ip2_chunk="$(printf "%s" "$ip2" | cut -c${chunk_start}-${chunk_end} )"
[ "$debugmode" ] && echo "comparing chunks '$ip1_chunk' - '$ip2_chunk'" >&2
bytes_diff=$((0x$ip1_chunk - 0x$ip2_chunk)) || \
{ echo "aggregate_subnets(): Error: failed to calculate '0x$ip1_chunk - 0x$ip2_chunk'." >&2; return 1; }
# if there is any difference, no need to calculate further
if [ $bytes_diff -ne 0 ]; then
[ "$debugmode" ] && echo "difference found" >&2
ip2_differs=true; break
fi
bits_processed=$((bits_processed + chunk_len))
char_offset=$((char_offset + char_num))
done
# shellcheck disable=SC2086
# if needed, calculate the next ip2 chunk and compare to ip1 chunk
if [ $bits_processed -ne $maskbits ] && [ -z "$ip2_differs" ]; then
[ "$debugmode" ] && echo "calculating last chunk..." >&2
chunk_start=$((char_offset + 1))
chunk_end=$((char_offset + char_num))
ip1_chunk="$(printf "%s" "$ip1" | cut -c${chunk_start}-${chunk_end} )"
ip2_chunk="$(printf "%s" "$ip2" | cut -c${chunk_start}-${chunk_end} )"
mask_chunk="$(printf "%s" "$mask" | cut -c${chunk_start}-${chunk_end} )"
# bitwise $ip2_chunk & $mask_chunk
ip2_chunk=$(printf "%0${char_num}x" $(( 0x$ip2_chunk & 0x$mask_chunk )) ) || \
{ echo "aggregate_subnets(): Error: failed to calculate '0x$ip2_chunk & 0x$mask_chunk'." >&2; return 1; }
[ "$debugmode" ] && echo "comparing chunks '$ip1_chunk' - '$ip2_chunk'" >&2
bytes_diff=$((0x$ip1_chunk - 0x$ip2_chunk)) || \
{ echo "aggregate_subnets(): Error: failed to calculate '0x$ip1_chunk - 0x$ip2_chunk'." >&2; return 1; }
if [ $bytes_diff -ne 0 ]; then
[ "$debugmode" ] && echo "difference found" >&2
ip2_differs=true
fi
fi
# if no differences found, subnet2 is encapsulated in subnet1 - remove subnet2 from the list
if [ -z "$ip2_differs" ]; then
[ "$debugmode" ] && echo "No difference found" >&2
sorted_subnets_hex="$(printf "%s\n" "$sorted_subnets_hex" | grep -vx "$subnet2_hex")" || \
{ [ -n "$sorted_subnets_hex" ] && { echo "aggregate_subnets(): Error: failed to remove '$subnet2_hex' from the list." >&2; return 1; }; }
fi
fi
done
# format from hex number back to ip
ip1="$(hex_to_ip "$ip1" "$family")" || return 1
# append mask bits and add current subnet to resulting list
res_subnets="${ip1}/${maskbits}${newline}${res_subnets}"
done
output_ips="$(printf '%s' "$res_subnets" | cut -s -d/ -f1)"
validate_ip "$output_ips" "$addr_regex" || \
{ echo "aggregate_subnets(): Error: failed to validate one or more of output addresses." >&2; return 1; }
printf "%s" "$res_subnets"
return 0
}
# attempts to find local subnets, requires family in 1st arg
get_local_subnets() {
family="$1"
case "$family" in
inet )
# get local interface names. filters by "scope link" because this should filter out WAN interfaces
local_ifaces_ipv4="$(ip -f inet route show table local scope link | grep -i -v ' lo ' | \
awk '{for(i=1; i<=NF; i++) if($i~/^dev$/) print $(i+1)}' | sort -u)"
[ -z "$local_ifaces_ipv4" ] && { echo "get_local_subnets(): Error detecting LAN network interfaces for ipv4." >&2; return 1; }
# get ipv4 addresses with mask bits, corresponding to local interfaces
# awk prints the next string after 'inet'
# grep validates the string as ipv4 address with mask bits
local_addresses="$(
for iface in $local_ifaces_ipv4; do
ip -o -f inet addr show "$iface" | \
awk '{for(i=1; i<=NF; i++) if($i~/^inet$/) print $(i+1)}' | grep -E "^$subnet_regex_ipv4$"
done
)"
;;
inet6 )
# get local ipv6 addresses with mask bits
# awk prints the next string after 'inet6'
# 1st grep filters for ULA (unique local addresses with prefix 'fdxx') and link-nocal addresses (fe80::)
# 2nd grep validates the string as ipv6 address with mask bits
local_addresses="$(ip -o -f inet6 addr show | awk '{for(i=1; i<=NF; i++) if($i~/^inet6$/) print $(i+1)}' | \
grep -E -i '^fd[0-9a-f]{0,2}:|^fe80:' | grep -E -i "^$subnet_regex_ipv6$")"
;;
* ) echo "get_local_subnets(): invalid family '$family'." >&2; return 1 ;;
esac
[ -z "$local_addresses" ] && { echo "get_local_subnets(): Error detecting local addresses for family $family." >&2; return 1; }
[ -z "$subnets_only" ] && {
echo "Local $family addresses:"
echo "$local_addresses"
echo
}
local_subnets="$(aggregate_subnets "$family" "$local_addresses")"; rv1=$?
if [ $rv1 -eq 0 ]; then
[ -z "$subnets_only" ] && echo "Local $family subnets (aggregated):"
if [ -n "$local_subnets" ]; then printf "%s\n" "$local_subnets"; else echo "None found."; fi
else
echo "Error detecting $family subnets." >&2
fi
[ -z "$subnets_only" ] && echo
return $rv1
}
## Constants
newline='
'
ipv4_regex='((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])\.){3}(25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])'
ipv6_regex='([0-9a-f]{0,4}:){1,7}[0-9a-f]{0,4}:?'
maskbits_regex_ipv6='(12[0-8]|((1[0-1]|[1-9])[0-9])|[8-9])'
maskbits_regex_ipv4='(3[0-2]|([1-2][0-9])|[8-9])'
subnet_regex_ipv4="${ipv4_regex}/${maskbits_regex_ipv4}"
subnet_regex_ipv6="${ipv6_regex}/${maskbits_regex_ipv6}"
## Checks
# check dependencies
! command -v awk >/dev/null || ! command -v sed >/dev/null || ! command -v tr >/dev/null || \
! command -v grep >/dev/null || ! command -v ip >/dev/null || ! command -v cut >/dev/null && \
{ echo "$me: Error: missing dependencies, can not proceed" >&2; exit 1; }
# test 'grep -E'
rv=0; rv1=0; rv2=0
printf "%s" "32" | grep -E "^${maskbits_regex_ipv4}$" > /dev/null; rv1=$?
printf "%s" "0" | grep -E "^${maskbits_regex_ipv4}$" > /dev/null; rv2=$?
rv=$((rv1 || ! rv2))
[ "$rv" -ne 0 ] && { echo "$me: Error: 'grep -E' command is not working correctly." >&2; exit 1; }
unset rv rv1 rv2
## Main
[ -n "$families_arg" ] && families_arg="$(printf '%s' "$families_arg" | awk '{print tolower($0)}')"
case "$families_arg" in
inet|inet6|'inet inet6'|'inet6 inet' ) families="$families_arg" ;;
''|'ipv4 ipv6'|'ipv6 ipv4' ) families="inet inet6" ;;
ipv4 ) families="inet" ;;
ipv6 ) families="inet6" ;;
* ) echo "$me: Error: invalid family '$families_arg'." >&2; exit 1 ;;
esac
rv_global=0
for family in $families; do
test_ip_route_get "$family" || return 1
get_local_subnets "$family"; rv_global=$((rv_global + $?))
done
exit $rv_global