Skip to content

Instantly share code, notes, and snippets.

@zikeji
Last active January 25, 2023 07:19
Show Gist options
  • Save zikeji/144247cb20793a5a7c65653e5f7c572b to your computer and use it in GitHub Desktop.
Save zikeji/144247cb20793a5a7c65653e5f7c572b to your computer and use it in GitHub Desktop.
Use dig and fping generate multiple remotes for an AirVPN config, replacing existing remote(s), and ignoring IPs that aren't responding. Remotes are placed in order of lowest ping to highest.
#!/bin/bash
PACKAGE=$(basename "$0")
# check for missing packages
MISSING_DEP="no"
if ! command -v fping &> /dev/null; then
echo "$PACKAGE: fping is not installed."
MISSING_DEP="yes"
fi
if ! command -v dig &> /dev/null; then
echo "$PACKAGE: dig is not installed."
MISSING_DEP="yes"
fi
if [[ "$MISSING_DEP" == "yes" ]]; then
exit 1
fi
display_usage() {
echo "$PACKAGE"
echo ""
echo "Description:"
echo " Use dig and fping generate multiple remotes for an AirVPN config, replacing existing remote(s), and ignoring IPs that aren't responding. Remotes are placed in order of lowest ping to highest."
echo ""
echo "Usage:"
echo " $PACKAGE [--port=<int>] [--query=<fqdn>] [--ipv4] [--ipv6] [--remote-random] [--in-place] [input-file]"
echo " $PACKAGE -h | --help"
echo ""
echo "Options:"
echo " -h, --help Show this screen."
echo " -p <int>, --port=<fqdn> Override the port supplied on each remote line [default: 443]."
echo " -q <fqdn>, --query=<fqdn> Supply the DNS record you wish to query to use the IPs from [default: ca.all.vpn.airdns.org]."
echo " -s <ns>, --server=<ns> The name server you wish to query the records against [default: ns1.airvpn.org]."
echo " -c <int>, --count=<int> Change the amount of pings ran by fping for more accurate ping sorting [default: 4]."
echo " -4, --ipv4 Only query IPv4."
echo " -6, --ipv6 Only query IPv6."
echo " -r, --remote-random Add remote-random to the AirVPN config (this will cause OpenVPN to randomize the server order when connecting)."
echo " -i<ext>, --in-place=<ext> Edit the file in place (makes backup if extension supplied), ignored if no input file is supplied."
echo ""
echo "The first non-option argument is the name of the input file; if no input file is specified, then the standard input is read. All other non-option arguments after the first are ignored."
}
TEMP=$(getopt -o 'h46rp:q:c:s:i::' --long 'help,ipv4,ipv6,remote-random,port:,query:,count:,server:,in-place::' -n "$PACKAGE" -- "$@")
if [ $? -ne 0 ]; then
display_usage
exit 1
fi
eval set -- "$TEMP"
unset TEMP
QUERY="ca.all.vpn.airdns.org"
SERVER="ns1.airvpn.org"
PORT="443"
IN_PLACE="no"
BACKUP_EXT=""
REMOTE_RANDOM="no"
IPV4="no"
IPV6="no"
PING_COUNT="4"
while true; do
case "$1" in
'-h'|'--help')
display_usage
exit 0
;;
'-4'|'--ipv4')
IPV4="yes"
shift
continue
;;
'-6'|'--ipv6')
IPV6="yes"
shift
continue
;;
'-r'|'--remote-random')
REMOTE_RANDOM="yes"
shift
continue
;;
'-q'|'--query')
QUERY="$2"
shift 2
continue
;;
'-p'|'--port')
PORT="$2"
shift 2
continue
;;
'-s'|'--server')
SERVER="$2"
shift 2
continue
;;
'-c'|'--count')
PING_COUNT="$2"
case $PING_COUNT in
''|*[!0-9]*)
echo "$PACKAGE: count provided is not an integer"
exit 1
;;
esac
shift 2
continue
;;
'-i'|'--in-place')
IN_PLACE="yes"
BACKUP_EXT="$2"
shift 2
continue
;;
'--')
shift
break
;;
*)
echo 'Internal error!' >&2
exit 1
;;
esac
done
INPUT=""
INPUT_FILE=""
USE_INPUT="yes"
for arg; do
if [[ "$INPUT_FILE" == "" ]]; then
INPUT_FILE="$arg"
fi
done
if [[ "$INPUT_FILE" != "" ]]; then
INPUT="$(cat "$INPUT_FILE" 2>&1)"
if [[ "$?" != "0" ]]; then
echo "$PACKAGE: can't read $INPUT_FILE: $(echo "$INPUT" | sed "s/cat: $INPUT_FILE: //" )"
exit 1
fi
fi
# if no input file specified, get stdin, if no stdin only output new remotes and not updated config
if [[ "$INPUT_FILE" == "" ]]; then
if [ -p /dev/stdin ]; then
INPUT=$(cat -)
IN_PLACE="no"
else
USE_INPUT="no"
fi
fi
# set the record type for dig
RECORD_TYPE="ANY"
if [[ "$IPV4" == "yes" && "$IPV6" == "no" ]]; then
RECORD_TYPE="A"
elif [[ "$IPV4" == "no" && "$IPV6" == "yes" ]]; then
RECORD_TYPE="AAAA"
fi
# get a list of all IPs from that record
IPS=$(dig +time=3 +tries=1 +short "$RECORD_TYPE" "$QUERY" @"$SERVER" 2>&1)
if [[ "$?" != "0" ]]; then
echo "$PACKAGE: error during dig lookup"
echo "$IPS"
exit 1
fi
# if dig gave us no results we can't really do anything, inform the user and exit
if [[ "$IPS" == "" ]]; then
echo "$PACKAGE: dig query returned no results, try adjusting your query flag, use the default, or double check your DNS"
exit 1
fi
# test the IPs for working IPs and sort the results by lowest ping,
RESULTS=$(fping -a --count="$PING_COUNT" --timeout=500 $IPS 2>&1 1>/dev/null | awk -F' +|/' '/min\/avg\/max/ {print $15 "ms", $1}' | sort -nk1)
if [[ "$RESULTS" == "" ]]; then
echo "$PACKAGE: fping found no reachable IPs"
exit 1
fi
# generate our remotes to be added to the config file
REMOTES=""
while IFS= read -r line; do
IP=$(awk '{print $2}' <<< "$line")
PING=$(awk '{print $1}' <<< "$line")
if [[ "$REMOTES" == "" ]]; then
REMOTES="remote $IP $PORT # $PING"
else
REMOTES="$REMOTES
remote $IP $PORT # $PING"
fi
done <<< "$RESULTS"
# prepend remote-random directive if requested
if [[ "$REMOTE_RANDOM" == "yes" ]]; then
REMOTES="remote-random
$REMOTES"
fi
# if no input file or stdin are provided, output the remotes we generated to stdout
if [[ "$USE_INPUT" == "no" ]]; then
echo "$REMOTES"
exit
fi
# remove remotes & remote-random
INPUT=$(echo "$INPUT" | sed -n '/remote-random.*$/!p')
INPUT=$(echo "$INPUT" | sed -n '/remote\ .*$/!p')
# prepend our generated remotes to the input config
INPUT="$REMOTES
$INPUT"
if [[ "$IN_PLACE" == "yes" ]]; then
# user wants an inplace update, check if they want to backup the original and copy that before overwriting
if [[ "$BACKUP_EXT" != "" ]]; then
cp "$INPUT_FILE" "$INPUT_FILE.$BACKUP_EXT"
fi
echo "$INPUT" > "$INPUT_FILE"
else
# output to stdout
echo "$INPUT"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment