Macos – Prevent outgoing traffic unless OpenVPN connection is active using pf.conf on Mac OS X

macmacosopenvpnwireless-networking

I've been able to deny all connections to external networks unless my OpenVPN connection is active using pf.conf. However, I lose Wi-Fi connectivity if the connection is broken by closing and opening the laptop lid or toggling Wi-Fi off and on again.

  • I'm on Mac OS 10.8.1.
  • I connect to the Web via Wi-Fi (from varying locations, including public Wi-Fi).
  • The OpenVPN connection is set up with Viscosity.

I have the following packet filter rules set up in /etc/pf.conf

# Deny all packets unless they pass through the OpenVPN connection
wifi=en1
vpn=tun0

block all

set skip on lo
pass on $wifi proto udp to [OpenVPN server IP address] port 443
pass on $vpn

I start the packet filter service with sudo pfctl -e and load the new rules with sudo pfctl -f /etc/pf.conf.

I have also edited /System/Library/LaunchDaemons/com.apple.pfctl.plist and changed the line <string>-f</string> to read <string>-ef</string> so that the packet filter launches at system startup.

This all seems to works great at first: applications can only connect to the web if the OpenVPN connection is active, so I'm never leaking data over an insecure connection.

But, if I close and reopen my laptop lid or turn Wi-Fi off and on again, the Wi-Fi connection is lost, and I see an exclamation mark in the Wi-Fi icon in the status bar. Clicking the Wi-Fi icon shows an "Alert: No Internet connection" message:

No Internet connection message

To regain the connection, I have to disconnect and reconnect Wi-Fi, sometimes five or six times, before the "Alert: No Internet connection" message disappears and I'm able to open the VPN connection again. Other times, the Wi-Fi alert disappears of its own accord, the exclamation mark clears, and I'm able to connect again. Either way, it can take five minutes or more to get a connection again, which can be frustrating.

Removing the line block all resolves the problem (but allows insecure connections), so it seems there's a service I'm blocking that Apple requires in order to regain and confirm a Wi-Fi connection. I have tried:

  • Enabling icmp by adding pass on $wifi proto icmp all to pf.conf
  • Enabling DNS resolution by adding pass on $wifi proto udp from $wifi to any port 53
  • Trying to learn more by logging blocked packets (by changing block all to block log all), but logging seems to be disabled under OS X, because doing sudo tcpdump -n -e -ttt -i pflog0 to see the log results in "tcpdump: pflog0: No such device exists".

None of this helps re-establish a Wi-Fi connection any faster.

What else can I do to determine what service needs to be available to regain Wi-Fi connectivity, or what rule should I add to pf.conf to make Wi-Fi reconnections more reliable?

Best Answer

By monitoring network connections using Little Snitch, I've found that Apple uses the mDNSResponder app in the background to check if the Wi-Fi connection is available. mDNSResponder sends UDP packets to nameservers to check connectivity and resolve hostnames to IPs.

Changing the UDP rule I had previously to allow all UDP packets over Wi-Fi allows mDNSResponder to connect, which means Wi-Fi now reconnects first time after a disconnection. In case it helps others in future, my final pf.conf including Apple's default rules for Mountain Lion looks like this:

#
# com.apple anchor point
#
scrub-anchor "com.apple/*"
nat-anchor "com.apple/*"
rdr-anchor "com.apple/*"as
dummynet-anchor "com.apple/*"
anchor "com.apple/*"
load anchor "com.apple" from "/etc/pf.anchors/com.apple"

#
# Allow connection via Viscosity only
#
wifi=en1 #change this to en0 on MacBook Airs and other Macs without ethernet ports
vpn=tun0
vpn2=tap0

block all

set skip on lo          # allow local traffic

pass on p2p0            #allow AirDrop
pass on p2p1            #allow AirDrop
pass on p2p2            #allow AirDrop
pass quick proto tcp to any port 631    #allow AirPrint

pass on $wifi proto udp # allow only UDP packets over unprotected Wi-Fi
pass on $vpn            # allow everything else through the VPN (tun interface)
pass on $vpn2           # allow everything else through the VPN (tap interface)

This means that data can now be leaked over Wi-Fi by the small number of applications that use the UDP protocol, unfortunately, such as ntpd (for time synchronisation) and mDNSResponder. But this still seems better than allowing data to travel unprotected over TCP, which is what the majority of applications use. If anyone has any suggestions to improve on this setup, comments or further answers are welcome.

Related Question