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

16,202

Solution 1

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.

Solution 2

You don't need to allow all UDP. The 'm' in mDNS means 'multicast', and it uses a specific multicast destination IP address called the "link local multicast address", and a UDP port number 5353.

This means in your solution above, you are unnecessarily allowing traffic to all 65535 UDP ports to all 3.7 Billion routable IP addresses in the world to bypass your VPN. You'd be surprised how many applications use UDP, so you are significantly defeating the purpose of your original idea to prevent outgoing traffic when the VPN is down.

Why not use this rule instead:

pass on $wifi proto udp to 224.0.0.251 port 5353

A very important rule of thumb with firewall configuration - when making exceptions through your firewall, always try to use the most specific rule possible. The specificity sometimes comes at the expense of convenience & ease of use, i.e. you might then find there's some other link-local protocol that needs to be let through, and add yet another specific rule.

If you swap in the above rule and find that the original wifi problem returns, then your PF may be blocking DHCP, the protocol used to autoconfigure your network devices' IP addresses. (in a home network, typically your broadband router would be your DHCP server). The rule you'd need to allow DHCP, would be:

pass on $wifi proto udp from 0.0.0.0 port 68 to 255.255.255.255 port 67

*Note: you may need to substitute 0.0.0.0 for any. The DHCPREQUEST packet your computer first sends, has a source address 0.0.0.0 because at that stage, your computer doesn't have an IP address yet.
To be honest, I would lean more towards using any. Another option is to rip out any source specification, i.e. pass on $wifi proto udp to 255.255.255.255 port 67, but that means we lose the source-port part of the rule, and being as specific as possible is always the most secure option.

Hope that helps. Here are some useful links:

mDNS: http://en.wikipedia.org/wiki/Multicast_DNS#Packet_structure

DHSP: http://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol#DHCP_discovery

Solution 3

With the aim to create the PF rules in an "easy" way, identifying existing active interfaces including the current (vpn) interfaces this small killswitch program may be used,

Still in progress but could be a good start for identifying external IP, and active interfaces in order to properly create the firewall rules.

example or output using the -i (info) option:

$ killswitch -i
Interface  MAC address         IP
en1        bc:57:36:d1:82:ba   192.168.1.7
ppp0                           10.10.1.3

public IP address: 93.117.82.123

Passing the server ip -ip:

# --------------------------------------------------------------
# Sat, 19 Nov 2016 12:37:24 +0100
# sudo pfctl -Fa -f ~/.killswitch.pf.conf -e
# --------------------------------------------------------------
int_en1 = "en1"
vpn_ppp0 = "ppp0"
vpn_ip = "93.117.82.123"
set block-policy drop
set ruleset-optimization basic
set skip on lo0
block all
pass on $int_en1 proto udp to 224.0.0.251 port 5353
pass on $int_en1 proto udp from any port 67 to any port 68
pass on $int_en1 inet proto icmp all icmp-type 8 code 0
pass on $int_en1 proto {tcp, udp} from any to $vpn_ip
pass on $vpn_ppp0 all

Is far from perfect but work is in progress more info/code can be found here: https://github.com/vpn-kill-switch/killswitch

Share:
16,202

Related videos on Youtube

Nick
Author by

Nick

Updated on September 18, 2022

Comments

  • Nick
    Nick over 1 year

    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?

  • jakev
    jakev over 11 years
    This is something I've casually been interested in, seeing your results has inspired me to go home and try it! thanks!
  • Nick
    Nick over 11 years
    @SixSlayer It seems to work pretty well! I have Viscosity set up to autoconnect on startup and on dropped connections, which makes the whole thing pretty much seamless. Main thing to note is that pf.conf and com.apple.pfctl.plist get reset to the default after OS updates, apparently, so it's worth keeping a backup of both.
  • jakev
    jakev over 11 years
    IMHO the UDP thing is kind of a bummer. I'm not a network guy but this kinda thing helps me learn, and I have a fascination with having control over these kinds of details. I'll spend some time looking for a work around, but if anyone beats me to it, just as well.
  • keo
    keo over 11 years
    this is awesome - exactly what I was looking for. Thank you!
  • keo
    keo over 11 years
    Have you maybe managed to have many OpenVPN connections open at the same time, and routing through them in parallel? (to gain and add up bandwidth)
  • Nick
    Nick over 11 years
    @keo Just pick a single VPN provider who doesn't cap throughput. I used speedtest.net to test speeds with the VPN connection turned off (you have to temporarily disable the packet filter with sudo pfctl -d for this to work). Then I connected to the VPN, tested the speed again, and compared it with the original. I've found a few providers who cap speeds and a few who don't (e.g. VyprVPN). You should expect to lose a little speed even for uncapped providers. (And you can re-enable the packet filter with sudo pfctl -e.)
  • sghael
    sghael over 11 years
    Works great! Note for other users: on my MacBook Air wifi=en0
  • Nick
    Nick over 11 years
    @sghael Thanks for the note. I've updated the rules to mention it. (Also note the changes to the AirDrop rules, which I've had the chance to test more extensively since I first posted here.)
  • Daniel_Bogorad
    Daniel_Bogorad over 10 years
    I'm using this as part of the Icefloor custom ruleset and it seems to work pretty well. Have you experimented with DNSCrypt at all?
  • Nick
    Nick over 10 years
    @WillSargent I haven't used DNSCrypt, but as long as you're doing lookups with UDP and not TCP (DNSCrypt supports both), it should work okay with the rules above.
  • baloo
    baloo over 6 years
    also read the answer on discussions.apple.com/thread/7604510 before mucking around with system files