i try to find the best wireguard/vpn server for certain endpoints at the moment, when im more familiar with this stuff i want this a bit more interactive and selective about the endpoints.
im reading the content of /etc/wireguard via ls into an array (ls for this should’nt be used, but works so far)
im looping through the config files and setup new vpn via wg-quick (only this supports Address= in config, but is limited to 16 bytes as config name, yey)
then i want to read the output of curl into a new array and want to output is after the loop again with the wireguard-config used. at the moment i echo the config name and curl speed, this worked. but somehow i cant get the 2nd loop to get working for doing the array of the speeds.
it seems that $avg_speed doenst count correctly when doing array output. my first time working with arrays and bash, had some pascal in school ages ago. made alot copy&paste for this.
Summary
#!/bin/bash
configs=( $(ls /etc/wireguard/ | sed 's/\.[^.]*$//') )
target="http://speedtest.fra1.de.leaseweb.net/10mb.bin"
for ((i=0; i<${#configs[@]};i++)); do
wg-quick down "${configs[i]}" > /dev/null 2>&1
done
printf 'VPN-Count:%i\n' "${#configs[@]}"
for ((i=0; i<${#configs[@]};i++)); do
#do something to each element of array
wg-quick down "${configs[i]}" > /dev/null 2>&1
echo "${configs[$i]}"
wg-quick up "${configs[i]}" > /dev/null 2>&1
sleep 2s
for ((c=0; c<${#configs[@]}; seq (${#configs[@]})) do
avg_speed=( $(curl -Ssfq -w '%{speed_download}' --url $target --output "/dev/null"))
echo "${avg_speed[$c]}"
echo "${#avg_speed[@]}"
done
sleep 2s
wg-quick down "${configs[i]}" > /dev/null 2>&1
printf '%s,%f\n' "${configs[$i]}" "${avg_speed[$c]}"
done
dont mind all the echos there, trying to figure out when something happens.
i googled some more and found that i have to declare an array first and count the output of curl with +. used some other variation with.
#!/bin/bash
configs=( $(ls /etc/wireguard/ | sed 's/\.[^.]*$//'))
target="http://hel.icmp.hetzner.com/100MB.bin"
declare avg_speed=()
echo "removing all wireguardlinks..."
for ((i=0; i<${#configs[@]};i++)); do
wg-quick down "${configs[i]}" > /dev/null 2>&1
done
echo "done."
sleep 1
for ((i=0; i<${#configs[@]};i++)); do
#do something to each element of array
wg-quick up "${configs[i]}" > /dev/null 2>&1
sleep 5s
avg_speed+=( $(curl -Ssfq -w '%{speed_download}' --url $target --output "/dev/null"))
# printf '%i\n' "${avg_speed[$i]}"
sleep 5s
wg-quick down "${configs[i]}" > /dev/null 2>&1
printf '%s-%.3f\n' "${configs[$i]}" "$(echo "${avg_speed[$i]}/1000000" | bc-l )"
sleep 1s
done
echo "done"
trying to figure out how to modify the decimal radix in printf so i get mbits and not bytes.
edit//
it does what it should do, now ive to make it pretty.
*8 to get from Bytes to bits, and your average should be against the number of runs, not a million.
You appear to be pulling times per config based on the number of configs.
Consider setting the number of runs before your outer “i” loop.
runs_per_config=5;
Not sure about using seq in a loop constructor. Try:
for((c=0; c<${runs_per_config}; c++)); do
You don’t need an array to hold the individual times. Before the inner loop, set a var to zero then add to it:
ttl_speed=0;
(In fact, avg_speed+= can be problematic based on your version of bash. avg_speed[$i] may give better results.
The average gets set after the “c” loop completes:
average_time=$(echo -e “${ttl_time} / ${runs_per_config}” | bc -l);
print f’%s,%.3f\n’ “${configs[$i]}” “${average_time}”;
Why are you looping through configs twice? Also, this loop is a bit funky in general?
Typically you will do a C-style for-loop, or a foreach-loop, but here you appear to be conflating the two syntaxes.
Your bug is on this line. You’re declaring a new array with a single element each time through the loop. You want to append the array (or use the loop index) instead. Or you can declare an associative array.
I am avoiding work and need to brush up on my shell scripting, so I took a stab at writing this like I would for a client. I went a little wild with it but I was challenging myself and having fun. Take it or leave it.
speedtests
#!/bin/bash
# N.B. https://gist.github.com/mohanpedala/1e2ff5661761d3abd0385e8223e16425
set -eEuo pipefail
IFS=$'\n\t'
readonly target="http://speedtest.fra1.de.leaseweb.net/10mb.bin"
readonly wireguard_dir="/etc/wireguard"
wg_quick() {
# N.B. We want to capture the output of wg-quick and only show it if there's
# an error.
local tmpfile; tmpfile=$(mktemp)
# N.B. This cleans up the temporary file whether the function returns zero
# (success) or not.
trap 'rm -f "$tmpfile"; trap - RETURN' RETURN
local rc=0
wg-quick "$2" "$1" >/dev/null 2>"$tmpfile" || rc=$?
if (( rc != 0 )) ; then
cat "$tmpfile" >&2
fi
return $rc
}
up() {
wg_quick "$1" up
}
down() {
wg_quick "$1" down
}
is_up() {
local ifname="$1"
local operstate_file="/sys/class/net/$ifname/operstate"
if [[ ! -f "$operstate_file" || ! -r "$operstate_file" ]] ; then
return 2
fi
local operstate; read -r operstate <"$operstate_file"
if [[ "$operstate" == "down" ]] ; then
return 1
fi
return 0
}
is_down() {
! is_up "$1"
}
down_if_up() {
is_down "$1" || down "$1"
}
bytes_to_bps() {
numfmt --to=iec --suffix=bps $(( $1 * 8 ))
}
curl_target() {
curl -sSfq -w '%{speed_download}' --url "$target" --output /dev/null
}
speedtest() {
local ifname=$1
up "$ifname"
results[$ifname]=$(bytes_to_bps $(curl_target))
sleep 2
down "$ifname"
}
log() {
local level=${1^^}
shift
# N.B. Write to stderr so user can capture the results from stdout.
echo "$level: $*" >&2
}
get_ifnames() {
if [[ ! -d "$wireguard_dir" || ! -r "$wireguard_dir" ]] ; then
log error "Unable to list configuration files in $wireguard_dir"
return 2
fi
# N.B. This makes the wildcard work properly if there are no WireGuard
# configuration files in the directory.
shopt -s nullglob
local config
for config in "$wireguard_dir"/*.conf ; do
basename "$config" .conf
done
shopt -u nullglob
}
loop() {
# Run the given callback function for each loop interation. The default
# callback function is the no-op bash function `:'.
local __callback=${1:-:}
if (( $# > 0 )) ; then
# N.B. This lets us pass along any "extra" arguments to the callback
# function.
shift
fi
local ifname
for ifname in "${ifnames[@]}" ; do
log info " * <$ifname>"
"$__callback" "$ifname" "$@"
done
}
main() {
# We'll store speedtest results--in bytes/sec--in an associative array, indexed
# by interface name.
# TODO: Make these local variables and pass them by reference to `loop' and
# `speedtest' or otherwise fix those methods to not directly reference them.
declare -a ifnames=($(get_ifnames))
declare -A results
log info "Downing WireGuard interfaces..."
# N.B. Escaping the callback function name here has the same effect as
# quoting it, but I prefer this syntax to highlight the "oddity".
loop \down_if_up
log info "Done!"
log info "Speed-testing WireGuard interfaces..."
loop \speedtest
log info "Done!"
log info "Summarizing speedtest results..."
# N.B. We could re-use `loop' with another callback function, or simply loop
# through the associatve array as I've done here.
for ifname in "${!results[@]}" ; do
echo -e "$ifname\t${results[$ifname]}"
done
log info "Done!"
}
main
gives output in bytes, according to guides/manpages, it should be correct.
i “benchmark all” servers of airvpn, they got a bunch. the first loop removing them is just for removing “dead” wireguard servers when i canceled the script.
the results in the array, yeah, writing down just a single var should be okay with printf >> results.log e.g. i wanted to sort them later on by speed with the corresponding server. avg_speed+= worked for me.
i wasnt to so far look more detailed why bash wants that many rounded braces.
yeah, it just gave the first result and every other result was empty/0. avg_speed+= worked. i copy&pasted alot of stuff from stack exchange.
i will read your bash script, it looks long but not that dense.
when i had pascal in school, wasnt that much, we just made basic stuff and now im eager to “solve” my problem. i think i can learn alot by your script, you doing cases/catches. and maybe i should stick to one language for a while, looking at different things. for e.g. looked at influxdb it messes up how to think about the problem.