How it all started
In the provider where I work, it has historically been established that client networks are fixed on FreeBSD servers with static routing in the switching fields connecting these servers. Accordingly, when adding a new network, the routes for it must be registered on all servers separately. Adding the human factor to this, it may turn out that some servers will be forgotten and skipped when adding or changing a route. In this regard, it becomes logical to somehow automate this process.
Implementation
We presume that we have a certain number of servers running FreeBSD, and when adding a new route (for example, on one of these servers, a new client network was fixed) it should be registered on all of these servers.
To begin with, we will create a text file, each line of which is the address of one server, and for each of these servers we will configure authorization by key (how to do this is described, for example,
here ).
Example of server list file:
192.168.0.1
192.168.1.1
mainserver.yourdomain.ru
Further work will be done by a bunch of two shell scripts:
1)
checkroute.sh [-c] destination gateway , where
-c - check-only option (only check the match without making changes),
destination - IP address or destination network,
gateway is the destination gateway.
This script checks the validity of the entered parameters of the script and, connecting in turn to each of the servers from the list by ssh, copies the second script to it and runs it.
2)
checker.sh destination gateway ifcheckIn turn, it checks the current routing table and configuration files, reports the results of the check and, if the first script was run without the -c option, adjusts the corresponding entries to the destination / gateway pair.
checkroute.sh
First check the options with which the script was run. At the moment, only the -c option is processed, but this functionality is easily extended by analogy.
')
ifcheck = "no"
while getopts "c" optname
do case "$ optname" in
"c") ifcheck = "check" ;;
*) echo "Usage: $ 0 [OPTIONS] DESTINATION GATEWAY
\ tc \ tcheck route, don't correct it
"
exit
;;
esac
done
shift $ (($ OPTIND -1))
The next step is to check the validity of the DESTINATION and GATEWAY parameters passed to the script. For our purposes, it is necessary that the IP address or the network could act as the DESTINATION, and only the IP address as the GATEWAY.
validIPregexp = "(25 [0-5] | 2 [0-4] [0-9] | [01]? [0-9] [0-9]?) \. (25 [0-5] | 2 [0-4] [0-9] | [01]? [0-9] [0-9]?) \. (25 [0-5] | 2 [0-4] [0-9] | [ 01]? [0-9] [0-9]?) \. (25 [0-5] | 2 [0-4] [0-9] | [01]? [0-9] [0-9 ]?) "
checkip () {
CHECK = $ (echo $ 1 | grep -E "^ $ validIPregexp $ 2")
if [! "$?" -eq 0]
then
echo "Incorrect $ 3: $ 1, please try again."
exit
fi
}
checkip $ 1 "(/ ([0-2]? [0-9] | 3 [0-2]))? $" "destination IP address or network"
checkip $ 2 "$" "gateway"
Next, read the $ serverlist file line by line and for each server in the list, initialize the ssh connection, copy and run the checker.sh script. The $ dir variable stores the path to the directory in which the $ serverlist and checker.sh files are located.
serverlist = 'servers.lst'
dir = '/ usr / local / etc / your_dir'
cat "$ {dir} / $ serverlist" | while read server
do
echo "$ server:"
cat "$ {dir} / $ checker.sh | ssh -q $ server" rm -f /var/tmp/checker.sh; cat - >> /var/tmp/checker.sh; sh /var/tmp/checker.sh $ 1 $ 2 $ ifcheck "
done
checker.sh
Now let's see what the checker.sh script does when running on one of our servers.
The function that applies the changes if the script script.sh was run without the option -c
ifcheck = $ 3
commit () {
if [$ ifcheck! = "check"]
then
eval "$ 1" 2> / dev / null
if [! $? -eq 0]
then
echo "Couldn't process the following command:
$ 1 "
else echo "$ 2"
fi
else echo "To correct it run this script without \" - with \ "option or process the following command manually:
$ 1
"
fi
}
Parse the current routing table (output netstat -rnW) and write the result to $ CurrentGW
Possible options:
1) The destination address is hardened on the local interface ($ CurrentGW = "LOCAL")
2) The destination address is hardened somewhere else and is present in the routing table, then $ CurrentGW will be assigned the current gateway for this address
3) The destination address is hardened somewhere else and is not in the routing table. In this case, $ CurrentGW will remain empty.
CurrentGW = `netstat -rnW | awk -v gw = "$ 2" -v src = "$ 1" '{if ($ 1 ~ src) {if ($ 2 ~ /' $ validIPregexp '/) {if ($ 2! ~ gw) {print $ 2} else {print "OK"}} else {print "LOCAL"}}} '`
if [-z $ CurrentGW]
then
echo "Route for $ 1 does not exist."
commit "sudo route add $ 1 $ 2" "Adding route."
else
case $ CurrentGW in
OK) echo "Current route to $ 1 is $ 2: OK" ;;
LOCAL) echo "It's on local interface" ;;
*) echo "Route for $ 1 exists but destination is wrong: $ CurrentGW instead of $ 2"; commit "sudo route change $ 1 $ 2" "Changing it." ;;
esac
fi
Next, parse the configuration file, the full path to which lies in the $ filename variable. To do this, select from it all the lines describing the addition of routes to a temporary file and check the existence of an entry for the destination address. It is done in the same way as the netstat -rnW output parsing above.
filename = '/ usr / local / etc / rc.d / rc.script.for.adding.routes.sh'
cat "$ filename" | grep -v '^ #' | sed '/ ^ $ / d' | sed 's / [\ t] + / / g' | grep -E 'route. + add'> /var/tmp/routes_temp.lst
FileGW = `cat /var/tmp/routes_temp.lst | grep $ 1 | awk -v gw = "$ 2" '{if ($ NF ~ gw) {print "YES"} else {print $ NF}}' `
if [$ CurrentGW = "LOCAL"]
then if [! -z $ FileGW]
then
echo "for $ 1 was found in $ filename."
commit "sudo sed -Ei '' '\: route [^ \ n] + add [^ \ n] + $ 1:' $ filename" "Deleting it from there."
fi
else
if [-z $ FileGW]
then
echo "Checked $ filename: no match."
t1 = `tail -1 /var/tmp/routes_temp.lst | awk '{print $ (NF-1)}' `
t2 = `tail -1 /var/tmp/routes_temp.lst | awk '{print $ NF}' `
IFS = ''
regex = "\\: route [^ \\ n] + add [^ \\ n] + $ t1 [\\ t] + $ t2: a \\
/ sbin / route -nq add $ 1 $ 2
"
commit "sudo sed -Ei '' '$ regex2' $ filename" "Adding route to the end of file"
else
if [$ FileGW! = "YES"]
then
echo "Record for $ 1 found, but gateway is wrong: $ FileGW."
regex = "s | $ 1 [\ t] + $ FileGW | $ 1 $ 2 | g"
commit "sudo sed -Ei '' '$ regex2' $ filename" "Changing gateway to $ 2"
else echo "Correct route was found in $ filename: OK"
fi
fi
fi
I want to clarify a couple of points from the last piece of code. The redefinition of the IFS system variable is necessary in order to create a multi-line variable $ regex, which, in turn, is dictated by the syntax sed. This way of adding a line to a file was chosen because routes to $ filename can be added inside one of the functions, as it happens in my case. If this is not critical for you, you can do with the construction of the form
commit "sudo echo '/ sbin / route -nq add $ 1 $ 2' >> $ filename"
Conclusion
The scripts described above can be adapted to other * nix systems, the difference is in the sed keys (for Debian, for example, -ri instead of -Ei "") and the syntax of the route command (see man route). The functionality of scripts, as already mentioned above, is easily extended by analogy.
There are quite a few checks in the scripts (“protection against a fool”), but the responsibility for the correctness of the destination / gateway pair lies with the administrator running the script. Be carefull!
The full texts of both scripts are attached:
checkroute.sh ,
checker.sh