Text to Search... About Author Email address... Submit Name Email Adress Message About Me page ##1## of ##2## Jan Feb Mar Apr May Jun Jul Aug Sept Oct Nov Dec



Sorry, this page is not avalable

Latest Articles

The modern OpenBSD home router

It’s no secret that most consumer routers ship with software that’s flaky at best, and prohibitively insecure at worst. While I’ve had good experiences with OpenWrt and pfSense, I wanted to build a router from the ground up, both to understand the stack and to have something to tinker with. I found many solid tutorials out there, but few of them covered the intricacies of both PPP and IPv6. Here’s what I’ve learned.

Choosing the hardware
The hardware itself is not very modern at all. In the interest of a challenge, I chose the oldest computer that could reasonably replace the Billion 7800N that I was using. Meet the IBM Aptiva, model 2194, whose tiny 95 watt power supply drives some hardware that’s nearly old as myself:
cpu0: Intel Pentium III ("GenuineIntel" 686-class) 602 MHzbios0: vendor IBM version "PTKT09AUS" date 05/15/2000bios0: IBM 219443Aral0 at pci1 dev 3 function 0 "Ralink RT2561S" rev 0x00: irq 11, address 00:21:29:e2:c6:03em0 at pci1 dev 4 function 0 "Intel 82541GI" rev 0x05: irq 5, address 00:1b:21:56:16:9cem1 at pci1 dev 5 function 0 "Intel 82541GI" rev 0x05: irq 3, address 00:1b:21:56:1b:86spdmem0 at iic0 addr 0x50: 128MB SDRAM non-parity PC133CL2spdmem1 at iic0 addr 0x51: 128MB SDRAM non-parity PC133CL2

After upgrading the memory’s speed and capacity, choosing Ethernet cards was easy. Almost all of mine were based on a Realtek RTL81xx or Intel 82451PI controller, both of which have excellent support with OpenBSD. The Realtek-based cards I owned had an empty socket for a PXE ROM, or no socket at all, so I opted for a pair of Intel PRO/1000 GT Desktop cards instead. A quick test with nc(1) shows that I can only push about 27 MiB/s through the router before the CPU becomes a bottleneck.
Finding a wireless card was more difficult, even though the spare cards I had only supported 802.11g. Of those, only one has a driver that supports hostap mode:

  • SMC SMCWPCI-G (Qualcomm Atheros AR5005G(S) 168C:001A): no driver
  • Netgear WPN311 V1H2 Rev. A3 (Atheros AR5005G(S) 168C:001A): no driver
  • Netgear WG311v3 Rev. A1 (Marvell Libertas 88W8335 11AB:1FAA): malo(4) ✗
  • Cisco Linksys WMP54G ver. 4.1 (Ralink RT2561S 1814:0301): ral(4) ✓
Conveniently, unlike Marvell, Ralink has also allowed OpenBSD to freely distribute the required firmware without necessitating the use of fw_update(1).
I’m still connected to the outside world through an ADSL service with Internode. Because internal modems for anything newer than G.992.1 are hard to come by, I simply dug out an old Netgear DG834Gv2 and put it into bridge mode.
To put the DG834Gv2 into bridge mode, head to /setup.cgi?next_file=mode.htm on the device, then ensure that the ADSL parameters are correct and wireless is disabled. The 7800N would not only have been a waste of fancy hardware, but doing the same on that made it unreachable over IPv4, whereas the DG834Gv2 remained reachable. On the other hand, the 7800N can lie to your DSLAM or MSAN about the SNR of an ADSL connection.
Having configured the hardware and installed OpenBSD 5.7 without any problems, connecting to the Internet soon became my next task.

Interface layout
Behind every fancy router is a bridge, and a bridge is what I made:
% cat /etc/hostname.bridge0
add vether0
add em0
add em1
add ral0 up
em0 goes to the DG834Gv2, em1 goes to a D-Link DGS-1008D, a dumb switch, ral0 hosts the wireless network, and vether0 serves two purposes: not only does it decouple whether or not IPv4 and IPv6 will work from whether or not a particular physical interface is up, but it also yields a stable interface identifier that’s independent of any physical interface.
% cat /etc/hostname.em0

% cat /etc/hostname.em1

% cat /etc/hostname.ral0
media autoselect mode 11g mediaopt hostap
nwid deLAN
wpakey hunter13
wpaprotos wpa2
wpaakms psk
wpaciphers ccmp
wpagroupcipher ccmp

% cat /etc/hostname.vether0
inet6 eui64

% sh /etc/netstart em0 em1 ral0 vether0 bridge0
Reaching the outside world
As far as I know, Internode is the only residential ISP in Australia that provides native IPv6 without the use of any transition mechanisms. While some consumer routers like the 7800N Just Work™ in this regard, others don’t, like the Netgear WNDR3700v2, for which the best connectivity reachable with stock firmware is 6to4, because it otherwise erroneously tries to establish two separate PPP sessions — one for each protocol.
Looking downwards, the stack of protocols sent over an Internode ADSL connection, sometimes known as PPPoEoA for short, is rather complex:

  • Transport layer or application layer payload
  • IPv4 packet header or IPv6 packet header [RFC 791, 2460]
  • PPP packet header (2 octets, type 002116 or 005716) [RFC 1661, 1332, 5072]
  • PPPoE header (6 octets) [RFC 2516 § 4]
  • Ethernet II frame (18 octets, EtherType 886316 or 886416) [IEEE 802.3-2012]
From here below, the ADSL side of the DG834Gv2 continues:

  • LLC Encapsulation for Bridged Protocols (10 octets) [RFC 2684 § 5.2]:
  •   IEEE 802 SNAP header (5 octets, OUI 00:80:C216, PID 000116) [IEEE 802]
  •   IEEE 802.2 LLC header (3 octets, AA:AA:0316) [IEEE 802.2]
  • AAL5 CPCS-PDU trailer (8 octets) [RFC 2684 § 4, ITU-T Rec. I.363.5]
  • ATM cell header (5 octets)
  • ADSL2+ physical layer [ITU-T Rec. G.992.5]
Alternatively, the Ethernet side of the DG834Gv2 continues:

  • Ethernet packet (8 octets) [IEEE 802.3-2012]
  • Ethernet interpacket gap (12 octets) [IEEE 802.3-2012]
  • 100BASE-TX physical layer [IEEE 802.3-2012]
IPv4 addresses are obtained with the PPP Internet Protocol Control Protocol [RFC 1332]. This includes dynamic and static addresses, but does not include routed subnets. IPv6 is where the situation becomes more confusing. The IPv6 Control Protocol [RFC 5072] is only used to negotiate a unique interface identifier for the client, because PPP is only concerned with link-local communication between a pair of peers. Even if no globally routable prefixes are assigned, once this negotiation is complete, IPv6 traffic to the ISP peer is technically possible, although not very useful of course.
After some hours hacking away because the OpenBSD 5.7 manual page for pppoe(4) was incorrect, I reached this configuration:
% cat /etc/hostname.pppoe0
!/sbin/ifconfig em0 up
inet NONE \
pppoedev em0 \
authproto chap \
authname '[email protected]' \
authkey hunter2
inet6 eui64
!/sbin/route add -ifp pppoe0
!/sbin/route add ::/0 -ifp pppoe0 fe80::

% sh /etc/netstart pppoe0

The two errors were subtle but fatal. Placing inet6 eui64 before the PPPoE parameters inadvertently brings the interface up, after which the parameters can’t be changed. Viable solutions include placing down just after inet6 eui64, or simply moving inet6 eui64 after the PPPoE parameters. Because of a peculiarity with route(8), add default fe80:: doesn’t work either unless the -inet6 option is specified, because is assumed, and incompatible address families ensue. I’ve since sent a patch to [email protected].

From here, either or both of two paths can be taken to obtain a globally routable IPv6 prefix: NDP Router Solicitation [RFC 4861] for the dynamic /64, or DHCPv6 IA_PD [RFC 3633] for the static /56.
% ifconfig pppoe0 inet6 autoconf
One command is required to start sending Router Solicitation messages, and ifconfig(8) handles it now that rtsold(8) has been removed. Because [RFC 4861] doesn’t specify whether or not routers are allowed to send Router Solicitation messages, OpenBSD errs on the side of caution, and will not send them if net.inet6.ip6.forwarding is enabled.
The wide-dhcpv6 package provides dhcp6c(8) for DHCPv6 IA_PD, or prefix delegation. Note that even though the /56 prefix is static, the prefix delegation process must still occur so that Internode can update its routing tables, something that I spent half a day scratching my head at when I tried to cut corners.
% pkg_add wide-dhcpv6

% cat /etc/rc.d/dhcp6c


. /etc/rc.d/rc.subr


rc_cmd $1

% cat /etc/dhcp6c.conf
interface pppoe0 {
send ia-pd 0;
send domain-name-servers;
send rapid-commit;

id-assoc pd {
prefix-interface vether0 {
sla-id 0;
sla-len 8;

% echo 'dhcp6c_flags=pppoe0' | tee -a /etc/rc.conf.local

% echo '!/etc/rc.d/dhcp6c restart' | tee -a /etc/hostname.pppoe0
!/etc/rc.d/dhcp6c restart

% /etc/rc.d/dhcp6c restart


Turning a client into a router
Most of the work involved in configuring routing involves pf(4) and pf.conf(5), but I left that until last. Before that I set the DNS search domain and resolvers to sane defaults, and enabled packet forwarding for both IPv4 and IPv6.
% cat /etc/resolv.conf
search home.daz.cat

% cat /etc/sysctl.conf

% xargs sysctl < /etc/sysctl.conf
net.inet.ip.forwarding: 0 -> 1

net.inet6.ip6.forwarding: 0 -> 1
From there I moved to DHCP, something that most people are familiar with.
% cat /etc/dhcpd.conf
subnet netmask {
default-lease-time 3600;
max-lease-time 604800;
option routers;
option domain-name-servers,;
option domain-name "home.daz.cat";
% echo 'dhcpd_flags=vether0' | tee -a /etc/rc.conf.local
% /etc/rc.d/dhcpd restart

As for IPv6, rtadvd(8) goes a long way by sending Router Advertisement messages, propagating the globally routable IPv6 prefix where it’s available. I’m leaving clients to generate their own interface identifiers using SLAAC [RFC 4862, 4941] instead of running a stateful DHCPv6 [RFC 3315] server. In its stateless form, DHCPv6 is only necessary to advertise IPv6 DNS servers, which I’ll get around to doing eventually.
% echo 'rtadvd_flags=vether0' | tee -a /etc/rc.conf.local

% /etc/rc.d/rtadvd restart

pf.conf(5) is where I spent a few full days of my time. I paid careful attention to RFCs when I was deciding which address blocks to drop traffic from [RFC 6890] and which ICMPv6 message types and codes to allow through the router [RFC 4890].

### ~~~ Interface layout ~~~ ###
# em0: 802.3ab to ADSL modem (
# ral0: 802.11g in hostap mode
# bridge0: Ethernet bridge over all of the above
# pppoe0: PPPoE session over em0
### ~~~ Constants and variables ~~~ ###
# All addresses associated with this host

# RFC 6890: Special-Purpose IP Address Registries:
# https://www.iana.org/assignments/iana-ipv6-special-registry/
# Included below are all address blocks with either Forwardable = False,
# superseded by more specific allocations, as of 2015-08-05.
table const { \, \, \, \, \, \, \, \, \, \, \, \, \, \, \
::1/128, \
::/128, \
::ffff:0:0/96, \
100::/64, \
2001::/32, \
2001:2::/48, \
2001:db8::/32, \
fc00::/7, \
fe80::/10 \

set skip on lo
# Normalise packets, especially IPv4 DF and Identification

match on pppoe0 scrub (max-mss 1440)
# Block all packets by default, logging them to pflog0

pass out quick on egress inet6 proto udp from (egress) to ff02::1:2 port dhcpv6-server

# vether0 is necessary here, but bridge0 is not

pass out quick on egress inet6 from { egress, (vether0:network) } modulate state
# Pass all outbound IPv4 traffic from this host

pass out quick on egress inet from (vether0:network) nat-to (egress) modulate state
### ~~~ Block undesirable traffic ~~~ ###
# These rules must not precede the DHCPv6 client or NAT rules above
block log quick on egress to { no-route, }
### ~~~ Pass some ICMP and ICMPv6 traffic ~~~ ####
# Pass all inbound ICMP echo requests

pass quick on egress inet6 proto icmp6 icmp6-type { 1, 2, 128, 129 }
pass quick on egress inet6 proto icmp6 icmp6-type 3 code 1
pass quick on egress inet6 proto icmp6 icmp6-type 4 code 1

pass in on egress proto { tcp, udp } to $self port ssh

unixlegion.com uses cookies to improve your experience. I agree