Synopsis #
This chapter describes how to configure OpenBSD as a small router and firewall using two network interfaces: one WAN interface that connects to the Internet service provider and one LAN interface that connects to the local network. The configuration enables IPv4 forwarding, network address translation (NAT), stateful firewalling, IPv4 address assignment with DHCP, and local DNS caching.
A router forwards IP packets between networks. A firewall enforces a policy controlling which packets are permitted. NAT translates addresses on egress to a public address, allowing multiple local hosts to share one public address. OpenBSD uses Packet Filter pf(4) for filtering, NAT, and traffic normalization. See pf(4) .
Network Topology and Assumptions #
The examples assume:
- WAN interface:
re0, configured by DHCP from the ISP, or configured with a static address if required by the ISP. - LAN interface:
re1, configured with the IPv4 subnet10.0.0.0/24. The router uses10.0.0.1on the LAN.
Adjust addresses and interface names to match the target environment.
Enable IPv4 Forwarding #
Enable IPv4 packet forwarding at runtime with sysctl(8) :
# sysctl net.inet.ip.forwarding=1
Persist the setting in sysctl.conf(5) :
net.inet.ip.forwarding=1
This guide does not enable IPv6 forwarding. An IPv6 router also requires an IPv6 addressing and prefix policy, router advertisements, PF rules, and tested upstream routing. The advanced networking chapter covers those requirements separately.
Configure Network Interfaces #
Configure the WAN interface. If the ISP assigns addresses by DHCP, write the interface configuration file described in hostname.if(5) :
# printf 'inet autoconf\n' > /etc/hostname.re0
If the ISP assigns a static address, set address, netmask, and broadcast accordingly:
# cat > /etc/hostname.re0 <<'EOF'
inet 203.0.113.10 255.255.255.0 203.0.113.255
EOF
Configure the LAN interface with a static address:
# cat > /etc/hostname.re1 <<'EOF'
inet 10.0.0.1 255.255.255.0 10.0.0.255
EOF
Apply interface configuration with netstart(8) :
# sh /etc/netstart
Verify the addresses with ifconfig(8) :
$ ifconfig re0
$ ifconfig re1
Configure DHCP Service #
The DHCP server dhcpd(8)
assigns IPv4 addresses and options to LAN clients. Create /etc/dhcpd.conf as described in dhcpd.conf(5)
for the 10.0.0.0/24 network. This example sets the default router and DNS server to the router itself and allocates a dynamic pool.
subnet 10.0.0.0 netmask 255.255.255.0 {
option routers 10.0.0.1;
option domain-name-servers 10.0.0.1;
range 10.0.0.51 10.0.0.250;
}
Optional fixed address assignment by hardware (MAC) address:
host server1 {
fixed-address 10.0.0.30;
hardware ethernet 00:00:00:00:00:00;
}
Enable dhcpd at boot, restrict it to the LAN interface, and start it using rcctl(8)
:
# rcctl enable dhcpd
# rcctl set dhcpd flags re1
# rcctl start dhcpd
# rcctl check dhcpd
Key directives:
subnetandnetmaskdefine the served IPv4 network.option routerssets the default gateway for clients.option domain-name-serversadvertises DNS resolvers to clients.rangedefines the dynamic address pool available for lease.fixed-addressandhardware ethernetbind a static lease to a client MAC address.
Configure the Firewall and NAT with PF #
Use pf.conf(5)
to define the firewall rules and NAT policy for pf(4). Create /etc/pf.conf with the following baseline policy. Adjust interface names and networks to match the environment.
# /etc/pf.conf - minimal router/firewall with NAT for a LAN
# Interfaces and networks
lan_if = "re1"
lan_net = "10.0.0.0/24"
# Non-routable and reserved address space
table <martians> { \
0.0.0.0/8, 10.0.0.0/8, 127.0.0.0/8, 169.254.0.0/16, \
172.16.0.0/12, 192.0.0.0/24, 192.0.2.0/24, 198.18.0.0/15, \
198.51.100.0/24, 203.0.113.0/24, 224.0.0.0/3, 192.168.0.0/16 \
}
# Default behaviors
set block-policy drop
set loginterface egress
set skip on lo
# Normalize traffic
match in all scrub (no-df random-id)
# NAT for LAN to the current egress address
match out on egress inet from $lan_net to any nat-to (egress:0)
# Anti-spoofing
antispoof quick for { egress, $lan_if }
block in quick on egress from <martians> to any
block return out quick on egress from any to <martians>
# Default deny
block all
# Allow established and related traffic
pass out on egress inet keep state
# Allow LAN to anywhere
pass in on $lan_if inet from $lan_net to any keep state
PF is enabled by default on a standard OpenBSD installation. Use pfctl(8) to validate, load, enable, and inspect the ruleset:
# pfctl -nf /etc/pf.conf
# pfctl -f /etc/pf.conf
# pfctl -e
# Enable PF at runtime if it was disabled
# pfctl -sr
# pfctl -sn
The egress keyword matches the interface that carries the default route, as documented in pf.conf(5)
. The policy drops unsolicited inbound traffic from the Internet, permits LAN to initiate connections, and performs NAT on outbound IPv4 traffic.
Configure Local DNS Caching with Unbound #
unbound(8)
provides a validating, recursive DNS resolver for the LAN clients in this design. Configure it to listen only on the router’s LAN address and to permit queries only from the LAN. Without a forward-zone, Unbound performs recursive resolution from the DNS root. Create /var/unbound/etc/unbound.conf as specified in unbound.conf(5)
:
server:
interface: 10.0.0.1
access-control: 10.0.0.0/24 allow
access-control: 0.0.0.0/0 refuse
hide-identity: yes
hide-version: yes
Enable and start the service with rcctl(8) :
# rcctl enable unbound
# rcctl start unbound
# rcctl check unbound
LAN clients receive 10.0.0.1 as their DNS server through DHCP. The router itself continues to use the upstream proposals that resolvd(8)
merges into /etc/resolv.conf; this procedure does not point the router at Unbound. Using the local resolver for both the router and LAN clients requires a separate, deliberate resolver-ownership and fallback design.
Verification #
After completing the configuration, connect a client to the LAN and confirm it receives an address in the configured pool, the default router 10.0.0.1, and the DNS server 10.0.0.1.
Verify IP connectivity and name resolution from the client. Use ping(8) for a basic reachability test to a public IP and ftp(1) to fetch a resource by hostname, which exercises DNS:
$ ping -n 8.8.8.8
$ ftp -o - https://example.com/ >/dev/null
On the router, confirm that NAT and states are present with pfctl(8) :
# pfctl -ss
# pfctl -s state
Refer to the Handbook for a concise pfctl cheat sheet at /pf/cheat_sheet/
. For detailed reference, use the Handbook-hosted manual pages: pf.conf(5)
, pfctl(8)
, dhcpd.conf(5)
, dhcpd(8)
, unbound.conf(5)
, unbound(8)
, hostname.if(5)
, sysctl(8)
, and sysctl.conf(5)
.