Eric Radman : a Journal

A Wireless Router with UPnP and Authpf

Hardware I most recently purchased, and I highly recommend:

System board apu4d2
Wireless adaptor wle200nx
mSATA SSD msata16g
USB serial adapter usbcom1a

They come equiped with coreboot and a SeaBIOS payload by default. Total cost is less than $170 USD. See also the dmesg running OpenBSD 6.6. These systems

For an alternate wireless configuration, I have also used the M.2 DW1820 wireless card with NGFF antennas and a M.2 to miniPCIe adaptor.

There is no video port, so be sure to set the tty to com0 at the OpenBSD boot loader

stty com0 115200
set tty com0

And enable the the login prompt for your terminal in /etc/ttys

tty00   "/usr/libexec/getty std.115200" vt220    on secure

To connect from a USB-to-RS232 adaptor on another host, run

cu -s 115200 -l cuaU0

Initial Router Configuration

Your service provider most likely uses DHCP to hand out addresses. If you have used another router you can keep the same IP by setting lladdr

# /etc/hostname.em0
dhcp lladdr 4C:E6:76:AB:E8:26
inet6 autoconf

For the inside address I use a virtual interface so that I can use a bridge to connect several interfaces together on the same subnet

# /etc/hostname.vether0

Creating a bridge that includes this interface is as easy as adding the participating interfaces

 # /etc/hostname.bridge0
 add vether0
 add em0
 add em1
 add em2
 add em3
 add bwfm0

All of these changes can be applied simply by running rc(8)

$ doas sh /etc/rc

Wireless AP

I have used two wireless cards with OpenBSD: bwfm(4) and athn(4)

Both of these require a firmware package, so be sure fw_update was able to run before you try to initialize them. To see the available modes of operation, run

$ ifconfig athn0 media | grep hostap
      media: IEEE802.11 autoselect hostap (autoselect mode 11n hostap)
              media autoselect mediaopt hostap
              media autoselect mode 11a mediaopt hostap
              media autoselect mode 11b mediaopt hostap
              media autoselect mode 11g mediaopt hostap
              media autoselect mode 11n mediaopt hostap

Use this to construct a hostname.if(5) for your interface.

Firewall Config

First enable routing in /etc/sysctl.conf


I also set the system to reboot on a panic instead of dropping into the debugger


Then set up a basic set of rules in /etc/pf.conf to enables port-address translation

# macros

# tables

# options
set skip on lo
set block-policy return

# nat
match out on egress from nat-to (egress)

UPnP Functionality

Video calls (such as Facetime) that use a peer-to-peer media channel require some method of permitting inbound forwarding on arbitrary ports. To accomplish this we need a daemon such as MiniUPnP. Devices will request open ports using SSDP.

The basic config is as follows:

# /etc/miniupnpd.conf
allow 1024-65535 1024-65535
deny 0-65535 0-65535

Activate changes to PF by adding a pointer to the rules

anchor "miniupnpd"

Now when a video call is place we should be able to see the new dynamic rules

# pfctl -a 'miniupnpd/*' -s rules
pass in log quick on em0 inet proto udp from any to any port = 29999 \
      label "NAT-PMP 29999 udp" rdr-to port 29999
pass in log quick on em0 inet proto tcp from any to any port = 29999 \
      flags any label "NAT-PMP 29999 tcp" rdr-to port 29999


Authpf is a method of activating a set of rules based on who has successfully authenticated via SSH. It is activated by setting a user's shell to /usr/sbin/authpf. Any number of users may have their own configurations. The basic configuration files for a user named www-access are


For per-user configuration, authpf.conf may be empty, but must be present. The other two simply contain a signin message

Access enabled for all blocked devices

and the PF rules to apply

pass in quick on !lo inet proto tcp from any to any port 80
pass in quick on !lo inet proto tcp from any to any port 443

After connecting via SSH the new rules will be attached

# pfctl -a 'authpf/*' -s rules
anchor "www-proxy(25253)" all {
  pass in quick on ! lo inet proto tcp from any to any port = 80 flags S/SA
  pass in quick on ! lo inet proto tcp from any to any port = 443 flags S/SA

Content Filtering

I want my children and anyone else using guest Wifi at my home to have access to the Internet from their mobile devices, but not necessarily everything on the web. Blocking ports 80 and 443 is too broad since many applications speak over those ports. OpenBSD's relayd provides an excellent mechanism for implementing transparent HTTP filtering.

It is easy enough to filter by URL, but this is never what I want--I am interested in particular content. Here are the HTTP headers for the video player for PBS KIDS:

$ curl  -s -D -
Content-Type: application/x-shockwave-flash
Content-Length: 16743
Connection: keep-alive
Date: Wed, 30 Dec 2015 13:38:06 GMT
Server: Apache
Last-Modified: Wed, 09 Dec 2015 23:07:30 GMT
Accept-Ranges: bytes
Access-Control-Allow-Origin: *
Cache-Control: max-age=3600
Expires: Wed, 30 Dec 2015 14:38:06 GMT
X-PBS-appsvrname: kids-fwcacheproxy10
Via: (t=1451482686108763), 1.1 (CloudFront)
Cache-Control: s-maxage=3600
Age: 40974
X-Cache: Hit from cloudfront
X-Amz-Cf-Id: 2gi8kI02gYSn-AW4a1tyiAj2LWD8geZb5yC9rfFZfDeV4tbGKoFXpQ==

For most web browsers blocking anything with a Content-Type set to application/x-shockwave-flash will suffice, but the iPad app doesn't use Flash. Instead I added a rule to match anything with a header named X-PBS-appsvrname. Here are the basic rules

# /etc/relayd.conf
http protocol httpfilter {
  return error style 'body { background: #f3f3f3; color: #606060; }'

  match label "Content blocked"
  block response quick header "Content-Type" value "application/x-shockwave-flash"
  block response quick header "Content-Type" value "video/x-flv"
  block response quick header "X-PBS-appsvrname" value "*"

relay plaininspect {
      listen on port 8080
      protocol httpfilter
      forward to destination

If we are willing to put up with certificate warnings on the clients we can also filter TLS (https://). The rules are almost identical to the filter for http://

http protocol httpsfilter {
  return error style 'body { background: #f3f3f3; color: #606060; }'

  match label "Content blocked"
  block response quick header "Content-Type" value "video/quicktime"

  # Configuration directives for SSL/TLS Interception
  tls ca key "/etc/ssl/private/ca.key" password "secret"
  tls ca cert "/etc/ssl/ca.crt"
  tls { sslv3, tlsv1 }

relay tlsinspect {
  listen on port 8443 tls
  protocol httpsfilter
  forward with tls to destination

relayd needs two certificates: one to be used as a fake client (enter the passphrase "secret), and the other without a password for the server side

# openssl req -x509 -days 365 -newkey rsa:2048 \
  -keyout /etc/ssl/relayd/ca.key -out /etc/ssl/relayd/ca.crt

# openssl req -nodes -x509 -days 365 -newkey rsa:2048 \
  -keyout /etc/ssl/private/ -out /etc/ssl/

Finally we redirect hosts not on a given whitelist using PF

table <permitted> {, }
pass in on !egress inet proto tcp from any to port 443 divert-to localhost port 8443
pass in on !egress inet proto tcp from any to port 80 divert-to localhost port 8080
pass in on !lo inet proto tcp from <permitted> to any port {80, 443}

Now rather than being subjected to the Internet as if we it were cable TV, we tell the Internet what it can do for us.


The difficult part to this arragement is that many applications on mobile devices communicate over the standard port for https. Selectively allowing services to run will often require a support ticket to find out exactally what kind of rule to add

# Zello uses 443 and 28225
pass in on !lo inet proto tcp from any to port 443

Another approach is to allow certain services (such as automatic updates) to run during a window of time. Here is a crontab that allows all web traffic from 4:00 to 8:00 in the morning

00      4       *       *       *       pfctl -a http -f /etc/anchor.http_allow
00      8       *       *       *       pfctl -a http -F rules
pass in proto tcp from to any port { 80 443 }

Power Monitoring

Battery backup and remaining runtime for a UPS can be monitored thanks to the upd(4) device

$ sysctl hw.sensors.upd0
hw.sensors.upd0.indicator0=On (BatteryPresent), OK
hw.sensors.upd0.indicator1=On (Charging), OK
hw.sensors.upd0.indicator2=Off (Discharging), OK
hw.sensors.upd0.indicator3=Off (NeedReplacement), OK
hw.sensors.upd0.indicator4=Off (ShutdownImminent), OK
hw.sensors.upd0.indicator5=On (ACPresent), OK
hw.sensors.upd0.indicator6=Off (Overload), OK
hw.sensors.upd0.percent0=99.00% (RemainingCapacity), OK
hw.sensors.upd0.percent1=100.00% (FullChargeCapacity), OK
hw.sensors.upd0.timedelta0=28819.000000 secs (RunTimeToEmpty), OK

Last updated on June 15, 2021