Sunday, November 2, 2025

FreeBSD as a Network Router

FreeBSD is great operating system to be used as router, firewall, and VPN concentrator. When you install and configure FreeBSD router you should begin with standard FreeBSD server installation and configuration covered in another my blog post - Typical tasks after FreeBSD installation.

After typical FreeBSD server installation we can follow with configuration of other roles as

  • Firewall and NAT
  • WireGuard Site2site VPN tunneling
  • Dynamic routing / OpenBGPD
  • DNS
  • DHCP

In this blog post, I will document various roles basic configurations.

Network and Loopback interfaces

In a router, you typically have more network interfaces. In addition to setting up physical network interfaces, it is a pretty good idea to also set a specific IP address on loopback interface, because you can reference the router in a better way then IP address of one specific network interface. Loopback interface sits inside the router between all other network interfaces. Loopback is typically a special IP address having network mask /32 (255.255.255.255), so the network subnet of a single device.

Let's assume we have following 5 physical network interface (vmx0, vmx1, vmx2, vmx3, vmx4, vmx5), 1 virtual network interface (wg0), and loopback interface (lo0). We would like to achieve following IP configuration:

Interface (Loopback): lo0 IP Addr: 10.100.0.1 Net Mask: 255.255.255.255
Interface (WAN-1): vmx0 IP Addr: 10.100.6.254 Net Mask: 255.255.255.0
Interface (LAN-4): vmx1 IP Addr: 10.100.4.254 Net Mask: 255.255.255.0
Interface (LAN-5): vmx2 IP Addr: 10.100.5.254 Net Mask: 255.255.255.0
Interface (LAN-7): vmx3 IP Addr: 10.100.7.254 Net Mask: 255.255.255.0
Interface (LAN-8): vmx4 IP Addr: 10.100.8.254 Net Mask: 255.255.255.0
Interface (LAN-9): vmx5 IP Addr: 10.100.9.254 Net Mask: 255.255.255.0
Interface (VPN-1): wg0 IP Addr: 172.16.100.254 Net Mask: 255.255.255.0

The default Network Gateway (Default Router) is 10.100.6.1

Procedure to set network interfaces permanently (saved in /etc/rc.conf)

# Set IP settings on vmx interfaces
sysrc ifconfig_vmx0="inet 10.100.6.254 netmask 255.255.255.0"
sysrc ifconfig_vmx1="inet 10.100.4.254 netmask 255.255.255.0"
sysrc ifconfig_vmx2="inet 10.100.5.254 netmask 255.255.255.0"
sysrc ifconfig_vmx3="inet 10.100.7.254 netmask 255.255.255.0"
sysrc ifconfig_vmx4="inet 10.100.8.254 netmask 255.255.255.0"
sysrc ifconfig_vmx5="inet 10.100.9.254 netmask 255.255.255.0"     

# Set IP on loopback
sysrc ifconfig_lo0_alias0="inet 10.100.0.1/32"

# set default router (default gateway)
sysrc defaultrouter="10.100.6.1"

# WireGuard interface is created and configured automatically by WireGuard

Procedure to set network interfaces on running system

 # Set IP settings on vmx0 interface
ifconfig vmx0 inet 10.100.6.254 netmask 255.255.255.0

# Set IP alias on loopback
ifconfig lo0 alias 10.100.0.1/32

# Show the current IP settings on vmx0 interface
ifconfig vmx0

# Show the current IP settings on loopback (lo0) interface
ifconfig lo0 

# Show the current IP settings on all interfaces

ifconfig vmx0 

Firewall and NAT

The firewall is a typical role on the router, because we usually want to secure our network zones (typically LAN segments). Another typical requirement in IPv4 networks is NAT (Network Address Translation).

In FreeBSD you can choose between IPFW (original FreeBSD firewall) and PF (firewall ported from OpenBSD) firewalls. Below is the typical configuration of IPFW firewall with NAT (SNAT/DNAT) feature ...

Procedure to configure IPFW firewall

sysrc firewall_enable="YES"
sysrc firewall_script="/etc/ipfw.rules" # the script file with Firewall rules
sysrc firewall_nat_enable="YES"

Firewall Script File /etc/ipfw.rules

#!/bin/sh

# Define binaries
ipfw="/sbin/ipfw"

# Define interfaces
wan_if="vmx0"

# FLUSH EXISTING IPFW RULES
$ipfw -q -f flush
$ipfw -q nat flush

# DELETE EXISTING NAT RULES
$ipfw nat delete 1

# DNAT RULE
$ipfw nat 1 config if $wan_if redirect_port tcp 10.1.10.1:22 2222

# SNAT RULE
$ipfw add 50 nat 1 ip from any to any via $wan_if

# INIT FIREWALL RULES
$ipfw add 100 allow ip from any to any via lo0
$ipfw add 200 deny ip from any to 127.0.0.0/8
$ipfw add 300 deny ip from 127.0.0.0/8 to any
$ipfw add 400 deny ip from any to ::1
$ipfw add 500 deny ip from ::1 to any
$ipfw add 600 allow ipv6-icmp from :: to ff02::/16
$ipfw add 700 allow ipv6-icmp from fe80::/10 to fe80::/10
$ipfw add 800 allow ipv6-icmp from fe80::/10 to ff02::/16
$ipfw add 900 allow ipv6-icmp from any to any icmp6types 1
$ipfw add 1000 allow ipv6-icmp from any to any icmp6types 2,135,136

# CUSTOM ALLOW RULES STARTS HERE
$ipfw add 65000 allow ip from any to any

# EVERYTHING ELSE IS DENIED BY DEFAULT BY RULE 65535
# LET'S ADD ONE MORE DENY RULE, JUST IN CASE THE DEFAULT BEHAVIOR CHANGES
$ipfw add 65500 deny ip from any to any

Above firewall script allows every traffic because of rule 65000. If you want to implement zero trust between network segments, you should remove rule 65000 and allow only traffic you define.

Manual DNAT configuration management (it is not necessary, just for info)

# Show NAT Configuration
ipfw nat show config

# Add DNAT rule
ipfw nat 1 config if vmx0 redirect_port 10.1.10.1:22 2222  

# Delete DNAT rule
ipfw nat delete 1 

WireGuard Site2site VPN tunneling

I wrote another blog post about WireGuard installation and configuration - WireGuard VPN on FreeBSD.

Dynamic routing / OpenBGPD

You can run BGP on your router. In my homelabs I use openbgpd (port of BGP daemon from OpenBSD).

Let's assume we want following BGP configuration parameters.

Internal ASN: 65030
Internal Router ID: 172.16.101.25  
Remote ASN: 65030
Remote Router IP address: 172.16.100.4
Local Networks:
loopback network 172.16.101.25/32
LAN network 10.41.0.0/16 

Installation and configuration procedures below work with BGP configuration parameters above. Change it appropriately to your particular environment.  

Procedure to Install and Enable OpenBGPD

# Install OpenBGPD
pkg install -y openbgpd8 

# Enable OpenBGPD
sysrc openbgpd_enable="YES"

Configure your BGP in configuration file /usr/local/etc/bgpd.conf

AS 65030
router-id 172.16.101.25
fib-update yes

neighbor 172.16.100.4 {
    remote-as 65000
    descr "wg-server-c4c"
}

# Announce local LAN
network 172.16.101.25/32
network 10.41.0.0/16

# Optional: accept everything
allow from any
allow to any


Procedure to Start BGP

# Start OpenBGPD service
service openbgpd start 


Procedure to Restart BGP

# Restart BGP service
service openbgpd restart


Troubleshooting BGP

Show BGP neighbors.

 root@r1:~ # bgpctl show  
 Neighbor              AS  MsgRcvd  MsgSent OutQ Up/Down  State/PrfRcvd  
 wg-server-c4c      65000        7        6    0 00:01:23     11  
 root@r1:~ #   

Show Routing Information Base (RIB).

 root@r1:~ # bgpctl show rib  
 flags: * = Valid, > = Selected, I = via IBGP, A = Announced,  
     S = Stale, E = Error, F = Filtered, L = Leaked  
 origin validation state: N = not-found, V = valid, ! = invalid  
 aspa validation state: ? = unknown, V = valid, ! = invalid  
 origin: i = IGP, e = EGP, ? = Incomplete  
 flags vs destination       gateway      lpref  med aspath origin  
 AI*> N-? 10.41.0.0/16      0.0.0.0        100   0  i  
 *>   N-? 10.200.0.0/16     172.16.100.4   100   0  65000 i  
 *>   N-? 172.16.100.0/31   172.16.100.4   100   0  65000 i  
 *>   N-? 172.16.100.2/31   172.16.100.4   100   0  65000 i  
 *>   N-? 172.16.100.4/31   172.16.100.4   100   0  65000 i  
 *>   N-? 172.16.101.1/32   172.16.100.4   100   0  65000 i  
 *>   N-? 172.16.101.11/32  172.16.100.4   100   0  65000 65020 i  
 *>   N-? 172.16.101.12/32  172.16.100.4   100   0  65000 65020 i  
 AI*> N-? 172.16.101.25/32  0.0.0.0        100   0  i  
 *>   N-? 192.168.4.0/24    172.16.100.4   100   0  65000 65020 i  
 *>   N-? 192.168.5.0/24    172.16.100.4   100   0  65000 65020 i  
 *>   N-? 192.168.7.0/24    172.16.100.4   100   0  65000 65020 i  
 *>   N-? 192.168.8.0/24    172.16.100.4   100   0  65000 65020 i  
 root@r1:~ #   

Show Forwarding Information Base (FIB).

 root@r1:~ # bgpctl show fib  
 flags: B = BGP, C = Connected, S = Static  
     N = BGP Nexthop reachable via this route  
     r = reject route, b = blackhole route  
 flags prio destination                    gateway               
 S        1 0.0.0.0/0                      10.0.0.138  
 C        1 10.0.0.0/8                     link#5  
 C        1 10.0.0.0/24                    link#1  
 C        1 10.0.0.40/32                   link#3  
 C        1 10.41.4.0/24                   link#2  
 C        1 10.41.4.254/32                 link#3  
 B       19 10.200.0.0/16                  172.16.100.4  
 C        1 100.66.0.45/32                 link#4  
 C        1 100.85.205.94/32               link#4  
 C        1 100.93.97.58/32                link#4  
 C        1 100.96.56.81/32                link#4  
 C        1 100.97.143.39/32               link#4  
 C        1 100.100.100.100/32             link#4  
 C        1 100.102.179.91/32              link#4  
 C        1 100.108.60.51/32               link#3  
 C        1 100.119.48.90/32               link#4  
 C        1 127.0.0.1/32                   link#3  
 C        1 172.16.100.0/24                link#5  
 B       19 172.16.100.0/31                172.16.100.4  
 B       19 172.16.100.2/31                172.16.100.4  
 CN       1 172.16.100.4/31                link#5  
 B       19 172.16.100.4/31                172.16.100.4  
 CN       1 172.16.100.5/32                link#3  
 C        1 172.16.101.0/24                link#5  
 B       19 172.16.101.1/32                172.16.100.4  
 B       19 172.16.101.11/32               172.16.100.4  
 B       19 172.16.101.12/32               172.16.100.4  
 C        1 172.16.101.25/32               link#3  
 C        1 192.168.0.0/16                 link#5  
 B       19 192.168.4.0/24                 172.16.100.4  
 B       19 192.168.5.0/24                 172.16.100.4  
 B       19 192.168.7.0/24                 172.16.100.4  
 B       19 192.168.8.0/24                 172.16.100.4  
 C r      1 ::/96                          link#3  
 C        1 ::1/128                        link#3  
 C r      1 ::ffff:0.0.0.0/96              link#3  
 C        1 fd7a:115c:a1e0::/48            link#4  
 C        1 fd7a:115c:a1e0::53/128         link#4  
 C        1 fd7a:115c:a1e0::d735:3c33/128  link#3  
 C r      1 fe80::/10                      link#3  
 C        1 fe80::%lo0/64                  link#3  
 C        1 fe80::1%lo0/128                link#3  
 C r      1 ff02::/16                      link#3  
 root@r1:~ #   

That's it. Your BGP should work.

DNS

You can run DNS on your FreeBSD router. In this blog post, I will show you DNS (BIND 9.20) Installation and Configuration.

Let's assume our Internal domain is home.uw.cz

Procedure to Install and Enable BIND Name Server

# Install Bind
pkg install -y bind920

# Enable Bind (named)
sysrc named_enable="YES"

# Start Bind (named) service
service named start

Edit configuration file /usr/local/etc/namedb/named.conf 

// ... OTHER CONFIGURATION IS ABOVE

options {
    // All file and path names are relative to the chroot directory,
    // if any, and should be fully qualified.
    directory    "/usr/local/etc/namedb/working";
    pid-file    "/var/run/named/pid";
    dump-file    "/var/dump/named_dump.db";
    statistics-file    "/var/stats/named.stats";
        allow-query     { any; };
        allow-transfer  { any; };
        dnssec-validation no;

// If named is being used only as a local resolver, this is a safe default.
// For named to be accessible to the network, comment this option, specify
// the proper IP address, or delete this option.
    listen-on    { 127.0.0.1; 10.0.0.1; };

// If you have IPv6 enabled on this system, uncomment this option for
// use as a local resolver.  To give access to the network, specify
// an IPv6 address, or the keyword "any".
//    listen-on-v6    { ::1; };

// These zones are already covered by the empty zones listed below.
// If you remove the related empty zones below, comment these lines out.
    disable-empty-zone "255.255.255.255.IN-ADDR.ARPA";
    disable-empty-zone "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA";
    disable-empty-zone "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA";

// If you've got a DNS server around at your upstream provider, enter
// its IP address here, and enable the line below.  This will make you
// benefit from its cache, thus reduce overall DNS traffic in the Internet.
    forwarders {
        1.1.1.1; 8.8.8.8; 8.8.4.4;
    };

// OTHER CONFIGURATION IS ABOVE …
zone "home.uw.cz" {
        type primary;
        file "/usr/local/etc/namedb/primary/home.uw.cz.db";
};


Create zone file /usr/local/etc/namedb/primary/home.uw.cz.db

$TTL 3600
@       IN  SOA ns1.int.msp.businesscloud.cz. hostmaster.int.msp.businesscloud.cz. (
                2025110201  ; Serial number (YYYYMMDDnn)
                3600        ; Refresh (1 hour)
                900         ; Retry (15 minutes)
                604800      ; Expire (1 week)
                86400       ; Minimum TTL (1 day)
)
        IN  NS  ns1.home.uw.cz.
;        IN  NS  ns2.home.uw.cz.

; --- Host records ---
ns1                      IN  A   10.100.4.5
;ns2                      IN  A   10.100.4.6
 
; --- MGMT segment ---
jump-01                  IN  A   10.100.4.1
devops-01                IN  A   10.100.4.10
 
; --- OFFICE segment ---
apple-tv-01              IN  A   10.100.5.51
apple-tv-02              IN  A   10.100.5.52
 
; --- HOME-AUTOMATION segment ---
thermostat-01            IN  A   10.100.7.11
thermostat-02            IN  A   10.100.7.12
fve-01                   IN  A   10.100.7.21
 
; --- SERVER segment ---
server-01                IN  A   10.1.8.11
server-02                IN  A   10.1.8.12


Procedure to Reload Name Server configuration

# Reload Bind (named) service
service named reload


Procedure to Restart Name Server

# Restart Bind (named) service
service named restart  


To use Internal Name Server, edit configuration file /etc/resolve.conf

search home.uw.cz
nameserver 10.1.10.254 

That's it. Your name server should work now. 

DHCP

You can run DHCP on your FreeBSD router. In this blog post, I will show you KEA Installation and Configuration.

Procedure to Install and Enable KEA DHCP Server

# Install KEA
pkg install -y kea

# Enable KEA (dhcpd)
kea_enable="YES"
kea_dhcp4_enable="YES"


# Start KEA service
service kea start

Edit configuration file /usr/local/etc/kea/kea-dhcp4.conf

{
  "Dhcp4": {
    "interfaces-config": {
      "interfaces": [
        "re1"
      ]
    },
    "lease-database": {
      "type": "memfile",
      "persist": true,
      "name": "/var/db/kea/dhcp4.leases"
    },
    "option-data": [
      {
        "name": "domain-search",
        "data": "home.uw.cz"
      },
      {
        "name": "domain-name-servers",
        "data": "10.100.4.5"
      }
    ],
    "subnet4": [
      {
        "id": 1,
        "subnet": "10.100.4.0/24",
        "pools": [
          {
            "pool": "10.100.5.150 - 10.100.5.199"
          }
        ],
        "option-data": [
          {
            "name": "routers",
            "data": "10.100.5.254"
          }
        ]
      }
    ]
    "loggers": [
      {
        "name": "kea-dhcp4",
        "output_options": [
          {
            "output": "/var/log/kea/kea-dhcp4.log",
            "maxsize": 1048576,
            "maxver": 3
          }
        ],
        "severity": "INFO",
        "debuglevel": 0
      }
    ]
  }
}
 
 

Procedure to Reload DHCP Server configuration

# Reload KEA service
service kea reload

Procedure to Restart DHCP Server

# Restart KEA service
service kea restart  

Conclusion

In this blog post we have covered typical roles you can use on your FreeBSD router. Hope you find this useful and in case of any trouble, do not hesitate to use comments to ask for further information or report any bug or misconfiguration in my configuration examples.

FreeBSD was the major router and server operating system on the Internet back in 1994 until early 2000' when Linux take somehow the dominance as the Internet Operating System. However, FreeBSD is in my opinion excellent operating system not only for network router and firewall. That's the reason why pfSense (2004 → present) and OPNsense (2015 → present) are very well known and used router and firewall appliances nowadays. Both of them are successors of m0n0wall (2003–2015).

OPNsense appliance is very good community project and it is a good solution for anyone without FreeBSD knowhow, however, if you are an IT-geek, you can use core FreeBSD to achieve what you want.

Enjoy.  

No comments:

Post a Comment