Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/bin/bash
- # dig.sh
- # automated VPN tunnel management script
- set -x
- # global variable declaration
- # Network Variables
- sVPN_Server="wedontdoresi.com"
- sIP_wlan0=$(ip a s wlan0 | grep 'inet ' | awk '{print $2}' | sed 's/\/.*//g')
- sIP_eth0=$(ip a s eth0 | grep 'inet ' | awk '{print $2}' | sed 's/\/.*//g')
- saIPs=( "$sIP_wlan0" "$sIP_eth0" )
- # gather interfaces with default routes
- while read -r line; do
- dvaIFs_Routed+=( "$line" )
- done <<< "$(ip r s | grep default | awk '{print $5}')"
- # File and path variables
- sPath="$(cd "$(dirname $0)" && pwd)"
- if [ -z "$sPath" ]; then
- echo "ERROR: Cannot derive path."
- exit 1
- fi
- sLogfile="connection.log"
- sConfig="hostus-embed-meerkat1.ovpn"
- sVPN_Logfile="vpn.log"
- #sPidfile_fullpath="/var/run/openvpn.pid"
- # Logging and debugging verbosity
- nVerbosity=1 # default to 1
- function fnLog()
- {
- # eventually we want optional 3rd parameter to have bit flags
- # 1 bit should force the log entry even if it's repeating
- # 1 bit should suppress log echoes for this call
- if [ -n "$3" ];then
- echo "Forced log entry:" >> "$sPath"/"$sLogfile"
- fi
- if ! tail -3 "$sPath"/"$sLogfile" | grep "$2"; then
- if [ "$1" -le $nVerbosity ]; then
- case $nVerbosity in
- 1) # default logging
- echo "$(date) | $2" >> "$sPath"/"$sLogfile"
- ;;
- 2) # noisy logging
- echo "$(date) | $2" | tee -a "$sPath"/"$sLogfile"
- ;;
- 3) # noisy debugging
- echo "$(date) | Debug msg--- $2" | tee -a "$sPath"/"$sLogfile"
- ;;
- 4) # "up to 11" (but really it's 4)
- echo "$(date) | $2" >> "$sPath"/"$sLogfile"
- sudo wall -n "$2"
- ;;
- esac
- fi
- fi
- }
- ##################################
- ## network related functions ##
- ##################################
- # takes two parameters, the first is mandatory [start|stop]
- # the second is a local ip to bind to the VPN tunnel and is only used in conjuction with start
- function fnVPN_Control()
- {
- case "$1" in
- start)
- # a few notes on starting the VPN:
- #+ we can't depend on the exit status of the openvpn command, because apparently it exits ok even if it doesn't start successfully
- # also, it doesn't like unset variables in the command path. so that precludes an entire approach to modular triggering
- if [ -z "$2" ]; then
- fnLog 3 "Executing: sudo -b openvpn --config $sPath/$sConfig --log $sPath/vpn.log --writepid /var/run/openvpn.pid"
- if sudo -b openvpn --config "$sPath"/"$sConfig" --log "$sPath"/"$sVPN_Logfile" --writepid /var/run/openvpn.pid; then
- sudo wall -n "Tunneling to Burrow ..."
- fi
- else
- fnLog 3 "Executing: sudo -b openvpn --config $sPath/$sConfig --log $sPath/vpn.log --writepid /var/run/openvpn.pid --local $2"
- if sudo -b openvpn --config "$sPath"/"$sConfig" --log "$sPath"/"$sVPN_Logfile" --writepid /var/run/openvpn.pid --local "$2"; then
- sudo wall -n "Tunneling to Burrow ..."
- fi
- fi
- # at this point, we should find a dependable way to check if the vpn was established sucessfully
- # sleep 5
- # if [ $(tail -1 vpn.log | grep 'Initialization Sequence Completed') ]; then
- # fnLog 1 "Connection to Burrow successful."
- # fi
- ;;
- stop)
- if pgrep vpn &> /dev/null; then
- sudo wall -n "Tunnel aborted."
- fnLog 1 "VPN tunnel closed."
- sudo killall openvpn
- else
- fnLog 1 "No VPN to kill."
- fi
- ;;
- esac
- }
- function fnNIC_to_IP()
- {
- local sIP
- sIP="$(ip a s "$1" 2> /dev/null | grep 'inet ' | awk '{print $2}' | sed 's/\/.*//g' | grep -v -e "[a-zA-Z]")"
- if [ -n "$sIP" ]; then
- echo "$sIP"
- else
- return 1
- fi
- }
- # takes two parameters, first is the interface name, the second is the output notation flag
- # output notation flag: 1 (default) CIDR notation (ex- 192.168.1.0/24) as used with iptables
- #+ 2 full notation (ex- 192.168.1.0 255.255.255.0) as used with openvpn configs
- # right now it's a crude, but functional version that makes a lot of assumptions and only really works for
- # networks we're likely to come across. later I can expand it to be more comprehensive and flexible
- function fnNIC_to_NET()
- {
- local nType
- local sResult
- local sNIC
- sNIC="$1"
- if [ -z "$2" ]; then
- nType=1
- else
- nType="$2"
- fi
- case "$nType" in
- 1) sResult="$(ip a s $sNIC | grep 'inet ' | awk '{print $2}' | sed 's/\.[0-9]\{1,3\}\//\.0\//')" ;; # pulls the ip address reported by "ip address show" (which reports CIDR notation) and replaces the last octet with 0
- 2) sResult="$(ifconfig $sNIC | grep 'inet ' | sed 's/.*inet //' | sed 's/\.[0-9]\{1,3\}\ \ netmask/\.0/' | sed 's/ destination.*//' | sed 's/\ \{0,\}broadcast.*//')";; # ifconfig conveniently reports netmasks in
- esac
- echo "$sResult"
- }
- # takes three parameters, first is a directive [add|remove|check]
- # second parameter is a NIC to SNAT/MASQUERADE (typically eth0)
- # third parameter is an optional type (selects SNAT/MASQUERADE with 1/2), defaults to 1 (2 for now because troubleshooting fewer variables in the command is easier)
- function fnIPtables() # (char [add|remove|check], char [<interface name>] [, integer <type flag, 1=SNAT, 2=MASQUERADE>])
- {
- local saNATs=()
- local nType
- local sTun_Net
- local sLocalIP
- # set default type
- if [ -z "$3" ]; then
- nType=2 # change to 1 once I get this to behave
- else
- nType="$3"
- fi
- sTun_Net="$(fnNIC_to_NET tun0 1)"
- sLocalIP="$(fnNIC_to_IP $2)"
- ((nType--)) # adjust flag to array index
- saNATs[0]="POSTROUTING -s $sTun_Net -o $2 -j SNAT --to-source $sLocalIP"
- saNATs[1]="POSTROUTING -s $sTun_Net -o $2 -j MASQUERADE"
- fnLog 3 "IP Table NAT entry type 1: ${saNATs[0]}"
- fnLog 3 "IP Table NAT entry type 2: ${saNATs[1]}"
- case "$1" in
- add)
- fnLog 1 "Appending the NAT table with the following rule: \"${saNATs[$nType]}\""
- #if sudo iptables -t nat -A "${saNATs[$nType]}" &>> "$sPath"/"$sLogfile"; then
- if sudo iptables -t nat -A POSTROUTING -s "$sTun_Net" -o "$2" -j MASQUERADE &>> "$sPath"/"$sLogfile"; then
- fnLog 1 "Append successful."
- echo 0
- else
- fnLog 1 "Append unsuccessful."
- echo 1
- fi
- ;;
- remove)
- fnLog 1 "Removing the following rule: \"${saNATs[$nType]}\" from the NAT table."
- if sudo iptables -t nat -D ${saNATs[$nType]} &>> "$sPath"/"$sLogfile"; then
- fnLog 1 "Remove successful."
- echo 0
- else
- fnLog 1 "Remove unsuccessful."
- echo 1
- fi
- ;;
- check)
- fnLog 1 "Querying the NAT table for either a SNAT or MASQUERADE route on interface $2"
- # note: if the full append command is contained in the array, the array must be preceded with a double backslash to properly escape the initial dash so grep doesn't process it
- if sudo iptables -t nat -S | grep -q "${saNATs[$nType]}";then
- #if sudo iptables -t nat -S | grep -q "${saNATs[0]}" || sudo iptables -t nat -S | grep -q "${saNATs[1]}";then
- fnLog 3 "NAT Table entry found."
- echo 0
- else
- fnLog 3 "NAT Table entry not found."
- echo 1
- fi
- ;;
- esac
- }
- # takes an interface name as a parameter and attempts to start a vpn connection
- # this can be thought of as a "careful start" to the VPN, with more debugging info
- function fnInitiate_Session()
- {
- #sScanCmd="sudo nmap -n -e $1 -sU -p1194 $sVPN_Server | grep 1194 | grep 'open '"
- fnLog 1 "Attempting fnInitiate_Session on $1"
- if sudo nmap -n -e $1 -sU -p1194 $sVPN_Server | grep 1194 | grep 'open ' &> /dev/null; then
- fnLog 1 "Burrow visible via source interface $1. Starting VPN Connection"
- fnVPN_Control "start" $(ip a s wlan0 | grep 'inet ' | awk '{print $2}' | sed 's/\/.*//g') #derive source ip and pass it for binding
- return 0
- elif nc -zv -s "$1" www.google.com 80 &> /dev/null; then
- fnLog 1 "Interface ($1) can see the internet, but we cannot see the VPN server ($sVPN_Server)."
- return 1
- else
- fnLog 1 "Interface ($1) cannot reach the internet."
- return 2
- fi
- }
- # This function takes an interface name as its only parameter and checks the status of its connection.
- function fnTestComms()
- {
- fnLog 1 "Checking the connection on NIC $1"
- if [ -n "$1" ]; then
- if sudo nmap -n -e $1 -sU -p1194 $sVPN_Server | grep 1194 | grep 'open ' &> /dev/null; then
- fnLog 1 "Burrow visible via interface $1."
- echo 0
- elif nc -zv -s "$1" www.google.com 80 &> /dev/null; then
- fnLog 1 "Interface ($1) can see the internet, but we cannot see the VPN server ($sVPN_Server)."
- echo 1
- else
- fnLog 1 "Interface ($1) cannot reach the internet."
- echo 2
- fi
- else
- fnLog 1 "Testing connection without NIC specification."
- if sudo nmap -n -sU -p1194 $sVPN_Server | grep 1194 | grep 'open ' &> /dev/null; then
- echo 0
- else
- echo 1
- fi
- fi
- }
- # takes an interface name and removes any default routes associated with it
- # if called without a parameter, it removes the first route, if there is more than one unless an
- #+ a force flag is passed in the form of a second parameter
- function fnRemDefRoute()
- {
- # store route before deleting
- local sIF
- local route
- # if called without a parameter, we will assume we're getting rid of the first default route
- if [ -z "$1" ]; then
- sIF=${dvaIFs_Routed[0]}
- else
- sIF="$1"
- fi
- if [[ (${#dvaIFs_Routed[@]} -gt 1) || ( -n "$2") ]]; then
- route=$(ip r s | grep default | grep "$sIF")
- fnLog 1 "Removing Default route <$route> and storing in file $sPath/route-$sIF.tmp"
- fnLog 3 "sIF is $sIF"
- echo "$route" > "$sPath"/route-"$sIF".tmp
- if sudo ip r d $(cat "$sPath"/route-"$sIF".tmp) &>> "$sPath"/"$sLogfile"; then
- fnLog 3 "Route delete result: $?"
- else
- fnLog 1 "Route delete unsuccessful."
- fi
- fi
- # god knows why this was so hard, but delete commands that threw errors are the following:
- #sudo ip r d $(ip r s | grep default | grep "$1")
- #sudo ip r d "$route"
- #sudo ip r d dev "$sIF"
- }
- function fnRestoreDefRoute()
- {
- fnLog 1 "Restoring route for interface $1, and removing temp file <$sPath/route-$1.tmp>."
- if [ -f "$sPath"/route-"$1".tmp ]; then
- local route
- route=$(cat "$sPath"/route-"$1".tmp)
- fnLog 3 "exact command used: \"sudo ip r a $route\""
- #if sudo ip r a "$route"; then
- fnLog 1 "Route file located. Attempting restore now."
- if sudo ip r a $(cat "$sPath"/route-"$1".tmp) &>> "$sPath"/"$sLogfile"; then
- fnLog 3 "Restore route result: $?"
- rm "$sPath"/route-"$1".tmp
- fnLog 1 "Route restored, $sPath/route-$1.tmp deleted."
- else
- fnLog 1 "Error restoring route. Result: $?"
- fi
- fi
- }
- ######################################
- ## command line option functions ##
- ######################################
- function fnInvalidSwitch()
- {
- echo "Invalid parameter: $1"
- echo "Try [ -h | --help ]"
- echo "Exiting."
- exit 1
- }
- function fnUsage()
- {
- cat <<USAGE
- Usage: `basename $0` [Options ...]
- If called with no options, it will simply attempt to establish or maintain a connection to the VPN server.
- Options:
- --start Removes the noconnect lock file, and then starts the VPN.
- Takes an optional parameter of a bind address
- --stop Stops the VPN and locks it by creating the noconnect lock file.
- --status Reports the status of the VPN process.
- [vars|connetion]
- --remove Removes the default route associated with the specified NIC from the routing table.
- If no NIC is specified, it removes the first route in the table as long as it's not the only default route.
- -v [n] Sets the verbosity to n, or if n is not specified, sets it to 2.
- 0 = silent, or no logging
- 1 = logging enabled \(default\)
- 2 = noisy logging, or echoed logging
- 3 = noisy debug logging
- 4 = as loud as it gets. debug logging with broadcast to all users
- -h, --help Displays this help message.
- USAGE
- exit 1
- }
- # needs to be expanded
- function fnStatus()
- {
- if [ -z "$1" ]; then
- # Is the
- if pgrep vpn &> /dev/null; then #ps aux | grep -q $(cat /var/run/openvpn.pid)
- echo "Open VPN client is running."
- local sVPN_Rtr_IP
- sVPN_Rtr_IP=$(ip a s tun0 | grep 'inet ' | sed 's/.*inet //' | sed 's/\.[0-9]\{1,3\}\/.*/\.1/')
- if ping -c1 -W3 "$sVPN_Rtr_IP" &> /dev/null; then
- echo "Tunnel is up."
- else
- echo "Tunnel is down."
- if nc -zv google.com 80 &> /dev/null; then
- if sudo nmap -n -sU -p1194 $sVPN_Server | grep 1194 | grep 'open ' &> /dev/null; then
- echo "We can see the Burrow." # how did we get here?
- else
- echo "Internet is up."
- fi
- else
- echo "Check internet connection."
- fi
- fi
- else
- echo "VPN is stopped."
- fi
- if [ -f "$sPath"/noconnect ]; then
- echo "\"noconnect\" file is present"
- else
- echo "\"noconnect\" file not found; gate is open"
- fi
- echo "Cronstatus for $USER: \"$(sudo cat /var/spool/cron/crontabs/$USER | grep $0)\""
- echo "Postrouting chain:"
- sudo iptables -t nat -S | grep '\-A POSTROUTING'
- else
- nVerbosity=3
- case "$1" in
- vars)
- fnLog 3 "Variable status Requested."
- fnLog 3 "Path set to $sPath"
- fnLog 3 "Primary local bind address set to ${saIPs[0]}"
- fnLog 3 "Secondary local bind address set to ${saIPs[1]}"
- fnLog 3 "Server set to $sVPN_Server"
- fnLog 3 "Config set to $sConfig"
- ;;
- connection)
- ;;
- esac
- fi
- }
- ######################################
- ## main program, VPN management ##
- ######################################
- function fnMain()
- {
- # If there are no objections, let's get this VPN started.
- # Potential objections include, the VPN is already running,
- #+ as well as the presence of 'noconnect' file in the working directory.
- if pgrep vpn &> /dev/null; then
- if ping -c1 -W3 -4 10.8.0.1 &> /dev/null; then
- fnLog 3 "VPN is running and the tunnel router is visible."
- # first we figure out which IF we're using for internet, and check if the other one has the appropriate SNAT/MASQUERADE rule in the nat table
- # this check should only be performed after we have successfully connected to the VPN
- if [ ! -f "$sPath"/noconnect ]; then
- fnLog 3 "Checking the NAT's postrouting chain."
- local sAV_IF
- case "${dvaIFs_Routed[0]}" in
- wlan0) sAV_IF="eth0";;
- eth0) sAV_IF="wlan0";;
- esac
- case "$(fnIPtables "check" "$sAV_IF")" in
- 0) fnLog 3 "POSTROUTING chain looks good." ;;
- 1) fnIPtables "add" "$sAV_IF" ;;
- esac
- fi
- else
- fnLog 1 "VPN is running, but the tunnel router is not visible. Either the VPN server is restarting, or there is a connection issue."
- if [ $(fnTestComms ${dvaIFs_Routed[0]}) -ne 0 ]; then
- fnRemDefRoute
- fi
- fi
- elif [ -f "$sPath"/noconnect ]; then
- # don't connect if we find the noconnect file in the working directory
- fnLog 1 "Connection prevented by noconnect file"
- else
- fnLog 1 "VPN is not running, and 'noconnect' file is not preset. Attempting to establish connection."
- # if we can see the server with our primary default route, just connect
- if [ $(fnTestComms) -eq 0 ]; then
- fnLog 3 "UDP Port 1194 is visible on $sVPN_Server"
- fnVPN_Control "start"
- else
- fnLog 1 "Dealing with connectivity issues."
- # how many default routes do we have right now?
- case "${#dvaIFs_Routed[@]}" in
- 0) fnLog 1 "No default routes found. We need internet in order to proceed.";;
- 1)
- case "$(fnTestComms ${dvaIFs_Routed[0]})" in
- 0) fnVPN_Control "start" "$(fnNIC_to_IP ${dvaIFs_Routed[0]})" ;; # how did we get here? this should have been sorted out at the beginning of this if conditional
- 1) fnLog 1 "Our lone default route can see the internet, but cannot see our server $sVPN_Server";;
- 2) fnLog 1 "Our lone default route cannot see the internet.";;
- esac
- ;;
- 2)
- # this is where the two main interfaces eth0 and wlan0 are hard-coded in
- # we may want to abstract this out if we want to make this script useful for secondary wireless/wired NICs
- # so the first default route doesn't work for us, if the second does, our work is simple
- # if we can see out on route #2, delete route #1
- case "$(fnTestComms ${dvaIFs_Routed[1]})" in
- 0) fnRemDefRoute; fnVPN_Control "start" ;; # if the second nic can see the VPN server, that's good enough
- 1) fnLog 1 "Is the $sVPN_Server online?";; ## this is where internet management gets tricky
- 2) ;;
- esac
- ;;
- *) # this should never trigger
- for i in "${!dvaIFs_Routed[@]}"; do
- fnTestComms "${dvaIFs_Routed[$i]}"
- if [ "$?" -eq 0 ]; then
- fnLog 1 "Looks like we should be able to get out. However more logic may be necessary if there's a potential issue we haven't accounted for yet."
- exit 0
- fi
- done
- fnLog 1 "All available NICs failed to reach the internet. Exiting. "
- exit 1
- ;;
- esac
- fi
- fi
- }
- ##############################
- ## command line parsing ##
- ##############################
- # !!! add the ability to pass multi argument command line switches
- # parse command line
- POSITIONAL=()
- while [ "$#" -gt 0 ]; do
- # command switches without parameters
- if [[ $(echo "$2" | grep '\-') || ( -z "$2") ]]; then # if the command switch is bare, use default value and shift 1
- POSITIONAL+=("$1")
- case "$1" in
- # check for help first, as this will trigger an exit
- -h|--help)
- fnUsage
- ;;
- -v)
- nVerbosity=2
- echo "verbosity set to $nVerbosity"
- ;;
- # list of valid commands that trigger functions
- --start\
- |--stop\
- |--status\
- |--showlogs\
- |--clearlogs\
- |--remove\
- |--test)
- saCMDs+=("$1")
- saParams+=("") # iterate the parameter array to keep the indices congruent
- ;;
- *)
- if [ -n "$1" ]; then
- fnInvalidSwitch "$1"
- fi
- ;;
- esac
- shift
- # command switches with single parameters
- else # otherwise set parameter and shift 2
- POSITIONAL+=("$1")
- POSITIONAL+=("$2")
- case "$1" in
- -v)
- nVerbosity="$2"
- echo "verbosity set to $nVerbosity"
- ;;
- # valid commands that take parameters
- --start\
- |--status\
- |--remove\
- |--restore\
- |--test)
- saCMDs+=("$1")
- saParams+=("$2")
- ;;
- *)
- if [ -n "$1" ]; then
- fnInvalidSwitch "$1"
- fi
- ;;
- esac
- shift 2
- fi
- done
- set -- "${POSITIONAL[@]}" # restore position parameters, just because
- #echo "Positional == ${POSITIONAL[@]}"
- # process command line parameters
- for i in "${!saCMDs[@]}"; do
- case "${saCMDs[$i]}" in
- --start)
- if [ -f "$sPath"/noconnect ]; then
- fnLog 1 "Removing \"noconnect\" file."
- rm "$sPath"/noconnect
- fi
- fnVPN_Control "start" "${saParams[$i]}"
- ;;
- --stop)
- fnVPN_Control "stop"
- fnLog 1 "Creating \"noconnect\" file."
- touch "$sPath"/noconnect
- ;;
- --status)
- fnStatus "${saParams[$i]}"
- ;;
- --showlogs)
- echo "*** connection.log ***"
- cat "$sPath"/"$sLogfile"
- echo "*** vpn.log ***"
- sudo cat "$sPath"/vpn.log
- ;;
- --clearlogs)
- echo -n > "$sPath"/"$sLogfile"
- echo -n | sudo tee "$sPath"/vpn.log
- echo "Logs cleared."
- ;;
- --remove) # !!! need to add input sanitization for parameters on remove/restore
- fnRemDefRoute "${saParams[$i]}"
- ;;
- --restore)
- fnRestoreDefRoute "${saParams[$i]}"
- ;;
- --test) ## this is just for some debugging, disregard
- #fnIPtables "add" ${saParams[$i]}
- fnIPtables "remove" ${saParams[$i]}
- ;;
- esac
- done
- # if no long commands were specified, run mainline
- if [ "${#saCMDs[@]}" -eq 0 ]; then
- fnMain
- fi
- #fnLog 3 "Script done."
- #############
- # End #
- #############
Add Comment
Please, Sign In to add comment