Skip to content

Local VPN Router on FreeBSD

Purpose

This guide describes how to build a FreeBSD-based VM that nails up a VPN tunnel to a provider and can be used as a local gateway for other systems.

Adding this system to your network will add another gateway to your network, but instead of routing traffic directly to the public Internet, it will route traffic through the VPN before reaching the public Internet.

Servers on the public Internet will see your systems connecting from the VPN Endpoint's IP address instead of yours.

graph LR
    A[System] --> E
    B[System]
    C{VPN VM} -. encrypted .-> D
    D{VPN Endpoint} --> E
    E((Public Internet))
    B --> C

Requirements

A FreeBSD-based router doesn't need much CPU or RAM to work well.

A single (v)CPU system with a gigabyte of RAM can easily handle 500Mbit/sec of traffic across the VPN.

You'll need a VPN account with a provider that provides OpenVPN configuration files and does not log.


Procedure

Operating System

Install FreeBSD

This guide assumes you have installed and configured FreeBSD and the Packages system.

The FreeBSD Handbook is an excellent resource if you run into problems.


Configure Operating System

rc.conf

All of FreeBSD's system configuration options are set in the /etc/rc.conf file.

These were probably configured during the OS installation. It doesn't hurt to verify that they are set properly.

Enable these options for your system in the configuration file:

Option Description
clear_tmp_enable="YES" clear the contents of /tmp on boot
syslogd_flags="-ss" do not log messages from remote machines, and do not listen on network sockets
sendmail_enable="NONE" do not start the sendmail daemon
sshd_enable="YES" start the sshd daemon on boot
ntpdate_enable="YES" run ntpdate to sync time on boot
ntpd_enable="YES" start ntpd on boot to keep time in sync
dumpdev="NO" disable saving crash dumps to disk

When complete, your /etc/rc.conf file should contain these entries:

clear_tmp_enable="YES"
syslogd_flags="-ss"
sendmail_enable="NONE"
sshd_enable="YES"
ntpdate_enable="YES"
ntpd_enable="YES"
dumpdev="NO"

sysctl.conf

The /etc/sysctl.conf file is used to control kernel tunables.

These were probably configured during the OS installation. It doesn't hurt to verify that they are set properly.

Enable these options for your system in the configuration file:

Option Description
security.bsd.see_other_uids=0 users only see their own processes
security.bsd.see_other_gids=0 groups only see their own processes
security.bsd.see_jail_proc=0 don't allow users to see inside jailed processes
security.bsd.unprivileged_read_msgbuf=0 unprivileged processes can't read the kernel message buffer
security.bsd.unprivileged_proc_debug=0 unprivileged processes may not use process debugging
kern.randompid=1 calculate PIDs by the modulus of an integer
kern.elf32.aslr.enable=1 enable ASLR for 32bit systems
kern.elf32.aslr.pie_enable=1 enable PIE for 32bit systems
kern.elf32.aslr.honor_sbrk=0 enable SBRK for 32bit systems
kern.elf64.aslr.enable=1 enable ASLR for 64bit systems
kern.elf64.aslr.pie_enable=1 enable PIE for 64bit systems
kern.elf64.aslr.honor_sbrk=0 enable SBRK for 64bit systems

When complete, your /etc/sysctl.conf file should contain these entries:

security.bsd.see_other_uids=0
security.bsd.see_other_gids=0
security.bsd.see_jail_proc=0
security.bsd.unprivileged_read_msgbuf=0
security.bsd.unprivileged_proc_debug=0
kern.randompid=1
kern.elf32.aslr.enable=1
kern.elf32.aslr.pie_enable=1
kern.elf32.aslr.honor_sbrk=0
kern.elf64.aslr.enable=1
kern.elf64.aslr.pie_enable=1
kern.elf64.aslr.honor_sbrk=0

Enable VPN

Your VPN provider should provide you with OpenVPN configuration files for your client.

Install the OpenVPN client:

pkg install openvpn

Copy the OpenVPN client configuration file to the /usr/local/etc/openvpn/ directory. In this example, the configuration filename is openvpn.ovpn.

Edit the /etc/rc.conf file and enable the OpenVPN client:

openvpn_enable="YES"
openvpn_if="tun"
openvpn_configfile="/usr/local/etc/openvpn/openvpn.ovpn"

Start the VPN client:

/usr/local/etc/rc.d/openvpn start

You should now have a tun0 interface:

root@vpn:~ # ifconfig tun0
tun0: flags=8043<UP,BROADCAST,RUNNING,MULTICAST> metric 0 mtu 1452
    options=80000<LINKSTATE>
    inet 10.4.112.148 netmask 0xffffff00 broadcast 10.4.112.255
    groups: tun
    nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
    Opened by PID 27105

Examining the routing table should show two default routes (one for default (your real gateway) and another for 0.0.0.0/1 (your VPN gateway)):

root@vpn:~ # netstat -rn | head -n 6
Routing tables

Internet:
Destination        Gateway            Flags     Netif Expire
0.0.0.0/1          10.4.112.1         UGS        tun0
default            192.168.200.1      UGS      vtnet0

Enable Firewall

FreeBSD's implementation of the pf firewall is easy to configure, offers good security features, and is included in the base system and kernel.

Enable the pf firewall by adding these files to the /etc/rc.conf file:

pf_enable="YES"
pflog_enable="YES"

The pf firewall ruleset is in /etc/pf.conf. Edit your config file to look like this:

ext_if="tun0"                                             # your vpn interface name
int_if="vtnet0"                                           # your ethernet interface name
set loginterface $ext_if                                  # set interface for logging
set optimization aggressive                               # aggressively expire connections
set block-policy return                                   # set default for 'block' action
set skip on lo0                                           # don't log anything about loopback
scrub on $ext_if all                                      # scrub all packets on VPN
nat on $ext_if from $int_if:network to any -> ($ext_if)   # NAT on VPN
pass out quick on $int_if inet all keep state             # keep state on connections
block in on $ext_if all                                   # block all ingress on VPN
pass out on $ext_if all                                   # allow all out on VPN

Start the pf and pflog services:

service pf start
service pflog start

Check the status of the pf firewall with /etc/rc.d/pf status (this one has been running for a while):

root@vpn:~ # /etc/rc.d/pf status
Status: Enabled for 96 days 16:08:49          Debug: Urgent

Interface Stats for tun0              IPv4             IPv6
  Bytes In                   4636817128872                0
  Bytes Out                   181189715493                0
  Packets In
    Passed                      3553031402                0
    Blocked                        3869284                0
  Packets Out
    Passed                         2431266                0
    Blocked                              0                0

State Table                          Total             Rate
  current entries                       47
  searches                     12591959181         1507.6/s
  inserts                         11646577            1.4/s
  removals                        11646530            1.4/s
Counters
  match                          235225998           28.2/s
  bad-offset                             0            0.0/s
  fragment                             522            0.0/s
  short                                  2            0.0/s
  normalize                              0            0.0/s
  memory                                 0            0.0/s
  bad-timestamp                          0            0.0/s
  congestion                             0            0.0/s
  ip-option                            173            0.0/s
  proto-cksum                            0            0.0/s
  state-mismatch                       735            0.0/s
  state-insert                           0            0.0/s
  state-limit                            0            0.0/s
  src-limit                              0            0.0/s
  synproxy                               0            0.0/s
  map-failed                             0            0.0/s

Configure Clients

You can now configure other systems on the network to use the FreeBSD VPN system as a default gateway.

Use the VPN system's IP non-VPN IP address (the one you ssh into) as the default gateway on a different system. All routed traffic for this system will now be routed across the VPN before reaching the public Internet.

Test this by querying for your public IP address from the commandline of a system routing traffic through the VPN. It should be a different IP address than what you see from a local system not routing traffic through the VPN.


Testing Client Routing

A system routing traffic through the VPN gateway should have a different public IP address than a system routing traffic through the network's default gateway.

This can easily be tested by using curl and ifconfig.co.


Public IP

Run curl ifconfig.co from a machine routing traffic through your regular gateway/router to get the public IP the traffic from your home network egresses from:

drexel@autobot:~$ curl ifconfig.co
8.66.124.88

Public IP Over VPN

Run curl ifconfig.co from a machine routing traffic through the VPN system to get the public IP address its traffic egresses from:

user@vpn:~$ curl ifconfig.co
181.41.206.185

This should be a different IP address than you got from a non-VPN routed system.