I have need of a VPN connection to get back home for some basic connectivity and work. And while it works just great, I need to a bit of CLI work to add a route prior to manually connecting the VPN. I figured a script would be the perfect solution to automate this. And given the simplicity of this, I figured this script would be quick and easy, and easily made to be "portable" for use in just about any similar situation. In the end, the script works great, but wasn't without it's challenges.

To start, I suppose I should make a note of some of the other applications that are in use for these connections. In the end, these details turned out to be far more important for the script than I could have ever anticipated. So here goes:

Comically enough, NetworkManager, theoretically, has the ability to do this very thing. The only problem being the route itself... The destination address is a dynamic host; I have a domain name which will resolve to the proper IP, but I have no way to know FOR SURE what the IP will be. NetworkManager does not accept domain name as destinations in the interface routes. So that is why I need a script to do this, so I can first resolve the IP, and then add that route to the routing table. Granted, #!++ will certainly take the DNS name as a destination, but since I'm scripting this all out, I will simplify things, and resolve the IP first, and just add the IP address.

Let us first define the workflow:

  1. First, we need to check if the interface is up and running
  2. If it is, let us then resolve the DNS name to an IP
  3. Only if this has been all succesful:
  4. Add the route
  5. THEN start the VPN

Now that we know what we need to do, let's take a look at the commands to accomplish this:

  1. Let us start by gathering the appropriate interface details. This get's broken up into 2 main commands. First, we check if the interface has an IP address:
    • IFACE_IP=$($IFCONFIG $IFACE | grep "inet addr" | awk '{ print $2 }' | awk -F':' '{ print $2}'
      • And also take a look for any routes on that interface:
    • ROUTE=$($NETSTAT -nr | grep $IFACE)
  2. Next, let us resolve the Target IP address:
    • TARGET_IP=$($NSLOOKUP $TARGET | awk -F': ' 'NR==6 { print $2 }')
  3. To verify that these are all good, we can run the rest of the script within an 'if' statement:
    • if [[ -n "$IFACE_IP" && -n "$ROUTE" && -n "$TARGET_IP" ]]
    • then
    • # ALL GOOD
  4. Add the route:
  5. Start the VPN!
    • $NMCLI connection up uuid $CONN_UUID
  6. Now, to make it run constantly... First off, I put the whole deal into a while loop:
    • COUNT=0
      while [ $COUNT -lt 60 ]
    • And at the end, I made it sleep for 1 second:
    • sleep 1
      let COUNT=COUNT+1
      done
    • This will make the whole script once every second, 60 times. Since the whole script takes time, it will last slightly longer than 60 seconds in total.
  7. Last, set a cron job to run the script every minute (the shortest amount of time a cron job can be set to):
    1. run command:
      • crontab -e
    2. add line:
      • */1 * * * * /home/jon/bin/dynamic_host_route.sh
  8. DONE!

But wait... It didn't work... How? Why? This was a total mystery to me at first. When I ran the script manually, it works like a charm, exactly as expected. So why was the cron job failing? I won't go through all the rabbit holes I went down in searching for this, but we'll just get to the problem and error. So first, I set the cron job to output everything to a file so I could examine it:

*/1 * * * * /home/jon/bin/dynamic_host_route.sh 2>>/home/jon/bin/error.log >> /home/jon/bin/error.log

Once I let it run a few times, I took a look at the error log file, and I could see the following error:

Error: Connection activation failed: Not authorized to control networking.

Again, I dove down so many more rabbit holes here. At this point, I knew the following:

  1. While the cron job is running as my user, it isn't running in a terminal-like environment. This means that details like the PATH environemnt variable isn't set, and my script addresses this by defining and using the commands by their whole path, rather than just the executable. This also has the additional impact on:
  2. NetworkManager, which is taking care of the VPN connection.
  3. Polkit (formerly PolicyKit) also exists in CrunchBang (why polkit?).

It turns out that polkit has a system-wide policy that disallows NetworkManager from being manipulated by anything external (including cron jobs). So all we have to do, is create a policy to allow this. To do this, we need to create a new file:

/etc/polkit-1/localauthority/90-mandatory.d/90-my.thinly.checked.pkla

With the following contents:

[Network Manager - Control... haha]
Identity=unix-user:Beaker
Action=org.freedesktop.NetworkManager.network-control
ResultAny=yes
ResultInactive=yes
ResultActive=yes

I was unable to find anything that explicitly mentions when the new "policy" is applied, or when they are checked. So to be 100% sure, I just rebooted my computer (this also resets everything in memory, so also serves as a nice "fresh start" for my script). And sure enough, once it was back up and running, the script was working like a charm.

The final(ish) script can be downloaded here.

Feel free to let me know what you think in the comments section below.