# IPFW killswitch for VPN



## carloshmm (Jul 22, 2018)

Hello, I'm currently trying to accomplish what is described here: https://bbs.archlinux.org/viewtopic.php?id=224655 on FreeBSD using IPFW.

These rules from iptables are restricting connections through tun0 interface only, with exception that it is allowing output connections to 209.222.18.222/32 and 209.222.18.218/32 (PIA DNS) and UDP connections on port 1198 (the port VPN .conf is using):

```
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:10]
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -i tun+ -j ACCEPT
-A OUTPUT -o lo -j ACCEPT
-A OUTPUT -d 209.222.18.222/32 -j ACCEPT
-A OUTPUT -d 209.222.18.218/32 -j ACCEPT
-A OUTPUT -p udp -m udp --dport 1198 -j ACCEPT
-A OUTPUT -o tun+ -j ACCEPT
-A OUTPUT -j REJECT --reject-with icmp-net-unreachable
COMMIT
```

My Brazil.conf file:

```
client
dev tun
proto udp
remote brazil.privateinternetaccess.com 1198
resolv-retry infinite
nobind
persist-key
persist-tun
cipher aes-128-cbc
auth sha1
tls-client
remote-cert-tls server

auth-user-pass /usr/local/etc/openvpn/pass.txt
auth-nocache
comp-lzo
verb 1
reneg-sec 0
crl-verify /usr/local/etc/openvpn/crl.rsa.2048.pem
ca /usr/local/etc/openvpn/ca.rsa.2048.crt
disable-occ
```

My resolv.conf file:

```
# Generated by resolvconf
nameserver 209.222.18.222
nameserver 209.222.18.218
```

I added the immutable status on it: 
	
	



```
sudo chflags schg /etc/resolv.conf
```

These are the IPFW rules (/etc/ipfw.rules) I constructed trying to mimic the behavior from the iptables rules above:

```
#!/bin/bash
# Flush out the list before we begin
ipfw -q -f flush

# Set rules command prefix
cmd="ipfw -q add"
vpn="tun0"
default="re0"

# allow all local traffic on the loopback interface
$cmd 00001 allow all from any to any via lo0

# allow any connection to/from VPN interface
$cmd 00010 allow all from any to any via $vpn

# allow connection to/from LAN
$cmd 00101 allow all from me to 192.168.0.1/24
$cmd 00102 allow all from 192.168.0.1/24 to me

# allow connection to PIA VPN servers and PIA VPN DNS
$cmd 00112 allow all from any to 209.222.18.222/32 out via $default
$cmd 00113 allow all from any to 209.222.18.218/32 out via $default
$cmd 00114 allow udp from any to any 1198 out via $default
```

This is how my firewall rules are when enabled:

```
00001 allow ip from any to any via lo0
00010 allow ip from any to any via tun0
00101 allow ip from me to 192.168.0.0/24
00102 allow ip from 192.168.0.0/24 to me
00112 allow ip from any to 209.222.18.222 out via re0
00113 allow ip from any to 209.222.18.218 out via re0
00114 allow udp from any to any 1198 out via re0
65535 deny ip from any to any
```

re0 is my default ethernet device, tun0 is the virtual device created by openvpn.
I added these lines on /etc/rc.conf: 

```
firewall_enable="YES"
firewall_script="/etc/ipfw.rules"
```

When I execute this command without IPFW enabled: 
	
	



```
sudo openvpn --config /usr/local/etc/openvpn/Brazil.conf
```
 I get the connection to the VPN server successfully, everything works as expected.

If I do the same but with IPFW enabled I'm unable to connect to PIA servers when executing the same command, this is the output:

```
Sun Jul 22 15:25:21 2018 OpenVPN 2.4.6 amd64-portbld-freebsd11.1 [SSL (OpenSSL)] [LZO] [LZ4] [MH/RECVDA] [AEAD] built on Jul  3 2018
Sun Jul 22 15:25:21 2018 library versions: OpenSSL 1.0.2o-freebsd  27 Mar 2018, LZO 2.10
Sun Jul 22 15:26:49 2018 RESOLVE: Cannot resolve host address: brazil.privateinternetaccess.com:1198 (hostname nor servname provided, or not known)
```

It seems that my last 3 IPFW rules are not working as expected since my system is unable to resolve PIA VPN hostname (notice that I'm "whitelisting" the DNS addresses from PIA on /etc/resolv.conf), I'm doing something wrong, can someone please help on this? I can give you more details about my system if you needed.


----------



## ShelLuser (Jul 22, 2018)

First of all: don't try to treat IPFW as an IPTABLES replacement because you're going to run into problems. Just set up a new set of rules based on how IPFW works.

See, for example: you set up rules for _outgoing_ data to connect to your VPN server, but what about the returning data? At the very least you should enable statefull filtering for that.


----------



## carloshmm (Jul 22, 2018)

I'm really a novice on this topic, most of the time I'm only using the most basic features of the system firewall reading through guides on forums. I just started using FreeBSD (came from Arch Linux) and I'm really enjoying. I started reading the handbook and I'm already on chapter 6, I will try to read it until I get some understanding about FreeBSD and this specific topic.

I tried to modify the rules removing the "out" keyword from the last 3 rules but it still hangs with the same problem, I'm sure I'm missing something very important here.


----------



## rigoletto@ (Jul 22, 2018)

How do you connect/start to the VPN? Depening on how its work you could create a 'watchdog-like' script using a `sh` loop instead of messing with IPFW ( just don't ask me to help with scripting because all my scripts are sh!t  ).


----------



## PMc (Jul 22, 2018)

The problem appears quite obvious: openvpn cannot resolve the hostname it is supposed to connect to.
From your fw rules, it appears you allow traffic out to the DNS servers, but where do you allow the answers that come back from these?

General strategy: for testing, put a "log" statement into any deny/drop/unreach rules of ipfw. Then check the logfile /var/log/security - there should be a record for every dropped packet, stating the exact addresses/ports and which rule did drop it. Via the timestamps you then can see wich packets got dropped leading to an error/failure.

In Your case, one might try to put the IP address of that brazil.privateinternetaccess.com host into the VPN config instead of the hostname, so that openvpn does not need to use DNS (if that is possible, i.e. constant IP address).



carloshmm said:


> I tried to modify the rules removing the "out" keyword from the last 3 rules but it still hangs with the same problem, I'm sure I'm missing something very important here.



Thats not enough. Did You also reverse the statement?
`00112 allow ip from any to 209.222.18.222 out via re0
00113 allow ip from any to 209.222.18.218 out via re0`
That should cover the outgoing DNS stuff (although I would put that more specific to only allow udp port 53). But then the incoming stuff should read someway like:
`00114 allow ip from 209.222.18.222 to me in via re0
00115 allow ip from 209.222.18.218 to me in via re0`

In any case, log the activity and check what packets get actually dropped, and then refine the rules based on that.


----------



## ShelLuser (Jul 23, 2018)

PMc said:


> Thats not enough. Did You also reverse the statement?
> `00112 allow ip from any to 209.222.18.222 out via re0
> 00113 allow ip from any to 209.222.18.218 out via re0`
> That should cover the outgoing DNS stuff (although I would put that more specific to only allow udp port 53). But then the incoming stuff should read someway like:
> ...


That is *seriously* bad advice.

What you're basically suggesting is that the OP opens up his entire machine for that remote IP, which is preposterous. You're only interested in getting the returning data back, you don't want to give it access to all of your other services as well, which is what these rules would do.

The answer to this problem, as I hinted at in my previous message, is to apply statefull filtering. Something clearly explained in Chapter 30.4 of the FreeBSD handbook.



carloshmm said:


> I started reading the handbook and I'm already on chapter 6, I will try to read it until I get some understanding about FreeBSD and this specific topic.


Why read it in sequence? Just pick out the chapters which cover a topic you want to read up on. In this case you'd really want chapter 30 which explains how the firewall(s) within FreeBSD work.



carloshmm said:


> I tried to modify the rules removing the "out" keyword from the last 3 rules but it still hangs with the same problem, I'm sure I'm missing something very important here.


Statefull filtering.

Firewalls these days can track what packets originated from the host and which returning data is then associated with that. This allows you to keep your firewall locked tight while returning data which was requested by you can still find its way past your firewall. But it will also keep your firewall locked tight; remote hosts can't "just" send you any data or connect to any of your services.

In this situation you need to enable statefull filtering. So, solely basing myself on your VPN rules you could try something as:


```
$cmd 00112 allow all from any to 209.222.18.222/32 out via $default keep-state
```
This would instruct the firewall to allow outgoing data to 209.222.18.222, and then keep track of any associated returning data which would then be allowed back in.

For more information on this see Chapter 30 of the handbook.


----------



## PMc (Jul 23, 2018)

ShelLuser said:


> That is *seriously* bad advice.
> What you're basically suggesting is that the OP opens up his entire machine for that remote IP



Good point.
The thing to understand is that a plain rule does only allow flow in one direction, and for functionality you need the other direction as well. 
The next step is then how to protect the scheme properly, either by wholly getting rid of the DNS requests, or by using stateful rules.


----------



## VladiBG (Jul 23, 2018)

For keep-state you need check-state rule
keep-state will create dynamic ipfw rule to allow returning traffic for a limited lifetime (controller by sysctrl variable). So using it for the DNS will be good as the returning UDP packet should come back in less than the maximum lifetime which is 10 sec. But you can't use it for the VPN connection to the server because when there's no UDP packets trasmited from you to the VPN server in the past 10 sec the IPFW will drop vpn.

net.inet.ip.fw.dyn_short_lifetime: 5
net.inet.ip.fw.dyn_udp_lifetime: 10
net.inet.ip.fw.dyn_rst_lifetime: 1
net.inet.ip.fw.dyn_fin_lifetime: 1
net.inet.ip.fw.dyn_syn_lifetime: 20
net.inet.ip.fw.dyn_ack_lifetime: 300


So in your case you will need to use something like this

# Allow packets for which a state has been build
$cmd 5 check-state

# For services permitted below
$cmd 6 allow tcp from me to any established

# Allow any connection out, adding state for each
$cmd 20 allow tcp from me to any setup keep-state
$cmd 30 allow udp from me to any keep-state
$cmd 40 allow icmp from me to any keep-state
$cmd 50 allow ipv6-icmp from me to any keep-state


# this will allow outbound udp traffic from me to vpn_server on udp port 1198
$cmd 110 allow udp from me to $vpn_server 1198

# this will allow all inbound udp traffic from the vpn_server to me
$cmd 120 allow udp from $vpn_server to me


----------

