1

There are a few answers on using pfctl on os x, but none really give a nice clean way to make an anchor and have it loaded on startup, or to allow programs to hook in and dynamically update / replace rules, or so it can also be removed without clobbering system rules if needed.

What is a good way to set up such a config so your computer can, say, SNAT when going out of some interface?

Some of the other questions are:

pfctl - howto add an anchor and make it active / load it

pfctl to add rules at runtime WITHOUT editing /etc/pf.conf?

pf: Dynamically add rule to nat-anchor

nate
  • 11

2 Answers2

2

It is very strange, that there is no official documentation, how to properly use pf with macOS. I don't know if i'm using it as intended. But the solution works in macOS 11.6 and has survived the update to macOS 12.0.1.

I use pf to redirect port 80 to 8080 and 443 to 8443. Should work equally for nat rules. My pfctl.plist is just a copy from /System/Library/LaunchDaemons/com.apple.pfctl.plist with modified ProgramArguments.

/etc/pf.anchors/ves

ext_ip = "192.168.1.201"

rdr pass inet proto tcp from any to $ext_ip port 80 -> $ext_ip port 8080 rdr pass inet proto tcp from any to $ext_ip port 443 -> $ext_ip port 8443

Dynamically loading and enabling with sudo pfctl -a 'com.apple/ves' -f /etc/pf.anchors/ves -e. Dynamically removing with sudo pfctl -a 'com.apple/ves' -F all.

Persisting between reboots with a LaunchDaemon.

/Library/LaunchDaemons/ves.pfctl.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" 
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Disabled</key>
  <false/>
  <key>Label</key>
  <string>ves.pfctl</string>
  <key>WorkingDirectory</key>
  <string>/var/run</string>
  <key>Program</key>
  <string>/sbin/pfctl</string>
  <key>ProgramArguments</key>
  <array>
    <string>pfctl</string>
    <string>-a</string>
    <string>com.apple/ves</string>
    <string>-f</string>
    <string>/etc/pf.anchors/ves</string>
    <string>-e</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
</dict>
</plist>

sudo launchctl load /Library/LaunchDaemons/ves.pfctl.plist

xoryves
  • 141
0

I am answering this with what i've done so far, cuz it took some digging and could be useful to someone. You could make something more complicated based on this, it's just kind of a start. If someone posts something better i will accept that, i guess.

A more complicated solution would save tokens and use '-E' and '-X' to retain and release references to the packet filtering system, or allow unloading the service, but i don't do that here. Note that launchd does not really support running a command when unloading a service. You could write a script to keep a process alive and handle the shutdown, or in most cases you would probably just handle flushing rules for the anchor when shutting down your process for your application.

Setup

Save all of the below files to their locations and do the following:

sudo launchctl load -w /Library/LaunchDaemons/org.myorg.mypf.plist 
# Note: the next step turns on ip forwarding for your system.
# You only need to do this if you are doing actual NAT
# and not just referencing this answer for something else like packet
# filtering
sudo sysctl -w net.inet.ip.forwarding=1

If you would like ip forwarding to persist across boots, create (if needed) /etc/sysctl.conf and set contents:

/etc/sysctl.conf

...
net.inet.ip.forwarding=1
...

Again, this is only necessary to make NAT work, as we are in this specific example. If you are doing something else, you don't need it.

Files

/Library/LaunchDaemons/org.myorg.mypf.plist

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE plist PUBLIC "-//Apple Computer/DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>org.myorg.pfsettings</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/local/bin/myorg-install-pf</string>
    </array>
    <key>LaunchOnlyOnce</key>
    <true/>
  </dict>
</plist>

/usr/local/bin/myorg-install-pf

#!/usr/bin/env zsh

/sbin/pfctl -f - <<EOF scrub-anchor "org.myorg/" nat-anchor "org.myorg/" rdr-anchor "org.myorg/" dummynet-anchor "org.myorg/" anchor "org.myorg/*" EOF

Flush existing rules.

Not that useful for our one-shot, but could be useful in more complicated setups

/sbin/pfctl -a org.myorg -F all /sbin/pfctl -a org.myorg/system -F all

/sbin/pfctl -a org.myorg/system -f /usr/local/etc/pf.anchors/org.myorg/system

/usr/local/etc/pf.anchors/org.myorg/system

# Set 192.168.142.0/24 to your "inside" network, or see `man pf.conf` and
# use something else (you could use someif0:network, for example)
nat on en0 inet from 192.168.142.0/24 to any -> (en0)
nate
  • 11