Setting up WireGuard vpn to work in restricted networks that block UDP traffic.
Something like this example:
The important part above is domain name of the server with IP address, which is
probably not what you want.
On the server side things might look something like this:
Note differences in the
In the
On a server we run
This will listen for a TLS connection on port 443 and will only forward packets
destined to a localhost and wireguard port.
Client will run this:
This will listen on port
You will then run
You probably want to make this auto-start on your server:
Extra advantage you gain by using
If at this point you try
Setup script reads configuration from
Save above to
I have only needed this on MacOS, Linux client will be very similar, but will likely need some changes here and there.
Don’t run
Make sure that
Make sure files under
Basic Idea
- Run wstunnel to tunnel UDP traffic to vpn server
- Configure local
wg-quick
to uselocalhost
as endpoint
- Have to do your own routing setup
- Possible issues with DNS when
wstunnel
needs to re-connect
Prerequisite
I’m going to assume that you have already got WireGuard working over UDP with Linux server in the cloud and MacOS client. If not, there are plenty of guides on-line. For me the main stumbling block was not realising early enough that even though WireGuard itself doesn’t have a concept of server/client, the way you configure the server and the way you configure clients is actually somewhat different. On a client side you have a single peer with the public key of your server and withEndpoint
pointing to the domain of your server,
you also probably want to configure DNS.Something like this example:
[Interface]
PrivateKey = oCkFT5ZmTJZapiCm2zZ/vNRhdVRFhKFhnkVFKRJW+2U=
Address = 10.10.0.2/24
DNS = 1.1.1.1
DNS = 8.8.8.8
[Peer]
PublicKey = xWdX6PjqZPG+So5ndzgjBa3OxSEgPA5Exi+GMLknHWA=
AllowedIPs = 0.0.0.0/0, ::0
Endpoint = yourhost.tld:51820
AllowedIPs = 0.0.0.0/0, ::0
, which tells
wg-quick
to route all the traffic (v4 and v6) through the tunnel when setting
up the connection. Also you should avoid using SaveConfig
option on the client
side as it will overwrite On the server side things might look something like this:
[Interface]
PrivateKey = AL7WeXT59GebMA5RLnI97fMarjKS1dnSFIDCLhxTymE=
Address = 10.10.0.1/24
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o ens3 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o ens3 -j MASQUERADE
ListenPort = 51820
[Peer]
PublicKey = kAodaLwCyX6t4Olxh0r6/ohxoIvYTQ24QIT/sijAAB0=
AllowedIPs = 10.10.0.2/32
[Peer]
...
[Interface]
section, it includes PostUp/PostDown
rules to setup/tear down packet forwarding from the wireguard interface (%i
)
to your main network interface (ens3
in this case). There is also ListenPort
directive and no DNS
.In the
[Peer]
section, AllowedIPs
is set to the value of Interface.Address
in the client config file, also Endpoint
is omitted. Each peer has to have
unique address, and different from that of a server.UDP Tunnel
Head over to wstunnel releases and download linux version for your server and MacOS version for the client.On a server we run
wstunnel -v -s wss://0.0.0.0/ --restrictTo 127.0.0.1:51820
Client will run this:
wstunnel -v --udp --udpTimeoutSec -1 -L 127.0.0.1:51820:127.0.0.1:51820 wss://yourhost.tld/
51820
on localhost only and forward these packets to
port 51280
on yourhost.tld
.Nginx as Proxy
If you would like to run webserver on the same machine that runswstunnel
then
you don’t want port 443
to be used solely for UDP tunnelling. With nginx
,
websockets tunnelling is possible with a configuration similar to below:Sample Nginx Config (click to expand)
wstunnel
server on port 33344
, binding to localhost
and
without TLS:wstunnel --server ws://127.0.0.1:33344 --restrictTo=127.0.0.1:51820
wstunnel: Systemd Service File
nginx
as reverse proxy is a kind of
“authentication”. Only requests made to
https://yourhost.tld/{longish-random-string}/...
will be forwarded to
wstunnel
server, and since we are using TLS {longish-random-string}
shouldn’t be visible to any middleman. You also get access logs and proper TLS
(wstunnel
only has one hard-coded certificate).Configure wg-quick to use UDP Tunnel
Copywg0.conf
into wg1.conf
and make this change[Peer]
PublicKey = xWdX6PjqZPG+So5ndzgjBa3OxSEgPA5Exi+GMLknHWA=
AllowedIPs = 0.0.0.0/0, ::0
- Endpoint = yourhost.tld:51820
+ Endpoint = 127.0.0.1:51820
wg-quick up wg1
, things won’t work for the following reasons:wg-quick
will route all traffic through tunnel including traffic toyourhost.tld
- We can solve this by adding custom route to
yourhost.tld
inPreUp
- We can solve this by adding custom route to
wg-quick
will route all traffic to127.0.0.1
through your default gateway, and so won’t be able to talk towstunnel
in the first place- No easy solution to this one, have to disable routing within
wg-quick
altogether withTable = off
option, then setup routing manually
- No easy solution to this one, have to disable routing within
wstunnel
will fail to connect due to DNS failure, since DNS traffic will be routed through a tunnel that hasn’t been established yet. This might not happen right away due to DNS caching, but will become a problem when trying to re-connect- Option 1: use IP address of the server on a client side, downside no
vhost
routing if usingnginx
, noTLS
verification. - Option 2: write current server IP to
/etc/hosts
- Option 3: run
dnsmasq
on client side, configurednsmasq
rather than/etc/hosts
- Option 1: use IP address of the server on a client side, downside no
wstunnel
and updating /etc/hosts
. To use it copy it to /etc/wireguard/
directory and add the following to your [Interface]
section of wg1.conf
[Interface]
PrivateKey = oCkFT5ZmTJZapiCm2zZ/vNRhdVRFhKFhnkVFKRJW+2U=
+ Table = off
+ PreUp = source /etc/wireguard/wstunnel.sh && pre_up %I
+ PostUp = source /etc/wireguard/wstunnel.sh && post_up %i %I
+ PostDown = source /etc/wireguard/wstunnel.sh && post_down %i %I
/etc/wireguard/{wg_interface}.wstunnel
REMOTE_HOST=yourhost.tld
REMOTE_PORT=51820
UPDATE_HOSTS='/etc/hosts'
# if using nginx with custom prefix for added security, configure it here
WS_PREFIX='E7m5vGDqryd55MMP'
# Can change local port of the wstunnel, don't forget to change Peer.Endpoint
#LOCAL_PORT=${REMOTE_PORT}
# If using dnsmasq can supply other file than /etc/hosts
# UPDATE_HOSTS='/usr/local/etc/dnsmasq.d/hosts/tunnels'
# Will send -HUP to dnsmasq to reload hosts
# USING_DNSMASQ=1
/etc/wireguard/wg1.wstunnel
and customise. Then all you have to
do is sudo wg-quick up wg1
. Behind the scenes wstunnel.sh
will:- Obtain current IP of
yourhost.tld
- Update
/etc/hosts
with current IP ofyourhost.tld
- Add custom route to
yourhost.tld
via default gateway - Launch client side of
wstunnel
(asnobody
) - Route all traffic through wireguard tunnel
- Clean up when you are done
sudo wg-quick down wg1
- stop tunnel app
- cleanup
/etc/hosts
Limitations
Currently Wi-Fi disconnects are likely to cause non-recoverable errors and will require bringing wireguard interface down and then back up manually. This is because route to the server is set up once and is not updated as you connect to a new router with possibly different gateway.I have only needed this on MacOS, Linux client will be very similar, but will likely need some changes here and there.
Notes on Security
It’s important to use--restrictTo=127.0.0.1:51820
option of wstunnel
on the
server as wstunnel
is not authenticated and you don’t want to let others use
your machine as a proxy. With that option “bad guys” would only be able to send
UDP packets to a port that is already open anyway and will appear silent unless
they have your private key.Don’t run
wstunnel
as root
, systemd file above launches wstunnel
as nobody
.Make sure that
wstunnel
on a client side listens on localhost
only, and
doesn’t run as root
.Make sure files under
/etc/wireguard/
are accessible by root
only,
wg-quick
runs as root
and so is wstunnel.sh
, and it sources wg1.wstunnel
as root
also, so make sure they are not writable by anyone except root
.Notes on Debugging
netstat -nr -f inet
display routing table for ipv4- Use
nc
(netcat) for testing UDP tunnel- server
nc -u -l 51820
(stop wireguard first) - client
nc -u 127.0.0.1 51820
- server
- Check your public ip:
curl http://api.ipify.org/
- If using
nginx
view logs:sudo tail -f /var/log/nginx/access.log
- Change
-q
to-v
on the server, thensudo journalctl -fu wstunnel
from https://kirill888.github.io/notes/wireguard-via-websocket/
https://github.com/erebe/wstunnel