How to choose a random wireguard server?

A LEDE router can be configured to connect to a random server among a list of pre-defined OpenVPN servers. Function random-remote as explained on https://community.openvpn.net/openvpn/wiki/Openvpn23ManPage

How can I achieve this on Wireguard connections so I am not always connected to one and the same wireguard server?

Read a byte from /dev/random, pick a server based on it.

1 Like

Sounds lovely, but, err, how to do this?

I'm not quite sure what's available on openwrt to do this in shell, but you can read say 4 bytes from /dev/random with

dd if=/dev/random bs=4 count=1 | ....

on a full featured distro you could then use hd (hexdump) to output a decimal in text format:

dd if=/dev/random bs=4 count=1 2>/dev/null | hexdump -e '1/4 "%u\n"'

dd if=/dev/random bs=4 count=1 2>/dev/null | hexdump -e '1/4 "%u\n"'
1169910383

You can read that into a shell variable and then do arithmetic on it.

The problem is... LEDE/OpenWRT doesn't have hexdump, so if someone knows how to do the same thing in a shell command that is available... let us know.

hexdump is part of busybox and AFAIK it is enabled by default.

2 Likes

hexdump is essential for sysupgrade on many devices, so you wouldn't even be able to disable it (well, you could, but you'd get to keep the pieces).

Hey awesome, at first I was using "hd" but of course that's not what's needed... so anyway my solution works

So, for example if you have 4 different servers numbered 1 2 3 4, you can do

SERVERNUM=$(echo $(($(dd if=/dev/random bs=4 count=1 2>/dev/null | hexdum
p -e '1/4 "%u\n"') % 4 + 1)))
1 Like

Thanks dlakelan. Would you have step-by-step instructions on how to configure the multiple servers and how to execute this automatically at startup?

There's no need to read 4 bytes when 1 will suffice.

There's no need to assume there are only 256 possible servers when we can just read 4 bytes :grinning:

Your example listed only 4 servers, so in fact 2 bits would have sufficed :wink: That's half a nibble.

I've been thinking about how to achieve this recently, too. Would something like this work?

/usr/local/sbin/servers.conf
gb2,185.16.85.130,gEor1tjIW4DmKfF24A8j8Ongw7B9pnhZ2n09xR5mH1o=
gb4,141.98.252.130,IJJe0TQtuQOyemL4IZn6oHEsMKSPqOuLfD5HoAWEPTY=
/usr/local/sbin/wg-server-randomizer.sh
WGSERVERIFACE="wg_mullvad"
FILE="servers.conf"

# pick a random server from list of all available servers
IFS=',' read -ra SETTINGS <<< "$(sort --random-sort $FILE | head -n 1)"


if [ ${#SETTINGS[@]} -eq 3 ]; then
    uci set network.$WGSERVERIFACE.description="${SETTINGS[0]}"
    uci set network.$WGSERVERIFACE.endpoint_host="${SETTINGS[1]}"
    uci set network.$WGSERVERIFACE.public_key="${SETTINGS[2]}"
    uci commit network
    reload_config
    ifup $WGSERVERIFACE >/dev/null 2>&1;
else
    echo "oops"
fi

and then run /usr/local/sbin/wg-server-randomizer.sh in cron?

BusyBox shell doesn't support it.

Grrrr.

This is really fragile: if servers.conf is malformed in anyway, it'll break. Notwithstanding, I think it should work?

/usr/local/sbin/servers.conf
gb2,185.16.85.130,gEor1tjIW4DmKfF24A8j8Ongw7B9pnhZ2n09xR5mH1o=
gb4,141.98.252.130,IJJe0TQtuQOyemL4IZn6oHEsMKSPqOuLfD5HoAWEPTY=
/usr/local/sbin/wg-server-randomizer.sh
#!/bin/sh
WGSERVERIFACE="wg_mullvad"
FILE="servers.conf"

# pick a random server
IN=`sed -n $(awk 'END {srand(); r=rand()*NR; if (r<NR) {sub(/\..*/,"",r); r++;}; print r}' $FILE)p $FILE`

DESCRIPTION=`echo "$IN" | awk -F "," '{print $1}'`
HOST=`echo "$IN" | awk -F "," '{print $2}'`
PUBKEY=`echo "$IN" | awk -F "," '{print $3}'`

uci set network.$WGSERVERIFACE.description="$DESCRIPTION"
uci set network.$WGSERVERIFACE.endpoint_host="$HOST"
uci set network.$WGSERVERIFACE.public_key="$PUBKEY"
uci commit network
reload_config
ifup $WGSERVERIFACE >/dev/null 2>&1;

opkg have bash in repository.

IFS="," read DESCRIPTION HOST PUBKEY << EOF
${IN}
EOF
&>/dev/null
1 Like

shellcheck is a great utility for catching bash-isms, as well as common errors (like redirects and quoting). It’s available both as an executable for desktop systems, as well as on their website.

1 Like

Top tip - thanks! Bookmarked for future use.

That's really elegant. Thanks!

Have made a couple of other changes since I needed to change the value of options in an unnamed section, but it's all working now. Here's my final script:

#!/bin/sh

WGPEER="wireguard_mullvad"
WGINTERFACE="mullvad"
FILE="servers.conf"

# pick a random server
IN=`sed -n $(awk 'END {srand(); r=rand()*NR; if (r<NR) {sub(/\..*/,"",r); r++;}; print r}' $FILE)p $FILE`

IFS="," read DESCRIPTION HOST PUBKEY << EOF
${IN}
EOF

uci set network.@"$WGPEER"[0].description="$DESCRIPTION"
uci set network.@"$WGPEER"[0].endpoint_host="$HOST"
uci set network.@"$WGPEER"[0].public_key="$PUBKEY"
uci commit network
reload_config
ifup $WGINTERFACE &>/dev/null

Thanks both. Much appreciated.

JFTR: It's a bit overkill setting the config file and especially saving it every time you rotate the server. I'm certainly not a member of the "oh my god the flash memory will die" camp, but depending on how often you rotate and write your config you might actually introduce some wear.

But most of all, it's not necessary and quite inelegant. Wireguard can be told to connect an interface to a different server using

wg set <interface> peer <public key> endpoint "<host>:<port>"

after which it will resolve the host and connect to it immediately.

3 Likes

That's a fair comment.

Perhaps I'm mistaken, but won't:

wg set <interface> peer <public key> endpoint "<host>:<port>"

add an additional peer rather than replace the existing one? I think it also requires allowed-ips.

I'll give it a whirl a bit later and post back here with my findings.