Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

Last active July 7, 2023 09:27
Show Gist options
  • Star 104 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save scy/8122924 to your computer and use it in GitHub Desktop.
Save scy/8122924 to your computer and use it in GitHub Desktop.
My OSX PF config for #30C3.

My OS X “VPN only” Setup For #30C3

You should never let passwords or private data be transmitted over an untrusted network (your neighbor’s, the one at Starbucks or the company) anyway, but on a hacker congress like the #30C3, this rule is almost vital.

Hackers get bored easily, and when they’re bored, they’re starting to look for things to play with. And a network with several thousand connected users is certainly an interesting thing to play with. Some of them might start intercepting the data on the network or do other nasty things with the packets that they can get.

If these packets are encrypted, messing with them is much harder (but not impossible! – see the end of this article). So you want your packets to be always encrypted. And the best way to do that is by using a VPN.

Target audience

Please note that this guide is written for users who are at least a bit tech-savvy. In case you don’t know what a VPN is or don’t feel comfortable doing the things I suggest in this guide (for example because you don’t understand anything at all), please contact a hacker you trust and ask her or him to make your connection more secure. Please don’t ask me (except if we know each other personally), because I don’t have the time to do user support for thousands of guests.

What’s being used?

The machine I’m using is a MacBook Air with OS X Mountain Lion, and the software I use is Tunnelblick for the OpenVPN connection and PF (“packet filter”) to transmit as little unencrypted traffic as possible when the VPN goes down. And the focus of this guide is definitely that second part, because setting up the VPN is pretty easy, but if the connection breaks, is blocked or for some other reason not running, your system will by default transmit data anyway, and unencrypted data will be interceptable. So what I basically do is to forbid nearly all network traffic (using PF), except for the things that are required to connect to the VPN. PF is OS X’s recommended way of filtering packets and available in Lion or higher, afaik.

The basic idea

So, what do we need in order to connect to the VPN?

  1. Internet access. This means we should allow DHCP (for getting an IP address) and ICMP (for being a nice internet citizen).
  2. DNS, if we don’t want to connect to the VPN by IP address. I’m using IPredator as the VPN provider, which uses several IP addresses and DNS Round Robin to spread the users across them, so I want to be able to use DNS.
  3. A connection to the VPN server(s) on whatever port the VPN uses. I’m using OpenVPN on UDP port 1194, and I highly recommend that you don’t use a TCP-based VPN connection. (Else, people might(?) be able to do evil things with DNS spoofing and SRV records.)

The config I use has several drawbacks:

  1. I completely disable IPv6. Yes, some of you will be outraged about that, and rightly so. But IPredator doesn’t support v6, and I didn’t find the time to configure my own VPN server. If I’d allow IPv6, all of my v6 connections would be bypassing the VPN!
  2. The DNS servers I use are Google’s, because I trust them a bit more than a shady Starbucks DNS. Note that rogue admins (or rogue users) can still try to spoof their IP address and impersonate Google’s servers, thus supplying you with incorrect DNS responses. That’s why in my setup TCP connections are only allowed over the VPN.
  3. There’s no automatic detection whatsoever whether OpenVPN is running, so if you want to use the Internet without the VPN again, you’ll need to explicitly disable PF.


There’s a nice guide that explains the PF setup on OS X, and I’m not doing anything more than that here. A tutorial on PF itself, which is a OpenBSD project, is available as well.

Set up OpenVPN and Tunnelblick

I’m using Tunnelblick as my OpenVPN GUI and IPredator as my VPN provider. Get an account, pay 6€ to activate it for a month (I’ve used PayPal, but you can also use BitCoin and other methods) and set it up according to the guides available on the IPredator website. Make sure that you enable “route all traffic through the VPN” in the “while connected” tab of the advanced settings. According to the Tunnelblick documentation, this is equivalent to the OpenVPN option --redirect-gateway def1.

I found Tunnelblick (3.3) to be kind of unstable on my machine. Sometimes I had to terminate OpenVPN myself (using sudo killall openvpn) because it wouldn’t reconnect and Tunnelblick wasn’t able to terminate it. But since the PF rules protect me from unencrypted communication, I don’t really care.

Having an exit strategy

If you fuck up your firewall rules, you might end up in a situation where you can’t even google how to fix things again. Therefore, keep this command in mind: sudo pfctl -d. It will completely disable PF.

The PF config

There’s a default PF configuration file, /etc/pf.conf, and I suggest you don’t modify it. Instead, write your own. Mine is /etc/pf.scy.conf.

In that file, you need to define one or more anchors which contain the actual rules. Therefore, my config file only contains these two lines:

anchor ""
load anchor "" from "/etc/pf.anchors/name.scy"

Use something unique for the anchor name. The suggested way is to use a reversed domain, so if you own, your anchor could be named I own, and thus it’s for me.

The really interesting part of the configuration is what’s written in the anchor file, /etc/pf.anchors/name.scy in my case. Since that file is a bit longer and contains lots of comments (which is a good thing), I won’t quote it here but instead link to a Gist containing it.

Loading it

Run sudo pfctl -v -n -f /etc/pf.scy.conf to check the config for errors, and sudo pfctl -e -f /etc/pf.scy.conf to actually load it.

Making sure it will be loaded after rebooting

I guess you’ll want your PF settings to survive a reboot, so you need to add a launchd item for it. This is simply an XML file that you place in /Library/LaunchDaemons. Note that it must be owned by root in order to do anything at all.

Do something like sudo nano -w /Library/LaunchDaemons/ (pay attention to the .plist extension) and paste the text you can find in the Gist there.

Please note that this might not work. The article I’ve linked to above suggests doing it this way, but on my machine /var/log/pf.log contained the error message

pfctl: DIOCADDRULE: Resource busy

and the rules were not loaded. So please double-check if your PF rules have been loaded, and load them manually, if necessary. If you reboot and Dropbox starts syncing without the VPN running, you’re doing it wrong. ;)

I currently don’t know why this is happening, and certainly not how to fix it. Sorry.

# This is my PF config for 30C3. Its purpose is to make sure that my system uses
# the IPredator OpenVPN connection for everything (this is controlled by
# OpenVPN/Tunnelblick's routing) and not to communicate unencrypted when the VPN
# connection goes down. Therefore, I block everything on the physical interfaces
# except for ICMP, DHCP, DNS and the VPN connection.
# Define interfaces. I'm on a MacBook Air, so en0 is WiFi and en1 my (optional)
# USB Ethernet adapter.
# Default block policy is sending a RST. This allows applications to quickly
# notice that they are not allowed to connect.
set block-policy return
# Don't filter on local loopback or the VPN interface.
set skip on { lo0 $vpn }
# Scrub all incoming packets.
scrub in all
# Don't allow IPv6 at all. This is sad, but IPredator doesn't support it.
block quick inet6
# By default, don't allow anything on the actual physical links.
block on { $wifi $ether }
# Allow ICMP.
# If you're looking for stealthy ping blocking or the like, this config is not
# for you.
pass quick proto icmp
# Allow DHCP.
# I could probably be more specific than "from any", but didn't find a way to
# specify the link's local network universally. "from $wifi:network" works, but
# "from $ether:network" for example doesn't as long as $ether does not exist.
pass quick on { $wifi $ether } proto udp from any port 67:68
# Allow DNS to Google, although on a rogue network they could still easily
# redirect you to another DNS server.
pass out quick proto udp to { } port 53
# Allow iPredator VPN.
# The destination address specification is a bit loose, but I couldn't get a
# complete list of hosts. Only allowing UDP 1194 should be enough to keep
# possible risks to a minimum.
pass quick proto udp to port 1194
# vim: ft=pf
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE plist PUBLIC "-//Apple Computer/DTD PLIST 1.0//EN" "">
<plist version="1.0">
<string>FreeBSD Packet Filter (pf) daemon</string>
anchor ""
load anchor "" from "/etc/pf.anchors/name.scy"
Copy link

I just took a look at the DIOCADDRULE: Resource busy error as I was experiencing it too. As it turns out, this has already been discussed in your linked article’s comments (see Danie Lord’s first comment). Waiting for all interfaces to come up using ipconfig waitall did the trick.

I also cleaned up the rest of a bit, maybe you want to merge some of my changes.

Copy link

aaronk6 commented Jul 29, 2015

Thanks, @SonicHedgehog! ipconfig waitall is much cleaner than the ugly sleep 5 which I had been using before I found this Gist :-)

Copy link

Lot of life-time deals from 20 to 50$ for VPN:
I bought vpn unlimited, tigger and ipinator (no deal for the moment).
[Avoid IPinator deal, it is in fact the limited version without premium access after ~30 days!]

Copy link

Some of my notes in issues for other DNS, tools...

Copy link

dato commented May 18, 2016

To make DNS spoofing difficult, you could use a DNSCrypt resolver on port 443. Or, if you continue to trust Google, their recently launched DNS-over-HTTPS, currently in beta. (As I understand it, it’s just an HTTP API to their public resolvers.)

Copy link

bpinto commented Sep 6, 2016

Isn't it not blocking ipv6 on vpn connection?

# Don't filter on local loopback or the VPN interface.
set skip on { lo0 $vpn }

# Code below is never called because it was already skipped on VPN interface.
# Don't allow IPv6 at all. This is sad, but IPredator doesn't support it.
block quick inet6

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment