Read other materials first to familiarize yourself with wireguard and wireguard on FreeBSD / Android: genneko has a nice writeup that is easy to follow and much better formatting here:
https://genneko.github.io/playing-with-bsd/networking/freebsd-wireguard-android/
Backup your entire pfsense system, or run this on a vm after a good snapshot. Failure to do this can easily break you, you have been warned. This is entirely unsupported. Wireguard on PFSense is experimental at this time. Nothing has been tested by me on non AMD64 arch, so unsure if arm pkgs are available. Backup your package database just in case since this impacts core PFSense runtime dependancies.
su root
pkg backup -d /root/backup.pkgdb
Modify your pfsense pkg configuration to allow generic FreeBSD binary packages.
Remove any lines related to disabling FreeBSD repo (usually first line or two)
vi /usr/local/etc/pkg/repos/PfSense.conf
mv /usr/local/etc/pkg/FreeBSD.conf /usr/local/etc/FreeBSD.old
Verify sane configuration of actual FreeBSD pkg repo man pkg.conf on FreeBSD and make modifications if needed
cat /etc/pkg/FreeBSD.conf
Update package database to reflect the new FreeBSD generic repo
pkg update
Install Wireguard and the qr code generater packages from the pkg repo
pkg install wireguard libqrencode
Disable FreeBSD pkg repos again to prevent accidental breakage and just leave pfsense pkg repo
mv /usr/local/etc/pkg/FreeBSD.old /ur/local/etc/pkg/FreeBSD.conf
Update package database to reflect removal of the FreeBSD generic repo
pkg update
Generate pfsense server and one roaming android client keys. Feel free to add as many "client" hosts as desired
By default wg-quick looks in /etc/wireguard
and /usr/local/etc/wireguard
for configuration files. Feel free to place
wherever desired and symlink as appropriate if not using the default location(s).
Private keys should be protected, and not copied around (except android via point-to-point qr code for ease of data entry)
Public keys should be generate on clients and server and need to be available via copy/paste or scp to the other endpoint config
-
Host #1 (PfSense server)
cd /usr/local/etc/wireguard umask 077 wg genkey > pfsense.private wg pubkey < pfsense.private > pfsense.public
-
Host #2 (android client)
wg genkey > android.private wg pubkey < android.private > android.public
-
On another host (#3) we want in this tunnel (say ssh into a FreeBSD client vm in the cloud)
pkg install wireguard cd /usr/local/etc/wireguard umask 077 wg genkey > freebsd.private wg pubkey < freebsd.private > freebsd.public
-
On another host (#4) we want in this tunnel (say ssh into Debian 10 client vm in the cloud)
apt install wireguard cd /etc/wireguard umask 077 wg genkey > debian.private wg pubkey < debian.private > debian.public
Change interface names and inside tunnel addresses to non-conflicting ipv4/ipv6 ranges as desired.
My ISP (Verizon FIOS) provides a nice large /56 IPV6 public address space via DHCPv6-PD, so I use one /64 from that allocation when I want global routing.
You can use IPv6 or IPv4 RFC1918/ULA/Link-local addresses as desired, but obviously dependant on your connectivity desires.
Globally routed IPv4 space is getting scarce, so this example focuses on IPv6.
Any givien tunnel can have multiple address ranges, however for simplicity of example and routing, we are sticking to a single IPv6 subnet here.
Example 1 (global ipv6 routable /56 address allocation from my ISP, I allocate one /64 for wireguard) PFSense (gateway) will be the server with a listening port (I use UDP 51820), all clients will use dynamic UDP ports. This means that the client needs to send traffic to the server before the server will send traffic to the client. To streamline this, you can use a PostUp configuration command from the client to send a ping (or other) packet to automate this handshake.
Post-Up does not work yet on Android or Windows, so just manually send some traffic using ping or a client app. As of the time of this writing, wireguard listen ports do NOT bind to a specific interface or address (wildcard IPv6/Ipv4 UDP socket bind is used), so ensure your pfsense firewall (floating) rules allow UDP 51820 for the desired address(es).
Do not use WAN rules, as pfsense UI does not know about the server0 interface, but floating will work fine as long as you do not sub-select interfaces. I use a dual-stack dns name (A and AAAA records) for the clients to find the server regardless of the outer protocol available. e.g (ds.pfsense.dyndns.foo). My cell provider does not alway have IPv6 connectivity, and this helps this case.
vi /usr/local/etc/wireguard/server0.conf
[Interface]
PrivateKey = <insert data from pfsense.private file generated above>
# This Address is inside the tunnel, use the first address in your selected /64 here XXXX:YYYY:ZZZZ::1
# Do not copy the 2001:DB8 address, as this is not a real IPv6 addr, and is used for documentation only
Address = 2001:DB8:4008:5320::1/64
# UDP Port *outside* the tunnel for listening for clients
ListenPort = 51820
[Peer]
PublicKey = <insert data from android.public file generated above>
# These Addresses are inside the tunnel, and is used for both routing, and ACL
AllowedIPs = 2001:DB8:4008:5320::2/128
[Peer]
PublicKey = <insert data from freebsd.public file generated above>
AllowedIPS = 2001:DB8:4008:5320::3/128
[Peer]
PublicKey = <insert data from debian.public file generated above>
AllowedIPS = 2001:DB8:4008:5320::4/128
Save the configuration file to server0.conf
in /usr/local/etc/wireguard
Generate Android configuration file on pfsense box. Create a QR code to import into your cell phone to ease data input of long key strings and eliminate typos.
vi /usr/local/etc/wireguard/android.conf
[Interface]
PrivateKey = <insert data from android.private file generated above>
# These Addresses are inside the tunnel.
Address = 2001:DB8:4008:5320::2/64
[Peer]
# These Addresses are inside the tunnel, and is used for both routing, and ACL
AllowedIPs = 2001:DB8:4008:5320::/64
PublicKey = <insert data from pfsense.public file generated above>
# This address or dns name and UDP port is outside the tunnel, and must be reachable
# IPv6 literal addresses are supported e.g.: [2001:DB8::32]:51820
Endpoint = ds.pfsense.dyndns.foo:51820
Save the configuration file to android.conf in /usr/local/etc/wireguard
Create a QR Code version of the configuration file so that you can import into your android client. If your ssh session / terminal is not properly setup to use UTF-8, this will likely have issues, but YMMV.
qrencode -t utf8 </usr/local/etc/android.conf
Make sure wireguard is installed from the Android app store (Google play, etc.)
- Launch the wireguard app on your phone
- Select "+" icon on bottom right to create a new profile
- Select "Create from QR code"
- Point the phone at the QRcode displayed in your ssh session from previous step above.
(qrencode -t utf8 </usr/local/etc/android.conf
) - Name the tunnel (e.g. pfsense)
- Select the tunnel to view the configuration and make sure it looks sane
Turn on the wireguard server using wg-quick which sets up usermode wireguard client and routing tables and intefaces with wg directly
wg-quick up server0
Check the config on the server
wg show
Check the interface
ifconfig server0
Check the routing table
netstat -rn6W
Turn on the Android client
Press on-off slider in the app, and wait a few seconds for state to change
Touch tunnel name (e.g.: pfsense) to see status.
Ping the pfsense server host outside the tunnel. I use "he.net - Network Tools" app on Android
Ping the pfsense server host inside the tunnel.
In the app view after you select the tunnel, should be a statement of number of bytes sent/received at the very bottom
e.g.: Transfer rx: 956 B, tx: 1.05 KiB
If for some reason things do not work, try running tcpdump
on the pfsense side on the wireguard interface "server0
" to see inside tunnel traffic.
tcpdump -vvv -i server0
Check wg running config as needed. Make sure that every peer has AllowedIPs
setup properly.
wg show
If you see nothing, try running tcpdump outside the tunnel looking for udp 51820
traffic where "em0
" is the interface that you expect wireguard server traffic based upon the Endpoint
statement in the client. - could be em1
or vtnet0
or vtnet1
, etc.
tcpdump -vvv -i em0 udp port 51820
If you see nothing, make sure that your pfsense floating firewall rules and address/dns names and UDP ports are setup properly. Since clients initiate connections to the server over UDP, most "normal" stateful client firewalls will track the state and allow the UDP traffic, however if you have an especially agressively locked down configuration, you may need to open a port. If this is the case, you may also want to configure a static UDP port for the client in the [Interface] section of the configuration file so you do not need to constantly deal with changing port numbers. If you have a NAT or firewall with agressive session timers, you can use the keepalive functionality of wireguard to keep the tunnel up.
After fixing configuration or firewall problems on the pfsense server, restart wireguard
wg-quick down server0 && wg-quick up server0
man wg
(8) and man wg-quick
(8) or lookup on the web since pfsense does not have man installed by default.
FreeBSD 12 example client configuration, including PostUp ping to intiate traffic
[Interface]
PrivateKey = <insert data from freebsd.private file generated above>
Address = 2001:DB8:4008:5320::3/64
# Ping the pfsense server inside tunnel address (note freebsd uses ping6 rather than ping -6)
PostUp = ping6 -c 2 2001:DB8:4008:5320::1
[Peer]
PublicKey = <insert data from pfsense.public file generated above>
Endpoint = ds.pfsense.dyndns.foo:51820
AllowedIPs = 2001:DB8:4008:5320::/64
Save configuation file to /usr/local/etc/wireguard/freebsd.conf
wg-quick up freebsd
Debian 10 example client configuration, including PostUp ping to initiate traffic
[Interface]
PrivateKey = <insert data from debian.private file generated above>
Address = 2001:DB8:4008:5320::4/64
# Ping the pfsense server inside tunnel address
PostUp = ping -6 -c 2 2001:DB8:4008:5320::1
[Peer]
PublicKey = <insert data from pfsense.public file generated above>
Endpoint = ds.pfsense.dyndns.foo:51820
AllowedIPs = 2001:DB8:4008:5320::/64
Save configuation file to /etc/wireguard/debian.conf
wg-quick up debian