-
Notifications
You must be signed in to change notification settings - Fork 10
/
setGW.sh
executable file
·406 lines (355 loc) · 12.3 KB
/
setGW.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
#!/bin/bash
# Author: Andrew Howard
# Purpose: Edit /etc/sysconfig/network-scripts/route-INTERFACE to ensure static
# route exists for the specified network via the specified gateway.
# Last updated: 2015-09-14
# To-Do:
# Nothing
#
# Verify the existence of pre-req's
PREREQS="awk cat cp cut date echo grep id sed sort uniq"
PREREQFLAG=0
for PREREQ in $PREREQS; do
which $PREREQ &>/dev/null
if [ $? -ne 0 ]; then
echo "Error: Gotta have '$PREREQ' binary to run." >&2
PREREQFLAG=1
fi
done
if [ $PREREQFLAG -ne 0 ]; then
exit 1
fi
#
# Usage statement
function usage() {
cat <<EOF
Usage: setGW.sh [-d] [-h] -n NETWORK -g NEWGW [-i INTERFACE]
Examples:
# ./setGW.sh -h
# ./setGW.sh -d -n 10.3.4.0/26 -g 10.3.4.5
# ./setGW.sh -n 10.3.4.0/26 -g 10.3.4.5 -i wlan0
This script sets a static route for NETWORK via the gateway NEWGW.
If a route already exists for the given NETWORK, the gateway will be changed.
If a route does not already exist, it will be added.
Arguments:
-d Optional. Dry-run. Don't change files - just print to STDOUT
the data we *would* output into route-INTERFACE.
-g X Set new gateway address to X.
-h Optional. Print this help.
-i X Optional. Set routes for interface X.
-n X Set the gateway for network range X (CIDR).
EOF
}
# ----- Begin Helper Functions ----- #
# Test if argument is a valid IPv4 address
function validIPv4() {
if grep -qP '^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$' <<<"$1"; then
return 0
fi
return 1
}
# Test if argument is a valid CIDR range
function validCIDR() {
if grep -qP '^\d+$' <<<"$1"; then
if [ "$1" -le 32 ]; then
return 0
fi
fi
return 1
}
# Convert a CIDR to subnet mask
# This isn't the most efficient implementation, but I wanted to write
# my own rather than steal something from the internet.
function cidr2nm() {
local NETMASK=""
local CIDR="$1"
for QUAD in {1..4}; do
if [ $CIDR -ge 8 ]; then
NETMASK="${NETMASK}.255"
elif [ $CIDR -ge 1 ]; then
NETMASK="${NETMASK}.$(( 256 - 2**(8-$CIDR) ))"
else
NETMASK="${NETMASK}.0"
fi
CIDR=$(( $CIDR - 8 ))
done
NETMASK="$( cut -d\. -f2- <<<"$NETMASK" )" # Trim the leading '.'
echo "$NETMASK"
}
# From a list of array indices, return the next available slot
function nextUnusedIndex() {
local INDICES="$@"
local INDEX=0
if [ -z "$INDICES" ]; then
echo 0
else
INDEX=$( echo "$INDICES" |
sed 's/\s\s*/\n/g' | # Sub whitespace with newlines
sort -n | tail -n 1 )
INDEX=$(( $INDEX + 1 ))
echo $INDEX
fi
}
# ----- End Helper Functions ----- #
#
# Handle command-line arguments
NEWGW=""
NETWORK=""
INTERFACE="eth0"
DRYRUN=0
USAGEFLAG=0
while getopts ":dg:hi:n:" arg; do
case $arg in
d) DRYRUN=1;;
g) NEWGW="$OPTARG";;
h) usage && exit 0;;
i) INTERFACE="$OPTARG";;
n) NETWORK="$OPTARG";;
:) echo "Error: Option -$OPTARG requires an argument." >&2
USAGEFLAG=1;;
*) echo "Error: Invalid option: -$OPTARG" >&2
USAGEFLAG=1;;
esac
done #End arguments
shift $(($OPTIND - 1))
#
# Verify command-line arg NEWGW
if [ -z "$NEWGW" ]; then
echo "Error: Must define NEWGW as argument." >&2
USAGEFLAG=1
# Test that NEWGW is a valid IPv4 address
elif ! validIPv4 "$NEWGW"; then
echo "Error: Specified gateway ($NEWGW) is not a valid IPv4 address." >&2
USAGEFLAG=1
fi
#
# Verify command-line arg NETWORK
# BEGIN null test
if [ -z "$NETWORK" ]; then
echo "Error: Must define NETWORK as argument." >&2
USAGEFLAG=1
else
# Check whether this is valid CIDR format (ie: foo/bar)
# BEGIN notation test
if ! grep -q / <<<"$NETWORK"; then
echo "Error: NETWORK ($NETWORK) is not a valid CIDR network." >&2
USAGEFLAG=1
else
# Split NETWORK into its network address, and its CIDR mask
NETADDR="$( cut -d/ -f1 <<<"$NETWORK" )"
CIDR="$( cut -d/ -f2- <<<"$NETWORK" )"
# Test that NETADDR is a valid IPv4 address
# BEGIN netaddr test
if ! validIPv4 "$NETADDR"; then
echo "Error: Address portion of NETWORK ($NETADDR) is not a valid IPv4 address." >&2
USAGEFLAG=1
fi # END netaddr test
# BEGIN cidr test
if ! validCIDR "$CIDR"; then
echo "Error: '$CIDR' is not a valid CIDR mask (0-32)." >&2
USAGEFLAG=1
fi # END cidr test
fi # END notation test
fi # END null test
#
# Confirm specified network interface exists
if ! grep -qP '^\s*'"$INTERFACE"':' /proc/net/dev; then
echo "Error: Network interface '$INTERFACE' does not exist." >&2
USAGEFLAG=1
else
ROUTEFILE="/etc/sysconfig/network-scripts/route-$INTERFACE"
fi
#
# Exit if USAGEFLAG got set by any invalid args
if [ $USAGEFLAG -ne 0 ]; then
usage && exit 3
fi
# Okay, so now we've got both valid syntax and arguments.
# Let's get down to business.
#
# Confirm we're root
if [ $(id -u) -ne 0 ]; then
echo "Error: Script must be executed as root (UID:0)." >&2
exit 4
fi
#
# Read current content of route-INTERFACE into parallel arrays
# Each route will be stored as "ADDRESS[x]/NETMASK[x] via GATEWAY[x]"
declare -a ADDRESS
declare -a NETMASK
declare -a GATEWAY
INDEX=0
IPROUTES=""
# This file could contain entries in one of two formats. I'm honestly not
# certain if mixing those two formats is legal, so I'll just assume it is
# for my input.
# "x/y via z" lines are non-positional, so we'll just append those to the end.
# That means we need to parse all the "var=val" lines first, since those are
# positional (ie: ADDRESS0, ADDRESS1, etc).
if [ -f "$ROUTEFILE" ]; then
while read LINE; do # <"$ROUTEFILE"
SYNTAXFLAG=0
if grep -qP '^\s*(#.*)?$' <<<"$LINE"; then
continue # Blank line or all-comment line - skip it
fi
if grep -qP '^\s*ADDRESS\d+\s*=\s*(\d+\.){3}\d+\s*(#.*)?$' <<<"$LINE"; then
# Regex help: ADDRESSxxx = ^-----IP----^ [#..]
INDEX="$( sed 's/^\s*ADDRESS\([0-9]*\)\s*=.*/\1/' <<<"$LINE" )"
VALUE="$( cut -d= -f2- <<<"$LINE" | sed 's/\s*(#.*)?$//' )"
if validIPv4 "$VALUE"; then
ADDRESS[$INDEX]="$VALUE"
else
echo "Error: '$VALUE' is not a valid IPv4 address." >&2
SYNTAXFLAG=1
fi
elif grep -qP '^\s*NETMASK\d+\s*=\s*(\d+\.){3}\d+\s*(#.*)?$' <<<"$LINE"; then
# Regex help: NETMASKxxx = ^-----IP----^ [#..]
INDEX="$( sed 's/^\s*NETMASK\([0-9]*\)\s*=.*/\1/' <<<"$LINE" )"
VALUE="$( cut -d= -f2- <<<"$LINE" | sed 's/\s*(#.*)?$//' )"
if validIPv4 "$VALUE"; then
NETMASK[$INDEX]="$VALUE"
else
echo "Error: '$VALUE' is not a valid IPv4 address." >&2
SYNTAXFLAG=1
fi
elif grep -qP '^\s*GATEWAY\d+\s*=\s*(\d+\.){3}\d+\s*(#.*)?$' <<<"$LINE"; then
# Regex help: GATEWAYxxx = ^-----IP----^ [#..]
INDEX="$( sed 's/^\s*GATEWAY\([0-9]*\)\s*=.*/\1/' <<<"$LINE" )"
VALUE="$( cut -d= -f2- <<<"$LINE" | sed 's/\s*(#.*)?$//' )"
if validIPv4 "$VALUE"; then
GATEWAY[$INDEX]="$VALUE"
else
echo "Error: '$VALUE' is not a valid IPv4 address." >&2
SYNTAXFLAG=1
fi
elif grep -qP '^\s*(\d+\.){3}\d+/\d+\s+via\s+(\d+\.){3}\d+(\s+dev\s+'"$INTERFACE"')?\s*(#.*)?$' <<<"$LINE"; then
#Regex help: ^-----IP----^/xx via ^-----IP----^[ dev ^---eth0---^ ] [#..]
# Line is "x/y via z" format - save the details for later
# Note: The regex that got us into this conditional branch was so
# explicit that it's save to make some assumptions on $LINE now.
# We only did lazy IPv4 & CIDR verification though, so we can test again
# within this conditional, and print more specific/helpful errors.
ONEADDR="$( awk '{print $1}' <<<"$LINE" | cut -d/ -f1 )"
ONEMASK="$( awk '{print $1}' <<<"$LINE" | cut -d/ -f2 )"
ONEGW="$( awk '{print $3}' <<<"$LINE" )"
if ! validIPv4 "$ONEADDR"; then
echo "Error: '$ONEADDR' is not a valid IPv4 address." >&2
SYNTAXFLAG=1
fi
if ! validCIDR "$ONEMASK"; then
echo "Error: '$ONEMASK' is not a valid CIDR range (0-32)." >&2
SYNTAXFLAG=1
else
ONEMASK="$( cidr2nm $ONEMASK )"
fi
if ! validIPv4 "$ONEGW"; then
echo "Error: '$ONEADDR' is not a valid IPv4 address." >&2
SYNTAXFLAG=1
fi
# If all is well, save the route for later
[ "$SYNTAXFLAG" -eq 0 ] && IPROUTES="${IPROUTES}$ONEADDR $ONEMASK $ONEGW\n"
elif grep -qP '^\s*default\s+(\d+\.){3}\d+(\s+dev\s+'"$INTERFACE"')?\s*(#.*)?$' <<<"$LINE"; then
# Regex help: default ^-----IP----^[ dev ^---eth0---^ ] [#..]
# Line is "default X.X.X.X dev interface" format
VALUE="$( awk '{print $2}' <<<"$LINE" )"
if validIPv4 "$VALUE"; then
IPROUTES="${IPROUTES}0.0.0.0 0.0.0.0 $VALUE\n"
else
echo "Error: '$VALUE' is not a valid IPv4 address:" >&2
SYNTAXFLAG=1
fi
else
# This line *must* be a syntax error. That means we can't possibly hurt
# anything by ignoring it and regenerating the file with known-good
# syntax. Print a warning and move on.
SYNTAXFLAG=1
fi
# If anything caused SYNTAXFLAG to get set, inform that we're skipping
# this line.
if [ $SYNTAXFLAG -ne 0 ]; then
echo -e "Warning: Skipping this line due to syntax error:\n $LINE" >&2
fi
done <"$ROUTEFILE"
fi
#
# Do a quick check that all three arrays have the exact same indices filled
# First we count how many times each index occurred:
INDEXCOUNT="$( echo "${!ADDRESS[@]} ${!NETMASK[@]} ${!GATEWAY[@]}" |
sed -e '/^\s*$/d' -e 's/\s\s*/\n/g' |
sort -n |
uniq -c |
sort -n )"
# Strip down to only indices that occurred exactly 3 times (once in each array)
INDICES="$( awk '$1 ~ /^3$/ {print $2}' <<<"$INDEXCOUNT" )"
# Print a warning if there were any entries *not* in all three arrays
if [ $(wc -l <<<"$INDEXCOUNT") -ne $(wc -l <<<"$INDICES") ]; then
echo "Warning: Some routes were partially defined, and were omitted." >&2
fi
#
# Now that we definitely know the last index of the valid "var=val" routes,
# we can append the non-positional "x/y via z" routes to the end.
INDEX="$( nextUnusedIndex $INDICES )"
# Now INDEX is pointing at the "end" of the array. Add our non-
# positional routes, starting at that INDEX.
while read LINE; do # <<<"$( echo -e "$IPROUTES" )"
[ -z "$LINE" ] && continue # Prevent the addition of a null route
read ADDRESS[$INDEX]="$ONEADDR" \
NETMASK[$INDEX]="$CIDR2NM" \
GATEWAY[$INDEX]="$NEWGW" <<<"$LINE"
INDICES="$INDICES $INDEX"
INDEX=$(( $INDEX + 1 ))
done <<<"$( echo -e "$IPROUTES" )"
#
# Find which array index contains our specified network address and netmask
# Reminder on command-line args, since we're finally using them:
# -n $NETADDR/$CIDR
# -g $NEWGW
CIDR2NM=$( cidr2nm "$CIDR" )
INDEXFLAG=0
for INDEX in $INDICES; do
if [ "${ADDRESS[$INDEX]}" == "$NETADDR" ]; then
if [ "${NETMASK[$INDEX]}" == "$CIDR2NM" ]; then
INDEXFLAG=1
break #INDEX will maintain the value we care about
fi
fi
done
#
# Add or adjust the specified route in our parallel arrays
if [ $INDEXFLAG -eq 1 ]; then
# If INDEXFLAG is set, we found an existing definition for the specified
# route - we'll need to edit the GATEWAY at that INDEX.
GATEWAY[$INDEX]="$NEWGW"
else
# If INDEXFLAG is unset, then we didn't find the specified network in any
# existing route definitions. That means we're adding, not editing.
# First find the "end" of the array:
INDEX="$( nextUnusedIndex $INDICES )"
# Insert the new element
ADDRESS[$INDEX]="$NETADDR"
NETMASK[$INDEX]="$CIDR2NM"
GATEWAY[$INDEX]="$NEWGW"
# By adding that entry, we've now added a new valid-index, so update
# INDICES with that new INDEX.
INDICES="$INDICES $INDEX"
fi
# Parallel arrays should now contain all desired network information.
#
# Write out the array data to route-INTERFACE format
OUTPUT=$(
for INDEX in $INDICES; do
echo "ADDRESS${INDEX}=${ADDRESS[$INDEX]}"
echo "NETMASK${INDEX}=${NETMASK[$INDEX]}"
echo "GATEWAY${INDEX}=${GATEWAY[$INDEX]}"
done )
#
# Clobber route-INTERFACE with the data from our parallel arrays
# unless "-d" was passed, then just echo the data instead.
if [ $DRYRUN -ne 0 ]; then
echo "$OUTPUT"
else
# Backup the route file, then clobber it
[ -f "$ROUTEFILE" ] && cp -p "$ROUTEFILE" "$ROUTEFILE.$(date '+%F-%T')"
echo "$OUTPUT" >"$ROUTEFILE"
fi
exit 0