VPN Killswitch

I want to setup a killswitch for my debian based linux machine.
I want it to start up automatically and only allow traffic through my tunnel interface.
I tried UFW but cannot get it to work properly.
Also I need to be able to forward ports. I can already do that part just need to be able to do it through the firewall or rules.

Comments

  • Hi,

    The Linux client actually uses iptables for the killswitch, so it is very easy to replicate. Since you are asking for this, I'm assuming you are using OpenVPN.

    To avoid interfering with OpenVPN itself, I would recommend first creating a dedicated user to run the VPN, so we can make our iptables rules based on this. This is fairly easy to do:

    useradd pia -r -U -d /etc/openvpn/ -s /bin/false

    (Note: you can use whatever you want instead of "pia", I just used that one to make it clear). Then, add the following to your OpenVPN configuration file to have it run as that user:

    user pia
    group pia

    Finally, with this done, you should be able to create an (actually very simple) rule for it:

    iptables -A OUTPUT ! -m owner --uid-owner pia ! -o tun+ -j DROP

    This reads as "When trying to output something, the owner is NOT pia and the output interface is NOT a tunnel interface, drop the packet". This has the advantage that openvpn isn't restricted in any way, so you don't need to hardcode IPs or make complicated scripts to let it go through. It will naturally do.

    I hope this helps!
  • Hi.
    I would recommend a simpler way and not complicating things:

    iptables -I FORWARD ! -o tun+ -j DROP

  • @PiaVipper: That would only work on a router, and I'm under the impression OP is not using this box as a router. Locally generated packets don't go through the FORWARD chain. It's doable without the owner plugin still, but we'd still need to know either the OpenVPN process' PID, or what is the IP of the VPN server. I vastly prefer the owner solution because once it is setup, it cannot fail because all of the involved variables are static (ie. the OpenVPN user ID).
  • Thank you for clarification.
    Yes, you are correct - I use that firewall command on a (DD-WRT) router.

    Perhaps you can comment further on my "solution" - is it failsafe or should I modify it?

  • @PiaVipper: It's actually safe! Yours is not wrong per-se, it just applies to a different situation. On a router I would have used the same.

    I find this flowchart to be very useful: http://stuffphilwrites.com/2014/09/iptables-processing-flowchart/

    Essentially, in the case of your router, packets will come in on the left, and take the path to the FORWARD chain as they go else where, to the wider internet. It's a very clear path, and that's why it's so simple.

    But in case of OP, the machine that runs the VPN also generates the traffic that should go through the VPN. In that chart, it would arrive on the right as it is internally generated. It doesn't come from another computer on the network. That one doesn't go through the FORWARD chain at all, so you rule wouldn't apply. Now, this situation is a bit more tricky, because you have to both allow the openvpn daemon to access the regular internet, but nothing else. And they all come in the same spot. There are multiple approaches to this:

    * You can block everything except the server IP you connect to -> you have to hardcode the IP because you can't do DNS nor fetch an updated list due to the block
    * You can use network namespaces -> requires changing the init unit/script to either move the real interface to a namespace, or namespace everything
    * You can remove the default gateway and manually add the route (that's how it's done in the app, because Windows) -> again requires to hardcode the IP, but also disrupts the network settings, and you may need to have network access without the VPN to say, update its IP.
    * And of course, you can have the VPN run as a different user, and use that information to make an exception just for the VPN.
    * (And technically, openvpn also has an fwmark option, but then you have to change both your openvpn *and* iptables, and apply this to every profile you use instead of once and forget.)

    Does that answer your question?

  • Max-P said:
    Does that answer your question?

    YES! Thank you very much for your time - very informative and I hope it helps others too.

    Vipper
  • Max-P said:
    iptables -A OUTPUT ! -m owner --uid-owner pia ! -o tun+ -j DROP
    @Max-P: I like this solution, and I'm trying to put it in place, but I'm having trouble with adding this iptables rule.

    I'm running PIA on Arch Linux. What I have done is added the pia user as you described, and then run the command

    sudo openvpn --config /etc/openvpn/CA_Toronto.conf --user pia --group pia

    Then, I try adding iptables rule, but I get an error (unexpected ! flag before --match). What should I try to get this to work? In case it matters, I have a simple firewall set up very similar to the example here. My plan was to just add your iptables rule to the existing rules, and then reload the old rules after the openvpn service has stopped.
  • @jeffmjoy: Hmm, it seems like I might have made a mistake in the iptables rule. The ! needs to be moved between the "-m owner" option. Just tried it on my computer (also running Arch) and it instantly dropped all my traffic so I guess I can say it works :)

    Just a warning with the openvpn command, I don't think it will drop privileges soon enough for the connection to go through as it only gets the tunnel details after the connection is established and still needs root to add the IP and routes. So you will have to make sure OpenVPN can entirely run as that user. My bad for this, I personally used root as my exception user so I didn't run into that particular detail.

    However, looking through the OpenVPN manual, it seems like there a --mark option that lets you add iptables marks:

           --mark value
                  Mark encrypted packets being sent with value. The mark value can
                  be matched in policy routing and packetfilter rules. This option
                  is  only  supported in Linux and does nothing on other operating
                  systems.

    So a potentially better rule could be a simple as iptables -A OUTPUT -m mark ! --mark 0x1 ! -o tun+ -j DROP, with openvpn launched with --mark 1. I just tried and it seems to work quite nicely! It breaks DNS however, so you will have to either put the IP directly in the config, or add an exception for root and DNS to let it go through (iptables -I OUTPUT 1 -m owner --uid-owner root -p udp --dport 53 -j ACCEPT). The DNS resolution is likely done by the libc, so it doesn't get marked properly.


    There is an ideal perfect solution to this, and it is to use network namespaces. However that gets complicated really quickly. I use it to test VPN profiles and it' sure is nice, but you have to essentially move your real network adapter into a namespace, run openvpn in it and move the newly created tun device back to the default namespace for other applications to use. Interfaces also needs to be fully reconfigured when moving namespaces, so you'd have to run dhcpcd/netctl/whatever inside the openvpn namespace to configure your interface but also make a script for openvpn to move the tun out and configure it there. Bulletproof, but complicated.
  • Thanks @Max-P, I'm quite new to using openvpn and iptables, so your input is very helpful! I'm confused about one thing though. What does it mean for DNS to be broken? When I just add the first rule to mark packets, I'm still able to browse websites just fine, and when I end the openvpn session, the rule successfully restricts my Internet connection. Do I need the second rule that explicitly allows DNS to go through?

    Also, what's the simplest way to make sure that no DNS leaks are occurring when my VPN is active? Is it good enough to run the test on dnsleaktest.com?
  • @jeffmjoy: What I meant is just that OpenVPN cannot resolve DNS despite having the --mark option, because that part is done by the libc. So that means OpenVPN cannot get the IP to connect. Once the VPN is connected, it doesn't matter. The second rule I added allows root to use DNS, so that OpenVPN can connect. Once the VPN is connected it doesn't matter, DNS will go through PIA just as much as everything else. it just won't killswitch DNS for root so that OpenVPN can connect fine.

    An alternative is to not use DNS by hardcoding the PIA IP in the VPN profile.

    And yes, running DNS leak tests is sufficient. If it doesn't leak, it doesn't leak so you're good! With my iptables rule, it's practically impossible for DNS to leak because everything that's not specifically marked to be allowed outside of the VPN will be blocked. You shouldn't even be able to use your local network with it. If you apply the DNS fix, only the root user will be allowed to leak DNS, and in theory should only be able to do so while the VPN is disconnected. As soon as it connects, the DNS should be protected by PIA.

    The only exception to the above would be if your DNS server in /etc/resolv.conf is your router. In that case, DNS will not work for regular users at all. You need to put the PIA DNS in there to avoid leaks, if you haven't done so already (209.222.18.222 and 209.222.18.218). You can prevent this file from ever changing with chattr +i /etc/resolv.conf. It's a bit hacky but it works (and I've never been able to get resolvconf to work on Arch for some reason).
  • Hey Max, I think the problem was the exception for tun only (! -o tun), it will block the initial connection to pia server required to tunnel through. You would then need to run any application as that user eg $pia firefox. I know this works with the gid as in: -A OUTPUT -m owner ! --gid-owner nointernet -j DROP. This dropped my standard users firefox session, but if i run $nointernet firefox i'm surfing again.....yes the group name is counter intuitive in the example :-), i added the ! to test. Funnily enough you posted this nice solution in another thread, for others interested the first ip is to allow the pia server which you can find by 'whois' ip search online: -A OUTPUT -j PIA_KILLSWITCH_OUTPUT_RULES -A PIA_KILLSWITCH_OUTPUT_RULES -d 172.98.67.120/32 -j RETURN -A PIA_KILLSWITCH_OUTPUT_RULES -d 127.0.0.0/8 -j RETURN -A PIA_KILLSWITCH_OUTPUT_RULES -o tun+ -j RETURN -A PIA_KILLSWITCH_OUTPUT_RULES -j DROP
  • Thanks again @Max-P! I'm pleased to have this all sorted out, and I learned a few useful things along the way
  • edited December 2016
    Got these firewall rules on a container running openvpn in ubuntu. You can connect to every pia server without the need to specify the ip. It also work with the port_forward script that is posted here somewhere on the forums:

    iptables -A INPUT -i lo -j ACCEPT
    iptables -A INPUT -s 255.255.255.255/32 -j ACCEPT
    iptables -A INPUT -s 192.168.0.0/16 -d 192.168.0.0/16 -j ACCEPT
    iptables -A INPUT -p udp -m udp --dport 53 -j ACCEPT
    iptables -A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT
    iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
    iptables -A INPUT -i tun+ -j ACCEPT
    iptables -A INPUT -j DROP
    iptables -A FORWARD -p udp -m udp --dport 53 -j ACCEPT
    iptables -A FORWARD -i tun+ -j ACCEPT
    iptables -A FORWARD -j DROP
    iptables -A OUTPUT -p udp -m udp --dport 1198 -j ACCEPT
    iptables -A OUTPUT -o lo -j ACCEPT
    iptables -A OUTPUT -d 255.255.255.255/32 -j ACCEPT
    iptables -A OUTPUT -s 192.168.0.0/16 -d 192.168.0.0/16 -j ACCEPT
    iptables -A OUTPUT -p udp -m udp --dport 53 -j ACCEPT
    iptables -A OUTPUT -o tun+ -j ACCEPT
    iptables -A OUTPUT -j DROP 
  • edited August 2017
    I have setup the rule and running openvpn using the pia user but I do not have any local network access.  How do I go about allowing local network access while using this vpn killswitch setup that @Max-P posted?
  • i believe 'killswitch' and 'local network access' are incompatible. the former is "ALL traffic through VPN" and the latter is "ALL traffic EXCEPT local network through VPN".
  • Tried using ufw but it periodically kills access to my nas, then the vpn drops - rules are default deny incoming, default deny outgoing, allow out on tun0 from any to any, allow 1194/udp to let VPN reconnect on failure, allow to 192.168.nas ip, allow from 192.168.nas ip, then just for fun allow to 192.168.***.0/24 just to blanket the whole LAN in allow. It works for a few hours, then stops talking to the share.
  • where's DNS in your ufw design? and broadcasts on your LAN?
  • martouf said:
    where's DNS in your ufw design? and broadcasts on your LAN?
    Shouldn't allowing 192.168.**.0/24 allow it to get to local dns, which is running on the gateway? Also, broadcasts?
  • "also" ? no. which address(es) do you think broadcasts use (DHCP uses this)?  and which ones are used for multicasts (there are DNS implementations which use this)?
  • @martouf sorry I'm not following, I SUPER appreciate you taking the time to respond!

    What do you mean by broadcasts/multicasts? My local DNS is done by the default gateway, would like an ipconfig/all give me multicast/broadcast addresses?
  • 192.168.x.0/24 does not cover all useful 'local' IP addresses. if you want to maximize the "it just works" experience, you need a couple more rules to include "all devices" broadcasts (legacy was 0.0.0.0, wasn't it?) [hint]. quite a few devices will use mDNS when available (it uses a multicast IP address)
  • I gotcha! so in order to continue getting to my local devices + the vpn, I'll have to add some other rules? I've gone over firewall rules other people are using and never noticed anything like that. Trying to stick it out with a Linux VM but if I can't get these things working I might go to the dark side :P
  • oh!? a VM is involved?! why didn't you say.. that can complicate things because even if you tell the VM network to be bridged - that mode has not always been faithfully emulated without some advanced configuration. much better lately but not always 'just works'. what sort/flavor of VM? what network interface is being emulated? what network mode? 
    it's almost but not quite exactly the same as running the OS on bare metal..
  •  :# didn't know it was relevant! Whoops. Running -
    Ubuntu 16.0.4 in VirtualBox on a Win10 host
    Emulating: Intel Pro/1000 MT desktop NIC
    Mode: Bridged
    Promiscuous: Deny
  • the assumption is generally that you're running on bare metal. because that's the case for the vast majority.
    the better choice for network interface is virtio-net (the paravirtualized one) try that first. if that didn't do it, the next thing is to allow promiscuous mode. no mode change - you're good there already.
  • Good deal, trying virtio-net, I'll update with results!
  • edited April 2018
    martouf said:
    the assumption is generally that you're running on bare metal. because that's the case for the vast majority.
    the better choice for network interface is virtio-net (the paravirtualized one) try that first. if that didn't do it, the next thing is to allow promiscuous mode. no mode change - you're good there already.
    Promiscuous mode is on now, it killed access to the share with just the virtio-net. Also to note, the host is going to sleep and resuming if there's no load on it - could this be causing problems as well? I haven't been able to MOUNT the share with UFW enabled, but I can mount it with UFW disabled and it stays mounted until it gets killed.
  • the paravirtualized network interface offers the best throughput lowest host CPU load for your VM.

    you can count on everything about the network interface and programs using it to break when the host goes to sleep if there's no signalling or no cooperation on behalf of the VM. from the VM's point of view: you pulled the plug out (unless you have a way to signal the VM to go to sleep before the host does).

    why are we still discovering the implementation and configuration of your collection of stuff?
  • It will now no longer go to sleep. Probably should've done that a while ago but the whole idea was power savings - nuc is pretty low power but when it's sleeping it barely uses anything.

    ...you're not wrong :D hopefully I'll serve as a cautionary tale at least. Will update with how this works after a little bit. Now running promiscuous all, (not just promiscuous vms) + paravirtualized interface + not going to sleep + the ufw rules listed above already
  • edited April 2018
    Been a day, still up 100%. I'll update if it goes down, but I think @martouf got it! 

    EDIT: Gonna call it, it's been 3 days and it's rock solid. Thanks for being great.
Sign In or Register to comment.