Synopsis #
A virtual LAN (VLAN) separates one Ethernet link into tagged Layer 2 networks. OpenBSD represents each tagged network with a vlan(4) interface. This guide configures two VLANs on one trunk link, enables IPv4 routing, applies an explicit pf.conf(5) policy, and verifies traffic at each layer.
The connected switch must already carry the selected VLAN identifiers as tagged traffic. Management access must not depend on the rules being changed. Keep a console or an established recovery session available until a new administrative session succeeds.
Example Topology #
The example uses:
| Role | Interface or network |
|---|---|
| Internet uplink | em0, configured separately |
| VLAN trunk | em1 |
| Administrative VLAN | VLAN 10, 10.10.10.0/24 |
| Service VLAN | VLAN 20, 10.20.20.0/24 |
| Router addresses | 10.10.10.1 and 10.20.20.1 |
Replace every interface, VLAN identifier, address, and policy with values from the actual network plan.
Configure the Trunk and VLAN Interfaces #
Leave the physical trunk without an IP address. Create /etc/hostname.em1:
up
Create /etc/hostname.vlan10:
parent em1
vnetid 10
inet 10.10.10.1 255.255.255.0
description "administration"
up
Create /etc/hostname.vlan20:
parent em1
vnetid 20
inet 10.20.20.1 255.255.255.0
description "services"
up
The file names do not have to match the VLAN identifiers, but matching names make the configuration easier to audit. Apply the three interfaces with netstart(8) :
# sh /etc/netstart em1 vlan10 vlan20
Confirm the parent, tag, addresses, and link state with ifconfig(8) :
$ ifconfig em1
# Confirm that the physical trunk is active
$ ifconfig vlan10
# Confirm parent em1, vnetid 10, and 10.10.10.1/24
$ ifconfig vlan20
# Confirm parent em1, vnetid 20, and 10.20.20.1/24
Enable IPv4 Forwarding #
Add the following line to /etc/sysctl.conf:
net.inet.ip.forwarding=1
Apply it immediately with sysctl(8) :
# sysctl net.inet.ip.forwarding=1
Forwarding only permits the kernel to route packets. PF still determines which traffic may cross between networks.
Add a PF Policy #
Integrate the following policy into the complete /etc/pf.conf; do not replace an established ruleset without reviewing its existing macros, tables, anchors, and management rules:
wan = "em0"
admin_if = "vlan10"
service_if = "vlan20"
admin_net = "10.10.10.0/24"
service_net = "10.20.20.0/24"
local_nets = "{ 10.10.10.0/24, 10.20.20.0/24 }"
set skip on lo
block all
match out on $wan from $local_nets nat-to ($wan)
pass out on $wan from $local_nets
pass in quick on $admin_if inet proto icmp from $admin_net to ($admin_if) icmp-type echoreq
pass in quick on $admin_if proto { tcp udp } from $admin_net to ($admin_if) port domain
pass in quick on $admin_if proto tcp from $admin_net to ($admin_if) port ssh
block in quick on $admin_if from $admin_net to self
pass in on $admin_if from $admin_net to any
pass in quick on $service_if inet proto icmp from $service_net to ($service_if) icmp-type echoreq
block in quick on $service_if from $service_net to self
block in quick on $service_if from $service_net to $admin_net
pass in on $service_if from $service_net to any
This example permits both VLANs to test their own router address, permits the administrative VLAN to use SSH and an optional local DNS resolver, permits administrators to reach the service network, prevents the service network from initiating other connections to the router or administrative network, and provides outbound Internet access through IPv4 network address translation. It does not configure DHCP or DNS service; a permitted port has no effect unless a daemon is bound to that address.
Parse the complete ruleset before loading it with pfctl(8) :
# pfctl -nf /etc/pf.conf
# Reject syntax errors without changing the active ruleset
# pfctl -f /etc/pf.conf
# Load the rules only after parsing succeeds
Keep the previous administrative session open. Test a new SSH connection from the administrative VLAN before ending the recovery session.
Verify Tagged Traffic and Routing #
Use tcpdump(8) on the physical trunk to confirm that tags arrive from the switch:
# tcpdump -eni em1 vlan
Then inspect each logical interface:
# tcpdump -ni vlan10
# Observe untagged packets after VLAN 10 decapsulation
# tcpdump -ni vlan20
# Observe untagged packets after VLAN 20 decapsulation
From a host in each VLAN, verify its address, default gateway, DNS configuration, and reachability to the router address. Then test only the cross-VLAN and Internet paths that the PF policy permits.
Display routes, PF rules, and states when a test fails:
$ route -n show
# Confirm connected routes for both VLAN networks
# pfctl -vvsr
# Inspect rule counters
# pfctl -ss
# Inspect active states
No tagged packets on em1 indicates a switch, cabling, parent-interface, or VLAN-identifier problem. Packets on a VLAN interface but no forwarding indicate an address, route, forwarding, or PF problem.
Operational Notes #
- A switch access port normally carries one untagged network and does not replace the tagged trunk used here.
- Native or untagged traffic on the trunk requires a separate, explicit design. Do not assume a switch’s native VLAN behavior.
- DHCP service requires one subnet declaration per served VLAN and explicit daemon interface flags.
- IPv6 requires routed prefixes, router advertisements, ICMPv6, and a separate PF policy. Route IPv6 Networks without NAT covers that design.
- VLAN separation does not itself enforce policy. PF, switch policy, and service binding must agree.
See Networking for the concise VLAN interface reference and Troubleshoot PF, NAT, and Routing for a layer-by-layer diagnostic procedure.