During the miniupnp project, I experienced the control of OpenBSD 3.0+ pf with the dedicated ioctl interface. Considering the difficulties I came through, I thought that making this small page was worth the effort. The pf(4) man page is a good starting point but lacks details to allow things to simply work. It is often needed to look at pfctl code to understand how things are supposed to be used.
To put it in a nutshell, miniupnpd expose a SOAP API to allow upnp clients (like an evil MSN client running on an evil Windows XP) to add and delete NAT redirection rules to the NAT gateway. Miniupnpd is using pf's ioctl interface to add and delete the rules.
To avoid trashing the main ruleset, miniupnpd is using an anchor and keeps all its rules under it. To allow the anchor's rules to be evaluated, the line
rdr-anchor miniupnpdhas to be added to the pf.conf(5) configuration file. Run
pfctl -f /etc/pf.confto have this taken into account.
The obsdrdr.c source file contains function to add a rule to the miniupnpd ruleset, get details from a rule, and delete a rule from this ruleset. The add_redirect_rule() function is in fact not doing what it should. I was wrongly using DIOCXBEGIN/DIOCADDRULE/DIOCXCOMMIT to add a single rule to the existing ruleset. Using these functions in fact clear the ruleset from all existing rules :) The right way to proceed is shown it the add_redirect_rule2() function using DIOCCHANGERULE that could be understood as "change ruleset".