Skip to content

Instantly share code, notes, and snippets.

@daemonhorn
Last active July 4, 2023 00:33
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save daemonhorn/abf8c6ad13b1140e4d5459b7f97027a0 to your computer and use it in GitHub Desktop.
Save daemonhorn/abf8c6ad13b1140e4d5459b7f97027a0 to your computer and use it in GitHub Desktop.
Wireguard on PFSense

Wireguard Setup on PfSense

Please read official documentation from PfSense project here:

## THIS SECTION is OUTDATED. *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 packages on PfSense machine

install Wireguard and the qr code generater packages from the pkg repo

pkg install wireguard libqrencode

Disable FreeBSD pkg repos again to prevent accidential 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

Configuration

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

cd /usr/local/etc/wireguard
umask 077
# Host #1 (PfSense server)
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)
cd /usr/local/etc/wireguard
pkg install 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

Create configuration files

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
# Add the following lines
[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, and 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
# Add the following lines
[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
  1. Make sure wireguard is installed from the Android app store (Google play, etc.)

  2. Launch the wireguard app on your phone

  3. Select "+" icon on bottom right to create a new profile

  4. Select "Create from QR code"

  5. Point the phone at the QRcode displayed in your ssh session from previous step above. If it scrolled off, feel free to re-execute qrencode command

  6. Name the tunnel (e.g. pfsense)

  7. select the tunnel to view the configuration and make sure it looks sane

  8. 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

  9. Check the config on the server wg show

  10. Check the interface ifconfig server0

  11. Check the routing table netstat -rn6W

  12. 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

TROUBLESHOOTING

If for some reason things do not work, try running tcpdump on the pfsense side on wireguard interface "server0" to see inside tunnel traffic

tcpdump -vvv -i server0 
  • check wg running config as needed. Make sure that every peer has allowed ips 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 See man wg and man wg-quick 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
@alirz1
Copy link

alirz1 commented Apr 26, 2020

Hi,

Would you have some examples of these floating NAT rules that have to be added to route traffic between WAN/LAN/WG Tunnler interface?
Thanks

@alirz1
Copy link

alirz1 commented Apr 26, 2020

Looks like im almost there but not sure what kind of nat rules i need to put. Right now,If i tcpdump on my pfsense internet side interface, i see the incoming connection attempts from thephone. But if i trace on the TUNWG0 interface i dont see anything. which tells me nothing is going from the WAN to the VPN(OPT4/TUNWG0) interface. Would anyone have any tips on what im missing? Thanks

@alirz1
Copy link

alirz1 commented Apr 28, 2020

got it all to work. But there is some odd DNS behaviour. At somepoint DNS via the wireguard vpn stops working and i have to restart the dns service on the pfsense for it to work again. Note, regular DNS on the lan is not affected.

@alirz1
Copy link

alirz1 commented Apr 28, 2020

To over come the “no dns” issue after a reboot. i installed the "shellcmd" package from package manager. It can launch commands during/after boot.
I added the following command in there to run after reboot.

sleep 30 && pfSsh.php playback svc restart unbound

Had to put a sleep because restarting dns right after reboot doesnt fix the issue.
Wish this get resolved eventually when this whole thing makes it into PFsense officially. I dont like these patchy fixes that i lived with for years when i was suing dd-wrt/openwrt years ago

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment