OpenBSD clearly separates network configuration responsibilities between the kernel and user space. The kernel routing table determines packet forwarding, ospfd and bgpd implement dynamic routing protocols, and dhcpd automatically assigns IP addresses. IPv6 support is fully integrated.
The article on System Configuration covers basic network configuration using hostname.if files. The Networking Fundamentals series explains the underlying concepts; this article focuses on their OpenBSD-specific implementation for routing, DHCP, IPv6, and network troubleshooting.
Routing Fundamentals
Kernel Routing Table
The kernel routing table determines packet forwarding. OpenBSD stores routes in kernel memory – persistent configuration is stored in configuration files.
To display the current routing table:
$ route -n show
Routing tables
Internet:
Destination Gateway Flags Refs Use Mtu Prio Iface
default 192.168.1.1 UGS 0 0 - 8 vio0
224/4 127.0.0.1 URS 0 0 32768 8 lo0
127/8 127.0.0.1 UGRS 0 0 32768 8 lo0
127.0.0.1 127.0.0.1 UHhl 1 2 32768 1 lo0
192.168.1/24 192.168.1.65 UCn 2 0 - 4 vio0
192.168.1.1 cc:32:e5:e9:1b:0b UHLch 1 2 - 3 vio0
192.168.1.65 52:54:00:ab:cd:09 UHLl 0 3 - 1 vio0
192.168.1.255 192.168.1.65 UHb 1 2 - 1 vio0
[...]
Internet6:
[IPv6 entries truncated – lo0-internal routes for compatibility addresses]
The -n option disables name resolution and displays addresses numerically.
The columns mean:
- Destination – Destination network or host
- Gateway – The address to which the packet is sent next. If the destination is on another network, this is usually the IP address of a router. For directly reachable destinations, OpenBSD may display a network interface hardware address here, such as a MAC address.
- Flags – Active route properties (see below)
- Refs – Number of active references to this route
- Use – Number of packets that have passed through this route
- Mtu – Maximum Transmission Unit; the maximum size, in bytes, of an IP packet that can be transmitted over the route without fragmentation.
-means that no separate MTU value is set for this route. - Prio – Routing priority; lower values are preferred
- Iface – Outgoing interface
The routing priorities 4 and 8 shown here are assigned when the routes are created. Each interface has a routing priority between 0 and 15. The default is 0. OpenBSD uses 4 for wireless interfaces, 6 for umb(4), and 15 for carp(4). The interface priority can be changed with ifconfig.
OpenBSD uses the interface priority as the starting value for the priority of new routes. For example, a route to a directly connected network is created by configuring an IP address and netmask on an interface. For such a route, OpenBSD adds 4 (RTP_CONNECTED) to the interface priority. For a new static route, it adds 8 (RTP_STATIC). If the interface priority is 0, this produces the values 4 and 8 shown in the example output.
“Directly connected” and “static” describe different properties. Directly connected means that the destination is reachable through an interface without an intermediate router. Static means that the route was explicitly configured rather than created, for example, from the interface configuration or by a routing protocol. A static route can therefore also lead to a directly reachable destination.
The flags indicate the state of each route. The most important ones in daily operations are:
| Flag | Name | Meaning |
|---|---|---|
U |
UP | Route is active |
G |
GATEWAY | Packets are forwarded through a gateway |
R |
REJECT | Destination is rejected as unreachable |
S |
STATIC | Manually configured route |
H |
HOST | Route to a single host (not a network) |
L |
LLINFO | Link-layer address (MAC) is known |
C |
CLONING | Using this route can create new, more specific routes from it |
c |
CLONED | The route was created from a cloning route |
n |
CONNECTED | Directly connected interface |
h |
CACHED | Referenced by a gateway route |
l |
LOCAL | Local address of this system |
b |
BROADCAST | Route to the broadcast address of a locally connected IPv4 network |
The default route (default) represents 0.0.0.0/0 – it applies to all packets for which no more specific route exists and points to the gateway on the local network.
A cloning route can serve as the basis for additional, more specific routing entries. When such a route is used and the kernel needs a separate entry for a particular destination, it can create that entry from the existing route. The original route carries the C flag, while the resulting route carries the c flag.
A host route, by contrast, is simply a route to a single destination host and is marked with H. Therefore, H does not automatically mean that the route was created through cloning; however, a cloned host route can carry both c and H.
The b flag identifies a route to the broadcast address of a locally connected IPv4 network. A packet sent to this address is directed to all IPv4 hosts on the corresponding network segment. For 192.168.1.0/24, the associated broadcast address is 192.168.1.255. There is no network-wide “global broadcast” spanning multiple networks; routers do not simply forward such broadcasts to other networks.
Static Routes
Static routes define explicit paths to specific networks. They are entered in /etc/hostname.if files.
Configure a default route through the interface configuration:
# /etc/hostname.vio0
inet 192.168.1.10/24
!route add default 192.168.1.1
A line beginning with ! executes a shell command while the hostname.if file is being processed. This directive is not limited to network commands; in principle, any command available in the system environment can be executed. OpenBSD also replaces $if within the command with the name of the interface currently being configured. For network configuration, this allows commands such as route to be executed, as shown here when configuring the default route.
Add additional network routes for multi-subnet environments:
# /etc/hostname.vio0
inet 192.168.1.10/24
!route add default 192.168.1.1
!route add 10.0.0.0/8 192.168.1.254
!route add 172.16.0.0/12 192.168.1.254
The route to 10.0.0.0/8 uses gateway 192.168.1.254. For destinations without a more specific route, the system uses the default route.
Configure a host-specific route for a single IP address:
!route add -host 203.0.113.1 192.168.1.253
-host identifies 203.0.113.1 as a single destination and creates a host route through 192.168.1.253.
/32 route to a single host is therefore more specific than a matching /24 network route, which in turn is more specific than a default route (/0).If multiple routes have the same matching prefix, prefix length alone is not sufficient to select a route. Additional route properties are then considered, such as their routing priority.
Default Route via /etc/mygate
/etc/mygate allows default gateways for IPv4 and IPv6 to be configured at system startup. netstart(8) processes the file after the required interfaces have been configured and installs the corresponding default routes.
The simplest case – an IPv4 gateway:
# /etc/mygate
192.168.1.1
IPv4 and IPv6 can be combined, with one gateway per line:
# /etc/mygate
192.168.1.1
2001:db8:1::1
Only the first address for each address family is used. Additional IPv4 or IPv6 entries are silently ignored.
/etc/mygate can also contain lines with !command. netstart(8) executes the specified shell command when processing the file:
# /etc/mygate
192.168.1.1
!doas rcctl start unbound
In this example, the local DNS resolver unbound is started using rcctl.
inet autoconf, netstart ignores IPv4 entries in /etc/mygate; with inet6 autoconf, the same applies to IPv6.
Enabling Routing Between Interfaces
By default, OpenBSD blocks packet forwarding between interfaces. IP forwarding must be enabled for router functionality.
Check the current status:
$ sysctl net.inet.ip.forwarding
net.inet.ip.forwarding=0
$ sysctl net.inet6.ip6.forwarding
net.inet6.ip6.forwarding=0
Enable it temporarily for the running system:
$ doas sysctl net.inet.ip.forwarding=1
$ doas sysctl net.inet6.ip6.forwarding=1
Enable it permanently through /etc/sysctl.conf:
net.inet.ip.forwarding=1
net.inet6.ip6.forwarding=1
IPv4 and IPv6 forwarding are enabled separately. Changes in /etc/sysctl.conf take effect after the next boot. To make them effective immediately, the corresponding values must also be set using sysctl.
Policy Routing with rdomains
Routing domains (rdomains) separate routing tables from one another. Interfaces and processes can be assigned to a specific rdomain.
One possible use case is multi-WAN with two separate uplinks.
Basic configuration in the interface files:
# /etc/hostname.vio0
inet 192.168.1.10/24
rdomain 0
!route -T 0 add default 192.168.1.1
# /etc/hostname.vio1
inet 10.0.0.10/24
rdomain 1
!route -T 1 add default 10.0.0.1
rdomain assigns the interface to a routing domain. The number following route -T specifies the routing table into which the command inserts the route. Thus, vio0 belongs to rdomain 0 and receives its default route in routing table 0. vio1 belongs to rdomain 1; its default route is inserted into routing table 1.
Explicitly start a process in a domain:
$ doas route -T 1 exec /usr/sbin/unbound
route -T 1 exec starts unbound in routing domain 1. Its child processes also remain assigned to this routing domain. For destinations without a more specific route, the process therefore uses the default route through 10.0.0.1.
Display the active routing tables:
$ route -T 0 -n show -inet
Routing tables
Internet:
Destination Gateway Flags Refs Use Mtu Prio Iface
default 192.168.1.1 UGS 5 133 - 8 vio0
127/8 127.0.0.1 UGRS 0 0 32768 8 lo0
192.168.1/24 192.168.1.65 UCn 2 897 - 4 vio0
192.168.1.1 cc:32:e5:e9:1b:0b UHLch 1 699 - 3 vio0
[...]
-inet limits the output to IPv4.
A routing table and a routing domain are not the same thing. A routing table contains the routes used to forward packets. A routing domain separates network areas from one another; interfaces in different rdomains cannot communicate directly with each other and may even use the same IP addresses. When an rdomain is created, OpenBSD also creates a routing table with the same number.
Dynamic Routing with ospfd
Routing Protocols
Static routes work for small networks. Larger infrastructures require dynamic routing—routes are distributed automatically based on the network topology.
OpenBSD includes separate daemons for each routing protocol—all part of the base system, with no pkg_add required:
- ospfd(8) — Open Shortest Path First, for internal networks (IGP)
- bgpd(8) — Border Gateway Protocol, for Internet routing (EGP)
- ripd(8) — Routing Information Protocol, an older IGP
- eigrpd(8) — Enhanced Interior Gateway Routing Protocol
OSPF Configuration
OSPF distributes routes within an organization. All routers in the same OSPF area exchange routing information.
Basic OSPF configuration in /etc/ospfd.conf:
router-id 10.0.0.1
area 0.0.0.0 {
interface vio0
interface vio1
}
The parameters define:
- router-id — Unique router identifier, typically an interface IP address
- area 0.0.0.0 — OSPF backbone area, the usual choice in simple single-area setups
- interface — Interfaces on which OSPF is active
Enable and start the daemon:
doas rcctl enable ospfd
doas rcctl start ospfd
Check neighbors:
doas ospfctl show neighbor
ID Pri State DeadTime Address Iface Uptime
10.0.0.2 1 FULL/DR 00:00:39 10.0.0.2 vio1 00:01:00
10.0.0.2 1 FULL/DR 00:00:39 192.168.1.65 vio0 00:01:00
The columns show:
- ID — Neighbor’s router ID
- Pri — Neighbor’s OSPF priority in the election of the Designated Router and Backup Designated Router
- State — Neighbor adjacency state;
FULLmeans the link-state databases are fully synchronized, while/DRidentifies the neighbor as the Designated Router - DeadTime — Time remaining before the neighbor is considered down if no further Hello packets are received
- Address — Neighbor’s IP address on this network
- Iface — Local interface through which the neighbor is reached
- Uptime — How long the neighbor adjacency has been established
The OSPF Routing Information Base (RIB) shows the routes known to the OSPF process:
doas ospfctl show rib
Destination Nexthop Path Type Type Cost Uptime
10.0.0.0/24 10.0.0.1 C Intra-Area Network 10 00:02:13
192.168.1.0/24 192.168.1.62 C Intra-Area Network 10 00:02:13
Here, Destination identifies the destination prefix, while Nexthop identifies the next router or the local address used to reach the destination. Under Path Type, C indicates a directly connected path; Intra-Area means the path is within the same OSPF area. Type, shown here as Network, describes a network destination. Cost represents the cumulative OSPF cost used for path selection, and Uptime indicates how long the entry has remained in its current state.
OSPF automatically updates the kernel routing table when the topology changes—if a router fails, the network converges without manual intervention.
Adjust interface costs:
router-id 10.0.0.1
area 0.0.0.0 {
interface vio0 {
metric 100
}
interface vio1 {
metric 10
}
}
Lower costs make certain routes preferable. OSPF calculates shortest paths based on cumulative interface costs.
BGP Configuration
bgpd(8) is covered in detail in the book “Sovereign Systems.” BGP misconfiguration can cause Internet-wide routing problems and requires knowledge of AS design and routing policy.
For small and medium-sized networks, OSPF is often entirely sufficient. BGP, by contrast, is intended for routing between autonomous systems.
Routing Metrics and Weighting
Multiple routes to the same destination can have different routing priorities. Lower priority values are preferred over higher ones. This kernel routing priority must be distinguished from the metrics and selection mechanisms used by dynamic routing protocols.
Add routes with priorities:
doas route add -priority 10 10.0.0.0/8 192.168.1.254
doas route add -priority 20 10.0.0.0/8 192.168.1.253
The route with priority 10 is preferred over the route with priority 20. However, different priorities alone do not implement gateway reachability checks and therefore do not provide automatic gateway failover. OpenBSD also supports multipath routing for multiple equivalent paths.
Dynamic routing protocols have their own selection criteria. In OSPF, metric influences OSPF path calculation; the priority assigned to OSPF routes when they are installed in the kernel routing table is controlled separately through fib-priority. BGP attributes such as AS-Path and Local Preference are likewise part of BGP path selection and must not be confused with kernel routing priority.
DHCP Server with dhcpd
Basic DHCP Configuration
The DHCP server dhcpd automatically assigns IP addresses to clients. Configuration is handled through /etc/dhcpd.conf.
Basic configuration for a single subnet:
# /etc/dhcpd.conf
subnet 192.168.1.0 netmask 255.255.255.0 {
range 192.168.1.100 192.168.1.200;
option routers 192.168.1.1;
option domain-name-servers 192.168.1.1;
option domain-name "internal.local";
default-lease-time 3600;
max-lease-time 7200;
}
The parameters define:
- subnet - Network range served by the DHCP server
- range - IP address pool for dynamic assignment
- option routers - Default gateway for clients
- option domain-name-servers - List of DNS servers
- option domain-name - Domain name provided to clients by the DHCP server
- default-lease-time - Default lease duration in seconds
- max-lease-time - Maximum lease extension
Start the DHCP server:
doas rcctl enable dhcpd
doas rcctl set dhcpd flags vio0
doas rcctl start dhcpd
vio0 restricts dhcpd to this interface; this instance does not respond to DHCP requests on other interfaces.
Two independently configured DHCP servers on the same network segment compete to assign leases—existing clients may receive incorrect or conflicting configurations. Before testing, check whether a DHCP server is already active on the target network.
For redundant configurations, dhcpd(8) supports synchronizing lease assignments between multiple instances. With -Y, an instance specifies a destination to which it sends synchronization messages; with -y, it specifies the address or interface on which it receives such messages. A shared key in /var/db/dhcpd.key can authenticate synchronization messages. The daemons synchronize their lease state with each other; they do not access a shared lease file. Details are provided in the SYNCHRONISATION section of the manual page.
Static IP Assignments
Servers and network hardware often require fixed IP addresses. DHCP can assign specific IP addresses to MAC addresses. A host declaration does not have to appear inside a subnet block; the following arrangement simply groups it with the associated network configuration for clarity:
subnet 10.0.0.0 netmask 255.255.255.0 {
range 10.0.0.100 10.0.0.110;
option routers 10.0.0.1;
option domain-name-servers 10.0.0.1;
default-lease-time 600;
max-lease-time 1200;
host client1 {
hardware ethernet 52:54:00:12:34:06;
fixed-address 10.0.0.50;
}
}
In this example, the client is identified by its Ethernet address. After a configuration change, dhcpd must be restarted (doas rcctl restart dhcpd)—according to dhcpd(8), it does not support automatic reloading via SIGHUP as some other daemons do.
On the client side, dhcpleasectl confirms that the fixed address has been received. The -l vio1 option restricts the output to the lease for interface vio1:
doas dhcpleasectl -l vio1
vio1 [Bound]
inet 10.0.0.50 netmask 255.255.255.0
default gateway 10.0.0.1
nameservers 10.0.0.1
lease 10 minutes
dhcp server 10.0.0.2
[Bound] indicates a successfully bound DHCP lease. The following lines identify the address and netmask, default gateway, DNS server, lease duration, and DHCP server that assigned the address.
fixed-address do not appear as dynamically assigned leases in /var/db/dhcpd.leases. The lease file manages dynamic assignments from the configured pools.
Determine a device’s MAC address:
$ arp -a
Host Ethernet Address Netif Expire Flags
printer.example.com 52:54:00:12:34:05 vio0 19m58s
The -a option displays all entries in the ARP cache. The columns contain the hostname or IP address, Ethernet address, associated interface, remaining validity period, and any applicable flags. For devices without a current ARP entry, the MAC address must be obtained directly from the device or its configuration.
Multiple Subnets
A shared-network describes multiple IP subnets that share the same physical network segment. The DHCP server treats the dynamic address ranges of the included subnets as a common pool.
Add an alias address for the second subnet to the server interface:
doas ifconfig vio1 alias 10.0.1.1/24
alias adds the additional IPv4 address 10.0.1.1/24 to interface vio1 without replacing an existing address.
Configuration with two subnets in a shared pool:
# /etc/dhcpd.conf
shared-network vio1-testnetz {
subnet 10.0.0.0 netmask 255.255.255.0 {
range 10.0.0.100 10.0.0.101;
option routers 10.0.0.1;
option domain-name-servers 10.0.0.1;
default-lease-time 600;
max-lease-time 1200;
}
subnet 10.0.1.0 netmask 255.255.255.0 {
range 10.0.1.100 10.0.1.101;
option routers 10.0.1.1;
option domain-name-servers 10.0.1.1;
default-lease-time 600;
max-lease-time 1200;
}
}
Within a shared-network block, all included subnets form a common address pool. The subnet assigned to a client does not depend on the order of the subnets in the pool—a test with three clients on the same segment produced the following distribution:
doas cat /var/db/dhcpd.leases
lease 10.0.0.100 {
client-hostname "hans";
}
lease 10.0.1.100 {
client-hostname "wilhelm";
}
lease 10.0.1.101 {
client-hostname "xaver";
}
client-hostname contains the hostname supplied by the respective client.
A client does not necessarily receive an address from the first subnet in the pool, even if addresses are still available there. Because the configuration defines the subnets as belonging to the same physical network, the server cannot distinguish between them based on the client’s location.
shared-network—each interface then has its own independent subnet, without a shared pool. This approach makes sense when the networks are already physically separated.
DHCP Relay for Remote Subnets
DHCP clients initially send their requests as broadcasts on the local network segment; routers do not forward these broadcasts to other subnets. A DHCP relay agent listens on the remote subnet and forwards requests as unicast to a DHCP server on another network.
Enable the relay on the router system positioned between the client segment and the DHCP server segment:
doas rcctl enable dhcrelay
doas rcctl set dhcrelay flags "-i vio2 10.0.0.2"
doas rcctl start dhcrelay
The parameters define:
- -i vio2 - Interface on which the relay agent receives DHCP broadcasts
- 10.0.0.2 - Destination IP address of the DHCP server to which requests are forwarded
Requests from the vio2 segment are forwarded to 10.0.0.2. The DHCP server sends its response back to the client through the same relay agent.
The DHCP server requires a subnet declaration for the relayed client’s network. If the client is to receive a dynamically assigned address there, this remote network needs its own appropriate pool:
subnet 172.16.2.0 netmask 255.255.255.0 {
range 172.16.2.100 172.16.2.105;
option routers 172.16.2.1;
option domain-name-servers 172.16.2.1;
default-lease-time 600;
max-lease-time 1200;
}
The remote network must not be placed in a shared-network block together with the server network if the two are physically separated. The relay agent inserts its address into the giaddr field of the DHCP request, allowing dhcpd to associate the request with the remote subnet.
Packet-level verification: tcpdump on the DHCP server interface shows the relayed request. -n prevents name resolution, -i vio1 selects the interface, and the filter port 67 or port 68 allows packets whose source or destination port matches DHCP server port 67 or DHCP client port 68:
doas tcpdump -n -i vio1 port 67 or port 68
172.16.2.1.67 > 10.0.0.2.67: (request) hops:1 xid:0xd76edd47 G:172.16.2.1 ether 52:54:00:cc:cd:01 [|bootp]
In the output, 172.16.2.1.67 sends from UDP port 67 to DHCP server 10.0.0.2.67. (request) identifies the BOOTP/DHCP request, hops:1 indicates the relay hop already traversed, and xid is the transaction ID used to associate requests with responses. G:172.16.2.1 is the gateway/giaddr address inserted by the relay; ether identifies the client’s hardware address. [|bootp] indicates that the captured data is insufficient for complete BOOTP decoding at this point.
The client on the remote segment receives an address from its own subnet’s pool. In the test using relay address 172.16.2.1, the client received 172.16.2.100/24 with gateway 172.16.2.1; the gateway address was subsequently reachable via ARP and ICMP.
DHCP Client with dhcpleased
On the client side, dhcpleased(8) handles IP configuration through DHCP. OpenBSD no longer provides a separate dhclient command.
Enable the daemon:
doas rcctl enable dhcpleased
doas rcctl start dhcpleased
The interface must be explicitly registered with dhcpleased through hostname.if:
# /etc/hostname.vio1
dhcp
After the file is created, OpenBSD checks its permissions and automatically corrects them if necessary:
doas sh /etc/netstart vio1
WARNING: /etc/hostname.vio1 is insecure, fixing permissions.
Simply running ifconfig vio1 down / up does not reread /etc/hostname.vio1. Changes to the file only take effect through netstart or a reboot.
If the interface was previously configured with a static address, switching to dhcp does not automatically remove the old address—both inet entries remain present. For a clean transition, manually remove the old address (ifconfig vio1 delete <old-IP>).
Query the lease status directly on the client. As above, -l vio1 restricts the output to this interface:
doas dhcpleasectl -l vio1
vio1 [Bound]
inet 10.0.0.100 netmask 255.255.255.0
default gateway 10.0.0.1
nameservers 10.0.0.1
lease 10 minutes
dhcp server 10.0.0.2
[Bound] indicates a successfully bound DHCP lease. The following lines show the assigned address and netmask, gateway, DNS server, remaining lease time, and DHCP server that assigned the address.
Lease Management
Active DHCP leases are stored in /var/db/dhcpd.leases:
doas cat /var/db/dhcpd.leases
lease 10.0.0.100 {
starts 1 2026/07/27 15:14:17 UTC;
ends 1 2026/07/27 15:24:17 UTC;
hardware ethernet 52:54:00:12:34:06;
uid 01:52:54:00:12:34:06;
client-hostname "client";
}
lease 10.0.0.100 identifies the assigned IP address. starts and ends specify the beginning and end of the lease in UTC; the leading number identifies the day of the week. hardware ethernet contains the client’s MAC address. uid stores its DHCP client identifier; in this example, it consists of hardware type 01 for Ethernet followed by the MAC address. client-hostname contains the hostname supplied by the client.
When the lease state changes, multiple entries for the same address may appear in the file; therefore, the file should not be interpreted as an immutable historical log.
DHCP clients automatically renew their leases before they expire. Deleting the server-side lease file does not force already bound clients to renegotiate immediately or obtain a new IP address. In the test, a client retained 172.16.2.100 after the server’s lease database was cleared and received confirmation of the same address during its regular renewal.
IPv6 Configuration
IPv6 Addresses and Autoconfiguration
IPv6 uses 128-bit addresses instead of IPv4’s 32-bit addresses. The address structure supports hierarchical routing and autoconfiguration.
Basic IPv6 autoconfiguration in /etc/hostname.if:
# /etc/hostname.vio2
inet6 autoconf
inet6 line configures neither an IPv6 address nor SLAAC. hostname.if(5) allows the address family to be specified without an address, but automatic IPv6 configuration explicitly requires inet6 autoconf, while a static address requires inet6 <address> <prefixlen>.
With inet6 autoconf, the interface is marked for IPv6 autoconfiguration (AUTOCONF6). slaacd(8) monitors interfaces marked accordingly and processes Router Advertisements received on them. These can provide additional global unicast addresses and other IPv6 configuration information. slaacd operates separately from the IPv4 DHCP daemon dhcpleased.
Display the current IPv6 addresses. The inet6 argument restricts the output to the interface’s IPv6 configuration:
$ ifconfig vio2 inet6
vio2: flags=2a48843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST,AUTOCONF6TEMP,AUTOCONF6,AUTOCONF4,LRO> mtu 1500
lladdr 52:54:00:cc:cd:01
inet6 fe80::5054:ff:fecc:cd01%vio2 prefixlen 64 scopeid 0x3
inet6 2001:db8:2:0:47f0:731b:6691:7f2e prefixlen 64 autoconf pltime 2651 vltime 5351
inet6 2001:db8:2:0:f185:af7d:7924:cdac prefixlen 64 autoconf temporary pltime 2651 vltime 5351
The number preceding the angle bracket in flags= is the numeric bitmask of the interface flags listed symbolically afterward. The first line shows the same general interface properties as the complete ifconfig output later in the “Checking Interface Status” section. AUTOCONF6 indicates that automatic IPv6 configuration is enabled, while AUTOCONF6TEMP indicates the generation of temporary IPv6 addresses. mtu is the Maximum Transmission Unit, and lladdr is the link-layer or MAC address.
The fe80 address is link-local and valid only within the local segment; %vio2 associates the link-local address with the interface. prefixlen 64 specifies the prefix length, and scopeid 0x3 is the interface’s numeric scope identifier. The two 2001:db8 addresses are global unicast addresses—one persistent, automatically generated address and one temporary privacy address (see the IPv6 Privacy Extensions section). autoconf identifies automatically configured addresses, while temporary identifies a temporary privacy address. pltime and vltime specify the preferred and valid lifetimes in seconds.
The process can be traced using tcpdump—Router Solicitation, Router Advertisement, and Duplicate Address Detection for the newly generated address:
$ doas tcpdump -n -i vio2 icmp6
fe80::5054:ff:fecc:cd01 > ff02::2: icmp6: router solicitation
fe80::5054:ff:fecc:cd08 > fe80::5054:ff:fecc:cd01: icmp6: router advertisement
:: > ff02::1:ff6a:49e9: icmp6: neighbor sol: who has 2001:db8:2:0:357a:c3ef:b66a:49e9
fe80::5054:ff:fecc:cd01 > ff02::2: icmp6: neighbor adv: tgt is 2001:db8:2:0:357a:c3ef:b66a:49e9
ff02::2 is the link-local multicast address for all routers. The address ff02::1:ff6a:49e9 is the solicited-node multicast address associated with the IPv6 address being checked. The Neighbor Solicitation message (neighbor sol) with source address :: uses who has to query the new address and checks whether it is already in use on the segment before assigning it (Duplicate Address Detection). neighbor adv identifies a Neighbor Advertisement; tgt is specifies its target address. The new address becomes usable only after the check succeeds.
Static IPv6 Configuration
Servers require static IPv6 addresses. Manual configuration is similar to IPv4:
# /etc/hostname.vio2
inet6 2001:db8:2::10/64
CIDR notation works the same way as with IPv4. SLAAC uses /64 prefixes.
Configure multiple IPv6 addresses using the alias keyword:
# /etc/hostname.vio2
inet6 autoconf
inet6 alias 2001:db8:2::30/64
The alias keyword adds an additional static address alongside the automatically generated one—useful when an interface should be configured through SLAAC while also retaining an additional stable address. IPv6 allows multiple addresses per interface.
Apply the changes after editing the file:
doas sh /etc/netstart vio2
Check the result:
$ ifconfig vio2 inet6
vio2: flags=2248843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST,AUTOCONF6TEMP,AUTOCONF6,LRO> mtu 1500
lladdr 52:54:00:cc:cd:08
inet6 fe80::5054:ff:fecc:cd08%vio2 prefixlen 64 scopeid 0x3
inet6 2001:db8:2::30 prefixlen 64
Addresses without lifetime values (pltime/vltime), as shown here, are statically configured. Unlike the time-limited autoconf addresses from the previous section, they do not expire automatically.
IPv6 Privacy Extensions
OpenBSD uses Semantically Opaque Interface Identifiers (SOII) according to RFC 7217 by default for persistent SLAAC addresses. This means the interface ID is not simply derived from the MAC address.
Temporary, randomized addresses (RFC 8981) are enabled by default on OpenBSD whenever autoconf is set—no separate activation is required. Once a suitable prefix becomes available through SLAAC, temporary addresses can be generated alongside the persistent, automatically configured address:
In the ifconfig vio2 inet6 output shown above, two automatically configured global unicast addresses appear together: the first has the autoconf attribute, while the second also has temporary. The AUTOCONF6TEMP flag already visible in that output indicates that temporary addresses are generated in addition to the persistent autoconf address. They can be identified by the temporary suffix on the address line. Their validity and renewal are controlled by the displayed preferred and valid lifetimes.
autoconfprivacy for this feature; in the versions covered here, the option is called temporary or -temporary.
Disable Privacy Extensions (for servers that should not use temporary addresses):
# /etc/hostname.vio2
inet6 autoconf -temporary
This disables the generation of new temporary SLAAC addresses. Temporary addresses that have already been configured are not removed immediately; according to the manual page, they expire normally. The persistent, automatically configured address remains available.
Router Advertisement Daemon
IPv6 routers send RA messages for autoconfiguration. OpenBSD can function as an IPv6 router using rad (Router Advertisement Daemon).
Basic RA configuration in /etc/rad.conf:
interface vio2 {
prefix 2001:db8:2::/64
}
The parameters define:
- interface - Outgoing interface for RA messages
- prefix - IPv6 prefix for clients
A dns block is optional and is only needed when IPv6 DNS servers should be distributed through RA.
Start the Router Advertisement Daemon:
doas rcctl enable rad
doas rcctl start rad
Reload the configuration while the daemon is running, without restarting it:
doas ractl reload
Clients on the network automatically configure themselves with addresses from the defined prefix—visible through slaacd on the client side, as described in the previous section.
Network Troubleshooting
Connectivity Tests
Check basic reachability with ping:
$ ping -c 4 192.168.1.1
PING 192.168.1.1 (192.168.1.1): 56 data bytes
64 bytes from 192.168.1.1: icmp_seq=0 ttl=64 time=1.108 ms
64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=1.048 ms
64 bytes from 192.168.1.1: icmp_seq=2 ttl=64 time=1.857 ms
64 bytes from 192.168.1.1: icmp_seq=3 ttl=64 time=1.384 ms
--- 192.168.1.1 ping statistics ---
4 packets transmitted, 4 packets received, 0.0% packet loss
round-trip min/avg/max/std-dev = 1.048/1.349/1.857/0.319 ms
The -c 4 parameter limits the test to four Echo Requests. The header identifies the destination and, with 56 data bytes, the payload size of each ICMP Echo Request. Each reply line shows the size of the received ICMP reply, including the ICMP header, as 64 bytes; the sequence number as icmp_seq; the received Time-To-Live value as ttl; and the measured round-trip time as time. The TTL is decremented by each router along the path; moving from one router to the next is called a hop. When the TTL reaches 0, the packet is discarded.
The statistics at the end count transmitted and received packets and calculate packet loss from those counts. min/avg/max/std-dev summarizes the minimum, average, and maximum round-trip times and their standard deviation. ping exits with status 0 as soon as at least one reply has been received; if all requests time out, the exit status is greater than 0. This allows a script to determine whether the destination was reachable during the test.
IPv6 reachability:
$ ping6 -c 4 2001:db8:2::30
ping6 uses the same -c parameter and the same basic output format for IPv6 addresses; it uses ICMPv6 instead of ICMP for IPv4.
Traceroute
Trace network paths with traceroute:
$ traceroute 192.168.1.1
traceroute to 192.168.1.1 (192.168.1.1), 64 hops max, 40 byte packets
1 kurt (192.168.1.1) 1.616 ms 1.602 ms 2.454 ms
The header identifies the destination, the maximum of 64 hops, and the 40-byte size of the probe packets. Each subsequent line begins with the hop number and shows the responding router and the response times of the three probes sent by default. Each line therefore represents one hop toward the destination. The measured response times provide indications of delays along the path; however, individual high values do not necessarily prove that a network segment is slow. In a flat test network where all systems are connected to the same bridge, the destination is often only one hop away. In production networks with multiple intermediate routers, a corresponding line appears for each hop. When multiple paths are available (ECMP), a hop line may also show several different response addresses, one per probe:
3 198.51.100.1 (198.51.100.1) 29.987 ms 31.592 ms 39.918 ms
4 198.51.100.5 (198.51.100.5) 42.059 ms 38.817 ms 198.51.100.9 (198.51.100.9) 40.03 ms
[... more hops ...]
If a hop does not respond within the timeout period, * * * appears instead of an address and latency.
traceroute(8) uses UDP probes by default. The -I option uses ICMP or ICMPv6 Echo probes instead. -P proto selects another IP protocol by number or by a name from /etc/protocols; the manual page notes that this does not work reliably for most protocols.
Traceroute using ICMP Echo instead of UDP:
$ traceroute -I example.com
Another IP protocol can be selected with -P, for example -P tcp. However, this is not equivalent to a specialized TCP SYN traceroute and, according to the manual page, does not work reliably for most protocols.
If traceroute: sendto: Permission denied occurs, the active PF ruleset should also be checked:
traceroute: sendto: Permission denied
$ doas pfctl -sr | grep -i block
block return all
pfctl -sr displays the loaded filtering rules (-s rules); grep -i block limits the output to lines containing block, ignoring case. The example output block return all is a rule that blocks matching traffic and generates a response.
However, the exact cause of a local Permission denied must not be inferred solely from the presence of a block return rule. PF semantics and responses to locally generated traffic are covered in the article Firewall and pf Fundamentals (planned).
Checking Interface Status
Detailed interface information:
$ ifconfig vio0
vio0: flags=2808843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST,AUTOCONF4,LRO> mtu 1500
lladdr 52:54:00:ab:cd:08
index 1 priority 0 llprio 3
groups: egress
media: Ethernet autoselect
status: active
inet 192.168.1.62 netmask 0xffffff00 broadcast 192.168.1.255
The first line shows the interface flags and the MTU (Maximum Transmission Unit), here 1500 bytes. The displayed flags mean:
- UP - Interface is enabled
- BROADCAST - Interface supports IPv4 broadcasts
- RUNNING - Driver or interface resources are operational
- SIMPLEX - The interface does not automatically receive its own transmitted frames as incoming traffic
- MULTICAST - Multicast is supported
- AUTOCONF4 - Interface obtains its IPv4 address through DHCP (see the DHCP Client section)
- LRO - Large Receive Offload; the driver can combine multiple incoming TCP segments for subsequent processing
lladdr is the link-layer or MAC address. index is the interface’s kernel index number, priority is its interface priority, and llprio is the priority for link-layer communication. groups: egress shows the interface group to which the interface belongs. media describes the transmission medium in use, and autoselect indicates automatic media selection. The inet line contains the IPv4 address, netmask, and broadcast address; 0xffffff00 corresponds to the netmask 255.255.255.0, or /24.
status: active indicates an active link. status: no carrier indicates that no carrier or link is detected. With virtual NICs (such as vio under QEMU/KVM), the duplex information normally shown after media: (for example, (1000baseT full-duplex)) is often absent. This is normal for virtual interfaces and is not an indication of an error.
Packet Statistics
Identify network errors. netstat -i displays the interface table with addresses and packet counters:
$ netstat -i
Name Mtu Network Address Ipkts Ifail Opkts Ofail Colls
lo0 32768 <Link> 0 0 0 0 0
lo0 32768 127/8 localhost 0 0 0 0 0
vio0 1500 <Link> 52:54:00:ab:cd:08 2762 0 2453 0 0
vio0 1500 192.168.1/2 hans.fridolin.lan 2762 0 2453 0 0
vio2 1500 <Link> 52:54:00:cc:cd:08 228 0 47 0 0
vio2 1500 172.16.2/24 172.16.2.1 228 0 47 0 0
vio2 1500 fe80::%vio2 fe80::5054:ff:fec 228 0 47 0 0
vio2 1500 2001:db8:2: 2001:db8:2::30 228 0 47 0 0
pflog0 33136 <Link> 0 0 0 0 0
The Name, Mtu, Network, and Address columns identify the interface, MTU, associated network or address family, and corresponding address. The counters mean:
- Ipkts - Received packets
- Ifail - Failed incoming packets
- Opkts - Transmitted packets
- Ofail - Failed outgoing packets
- Colls - Collisions (on hubs, rarely on switches)
Each interface appears multiple times: one line for link-layer statistics (<Link>) and another line for each configured address family or network. An interface with IPv4 and IPv6 addresses therefore appears on several lines. Increasing Ifail or Ofail values warrant further investigation; their cause cannot be determined from the counters alone.
ARP Table
Check MAC address resolution:
$ arp -a
Host Ethernet Address Netif Expire Flags
198.51.100.1 52:54:00:12:34:01 vio1 permanent l
client.example.com 52:54:00:12:34:02 vio0 19m58s
router.example.com 52:54:00:12:34:03 vio0 permanent l
As above, arp -a displays all entries. The columns show the hostname or IP address, MAC address, interface, expiration time, and flags. The l flag identifies a local ARP entry. The Expire column indicates whether an entry expires; entries marked permanent do not expire.
Display only numeric addresses without DNS resolution. -a selects all cache entries, while -n suppresses name resolution:
$ arp -an
Numeric output avoids delays caused by DNS lookups and works even when the resolver is unreachable.
Clear the ARP cache. -d deletes entries; combined with -a, the deletion operation is applied to all cache entries:
$ doas arp -d -a
198.51.100.1 (198.51.100.1) deleted
203.0.113.132 (203.0.113.132) deleted
Clearing the cache forces a new ARP resolution during the next communication attempt. This is useful, for example, after a MAC address change or during troubleshooting. The cache is then automatically populated again.
Socket Statistics
Display open network connections. With netstat, -a displays all sockets and -n displays addresses and ports numerically; grep ESTABLISHED limits this output to TCP connections in the ESTABLISHED state:
$ netstat -an | grep ESTABLISHED
tcp 0 44 192.168.1.65.22 192.168.1.132.52174 ESTABLISHED
netstat separates the IP address and port with a period (192.168.1.65.22), not a colon as is common with many Linux tools.
The first column identifies the transport protocol and address family as tcp or tcp6. The remaining columns show:
- Recv-Q - Socket receive queue
- Send-Q - Socket send queue
- Local Address - Local IP.port pair
- Foreign Address - Remote IP.port pair
- State - TCP connection state
Identify listening ports. In addition to -a and -n, -l displays only listening sockets; grep LISTEN limits the text output to the TCP state LISTEN:
$ netstat -anl | grep LISTEN
tcp 0 0 *.22 *.* LISTEN
tcp 0 0 127.0.0.1.25 *.* LISTEN
tcp6 0 0 fe80::1%lo0.25 *.* LISTEN
tcp6 0 0 ::1.25 *.* LISTEN
tcp6 0 0 *.22 *.* LISTEN
Services bound to a wildcard address show *.PORT. *.* in the Foreign Address column means that no specific remote endpoint is associated with a listening socket yet. Specific local addresses (127.0.0.1.PORT, ::1.PORT) restrict the socket to that address; IPv4 and IPv6 appear as separate entries (tcp versus tcp6).
For live statistics with automatic updates:
$ systat netstat
The systat tool continuously displays network activity. By default, the display is updated every five seconds; the -s option changes the interval so traffic can be observed at shorter or longer intervals during testing.
tcpdump for Packet Analysis
Capture live network traffic:
$ doas tcpdump -n -i vio0
tcpdump: listening on vio0, link-type EN10MB
07:40:01.579084 192.168.1.65.22 > 192.168.1.132.52174: P 2031605786:2031605822(36) ack 150708228 win 271 <nop,nop,timestamp 4109385259 2708599083> (DF) [tos 0xb8]
07:40:01.590399 192.168.1.65.22 > 192.168.1.132.52174: P 36:112(76) ack 1 win 271 <nop,nop,timestamp 4109385269 2708599083> (DF) [tos 0xb8]
[... more packets ...]
The parameters:
- -n - Display addresses and ports numerically, without name resolution
- -i vio0 - Capture packets on interface
vio0
The message listening on vio0, link-type EN10MB confirms the monitored interface and identifies EN10MB as the Ethernet link type used by BPF. Each packet line begins with a timestamp. This is followed by the source address and source port, >, and the destination address and destination port. In the TCP packet shown, P indicates that the PSH flag is set; 2031605786:2031605822(36) identifies the sequence number range and the 36 bytes of payload it contains. ack specifies the next sequence number being acknowledged, and win specifies the advertised TCP receive window. TCP options appear inside <...>, here two No-Operation padding options and the TCP timestamps. In timestamp 4109385259 2708599083, the first value is the current TCP Timestamp Value (TSval), and the second is the Timestamp Echo Reply Value (TSecr) echoed back from the communication partner. (DF) indicates the IPv4 “Don’t Fragment” flag. [tos 0xb8] displays the Type-of-Service byte of the IPv4 header in hexadecimal; 0xb8 corresponds to DSCP 46 (Expedited Forwarding) with the ECN bits unset. In the second line, 36:112(76) represents sequence numbers relative to the first observed sequence.
Filter network traffic by port:
$ doas tcpdump -n -i vio0 port 22
When troubleshooting an individual service, the capture can be restricted to that service’s network traffic. This filter captures only packets to and from port 22.
Display ICMP traffic—a simultaneous ping from another host provides suitable test traffic:
$ doas tcpdump -n -i vio0 icmp
tcpdump: listening on vio0, link-type EN10MB
07:43:12.985240 192.168.1.62 > 192.168.1.65: icmp: echo request
07:43:12.985575 192.168.1.65 > 192.168.1.62: icmp: echo reply
The timestamp appears at the beginning of each line; source and destination addresses are separated by >. icmp: echo request is the Echo Request, and icmp: echo reply is the corresponding reply. The filter also displays other IPv4 ICMP messages if they occur on the interface. IPv6 uses ICMPv6 and is filtered separately:
$ doas tcpdump -n -i vio0 icmp6
This makes it possible to observe Router Solicitations, Router Advertisements, and Neighbor Discovery, for example.
Display packet contents in hexadecimal/ASCII:
$ doas tcpdump -n -i vio0 -X icmp
With the -X flag, tcpdump displays packet data as a hexadecimal and ASCII dump. This also makes the transmitted payload visible. With encrypted protocols, however, the payload cannot be read as plaintext without decryption.
tcpdump: -i requires an argument. -i -X vio0 incorrectly interprets -X as an interface name, followed by the error “Failed to open bpf device”. The correct form is -i vio0 -X, or -X in any other position except immediately after -i.
Testing DNS Resolution
Perform a DNS lookup:
$ host example.org
example.org has address 203.0.113.10
example.org mail is handled by 10 mail.example.org.
The output contains the domain’s A record and, if present, its MX records. In the MX entry, 10 is the mail server’s preference value; lower values are preferred over higher ones. When troubleshooting, a specific DNS server can be queried directly instead of using the configured system resolver:
$ host example.org 192.168.1.1
Using domain server:
Name: 192.168.1.1
Address: 192.168.1.1#53
example.org has address 203.0.113.10
In the Using domain server block, Name identifies the queried DNS server, Address identifies its address, and #53 indicates DNS port 53. Comparing the results helps determine whether the problem lies with the configured local resolver or elsewhere in DNS resolution. In the test network shown, the local unbound resolver handles this task. Public third-party resolvers such as the familiar 1.1.1.1 or 9.9.9.9 conflict with the sovereignty principle of this series and are suitable only for an occasional comparison.
Reverse DNS lookup:
$ host 203.0.113.10
10.113.0.203.in-addr.arpa domain name pointer example.org.
For IPv4, host constructs the name in the reverse DNS zone in-addr.arpa: the address octets appear in reverse order, and a PTR record (domain name pointer) points to the hostname. Reverse DNS therefore resolves an IP address back to a hostname. Reverse DNS plays an important role particularly in mail server operations and various access and trust checks.
Networking as an Observable System State
Troubleshooting has taken us through the different layers of the network: from the reachable destination through routing and interfaces down to the individual packet. This makes it possible not only to determine that a connection is failing, but also to identify where the expected network state differs from the state actually observed.
OpenBSD therefore does not treat network configuration as a single, centralized state. Addresses, routes, routing domains, and dynamically learned information originate in different places, but together they determine how the kernel forwards a packet. This separation is precisely what makes more complex networks understandable: every change can be traced to its origin, and its effect can be verified in the resulting network state.
We have now established where OpenBSD sends packets and how to trace their paths. Whether those packets are permitted to take those paths is the next layer. That leads to the article Firewall and pf Fundamentals (planned).
Software and Versions Used
This guide covers:
- OpenBSD: 7.8, 7.9
- ospfd: Included in the OpenBSD base system
- dhcpd: Included in the OpenBSD base system
- rad: Included in the OpenBSD base system
- Context: Server and router systems
- As of: September 2026