Linux firewall Howto

We often get questions from our users regarding more advanced uses of iptables when using a VPN. Since direct use of iptables can be tedious we decided to describe how ferm can be used to configure a Ubuntu 13.10 for a common use case. This Howto covers the basic format of the ferm and iptables chains configuration and describes how to debug common firewall issues using tcpdump and iptables.

With ferm you define firewall rules in a single configuration file in a well-readable format. You then invoke ferm to activate your ruleset. ferm cares for inserting and removing rules seamlessly with iptables. You do not need to deal with iptables directly which makes managing complex Linux firewalls way more comfortable and fail-safe on the command line.

As the use case we chose a road warrior setup where you connect to untrustworthy networks. As long as no VPN connection is active, ferm prevents the host from connecting to the internet other than for establishing a VPN connection. Once ferm detects the presence of a VPN interface it loads further rules to permit controlled Internet access through the VPN connection. udev was used for the VPN interface detection magic.

Finally all dropped packets are logged into a PCAP file that can easily be read with tcpdump on the shell or with Wireshark on the GUI. Using tcpdump to debug firewall issues is a lot nicer than the default ulogd2 output via syslog.

For this Howto it does not matter if you configured the OpenVPN connection in the Gnome Network Manager or if you are using the command-line client on Debian GNU/Linux. In both cases udev detects when a tunnel interface is created or removed.

Table of contents

A word of warning
Requirements
Installation
Firewall rules
Set up ferm rules
Hooks
Variables
Conditionals
Domains
Filter tables and chains
The IPv4 INPUT chain
The IPv4 OUTPUT chain
VPN specific outbound rules
IPv6
Interactive test run
Logging dropped packages
System start
udev rules and fermreload.sh
Seeing it all in action

A word of warning  

It is very easy to break your network connection when configuring firewall rules, so be careful. If in doubt run ferm -i for interactive mode.

Requirements  

The reference system for this Howto is running Ubuntu Linux 13.10, but this Howto also works on Debian GNU/Linux. ferm runs on any Linux system compiled with iptables support and Perl.

Needed files

Installation  

Execute the following command to install ferm and ulogd2 on Ubuntu Linux:

$ sudo apt-get update && sudo apt-get install ferm ulogd2 ulogd2-pcap

The package configuration script will ask if ferm should be started on every boot. Select NO there and do NOT enable ferm on boot yet.

 

If ulogd2 is not available on the Linux distribution you are using, you can also install ulogd.

Firewall rules  

Firewall rules basically allow or deny communication to or from a network. The rules controlling traffic that comes in on an interface are called inbound rules. For the opposite direction, these rules are called outbound rules.

Depending on the type of firewall, a rule defines parameters that specify the kind of traffic they are being triggered on, e.g. a single IP or IP ranges, TCP or UDP ports, or the process, owner or group ID they should be bound to.

Set up ferm rules  

The ferm configuration file is located at /etc/ferm/ferm.conf. Copy and paste the following content into the ferm system config and read through the explanations.

# -*- shell-script -*-
#
#  Configuration file for ferm(1).
#
#  V: 0.1
#
#  ferm manual: http://ferm.foo-projects.org/download/2.2/ferm.html
#  Blog post:   https://blog.ipredator.se/linux-firewall-howto.html
#

# Really make sure that these modules exist and are loaded.
@hook pre "/sbin/modprobe nf_conntrack_ftp";
@hook pre "/sbin/modprobe nfnetlink_log";

# Network interfaces.
@def $DEV_LAN = eth0;
@def $DEV_LOOPBACK = lo0;
@def $DEV_VPN = tun0;

# Network definition for the loopback device. This is needed to allow
# DNS resolution on Ubuntu Linux where the local resolver is bound
# to 127.0.1.1 - as opposed to the default 127.0.0.1.
@def $NET_LOOPBACK = 127.0.0.0/8;

# Common application ports.
@def $PORT_DNS = 53;
@def $PORT_FTP = ( 20 21 );
@def $PORT_NTP = 123;
@def $PORT_SSH = 22;
@def $PORT_WEB = ( 80 443 );

# The ports we allow OpenVPN to connect to. IPredator allows you
# to connect on _any_ port. Simply add more ports if desired but
# stick to only those that you really need.
@def $PORT_OPENVPN = (1194 1234 1337 2342 5060);

# Public DNS servers and those that are only reachable via VPN.
# DNS servers are specified in the outbound DNS rules to prevent DNS leaks
# (https://www.dnsleaktest.com/). The public DNS servers configured on your
# system should be the IPredator ones (https://www.ipredator.se/page/services#service_dns),
# but you need to verify this.
#
@def $IP_DNS_IPR_PUBLIC = (194.132.32.32/32 46.246.46.246/32);

# Add your ISP name server to this object if you want to restrict 
# which DNS servers can be queried.
@def $IP_DNS_PUBLIC = 0.0.0.0/0;

# DNS server available within the VPN.
@def $IP_DNS_VPN = ( 46.246.46.46/32 194.132.32.23/32 );

# Make sure to use the proper VPN interface (e.g. tun0 in this case).
# Note: You cannot reference $DEV_VPN here, substition does not take
#       place for commands passed to a sub shell.
@def $VPN_ACTIVE = `ip link show tun0 >/dev/null 2>/dev/null && echo 1 || echo`;

# VPN interface conditional. If true the following rules are loaded.
@if $VPN_ACTIVE {
    domain ip {
        table filter {
            chain OUTPUT {
                # Default allowed outbound services on the VPN interface.
                # If you need more simply add your rules here.
                outerface $DEV_VPN {
                    proto (tcp udp) daddr ( $IP_DNS_VPN $IP_DNS_IPR_PUBLIC ) dport $PORT_DNS ACCEPT;
                    proto tcp dport $PORT_FTP ACCEPT;
                    proto udp dport $PORT_NTP ACCEPT;
                    proto tcp dport $PORT_SSH ACCEPT;
                    proto tcp dport $PORT_WEB ACCEPT;
                }
            }
        }
    }
}

# The main IPv4 rule set.
domain ip {
    table filter {
        chain INPUT {
            # The default policy for the chain. Usually ACCEPT or DROP or REJECT.
            policy DROP;

            # Connection tracking.
            mod state state INVALID DROP;
            mod state state (ESTABLISHED RELATED) ACCEPT;

            # Allow local traffic to loopback interface.
            daddr $NET_LOOPBACK ACCEPT;
 
            # Allow inbound SSH on your LAN interface _only_.
            interface $DEV_LAN {
                proto tcp dport $PORT_SSH ACCEPT;
            }

            # Respond to ping ... makes debugging easier.
            proto icmp icmp-type echo-request ACCEPT;

            # Log dropped packets.
            NFLOG nflog-group 1;
            DROP;
        }

        chain OUTPUT {
            policy DROP;

            # Connection tracking.
            mod state state INVALID DROP;
            mod state state (ESTABLISHED RELATED) ACCEPT;

            # Allow local traffic from the loopback interface.
            saddr $NET_LOOPBACK ACCEPT;
  
            # Respond to ping.
            proto icmp icmp-type echo-request ACCEPT;

            # Allowed services on the LAN interface.
            outerface $DEV_LAN {
                proto (tcp udp) daddr $IP_DNS_PUBLIC dport $PORT_DNS ACCEPT;
                proto udp dport $PORT_NTP ACCEPT;
                proto (tcp udp) dport $PORT_OPENVPN ACCEPT;
                proto tcp dport $PORT_SSH ACCEPT;
            }

            # Log dropped packets.
            NFLOG nflog-group 1;
            DROP;
        }

        chain FORWARD {
            policy DROP;

            # If you use your machine to route traffic eg. 
            # from a VM you have to add rules here!

            # Log dropped packets.
            NFLOG nflog-group 1;
            DROP;
        }
    }
}

# IPv6 is generally disabled, communication on the loopback device is allowed.
domain ip6 {
    table filter {
        chain INPUT {
            policy DROP;

            # Allow local traffic.
            interface $DEV_LOOPBACK ACCEPT;

            # Log dropped packets.
            NFLOG nflog-group 1;
            DROP;
        }
        chain OUTPUT {
            policy DROP;

            # Log dropped packets.
            NFLOG nflog-group 1;
            DROP;
        }
        chain FORWARD {
            policy DROP;

            # Log dropped packets.
            NFLOG nflog-group 1;
            DROP;
        }
    }
}

Hooks  

Starting at the top of the file, ignoring the comments, we start to define two hooks. Hooks are used to react on events while ferm processes the ruleset. Here the connection tracking kernel module for FTP gets loaded before the rules are processed. Other hooks are post (invoked after the rule set is loaded) and flush (invoked after the tables got flushed).

@hook pre "/sbin/modprobe nf_conntrack_ftp";
@hook pre "/sbin/modprobe nfnetlink_log";

Connection tracking gets activated by opening an outbound connection to the well-known FTP ports. All affiliated traffic is allowed to pass back in. This is needed because FTP negotiates ports inside its protocol so the firewall has no idea which ports are used by FTP.

Further more the iptables logging module is loaded which enables the NFLOG statement. It is used to pass packets to the ulogd2 logging daemon.

Variables  

To facilitate easy changes to the firewall script, variables can be defined which are used to reference network devices. For a basic IPredator host firewall, you should define three variables, e.g. $DEV_LAN, $DEV_LOOPBACK and $DEV_VPN.

# Network interfaces
@def $DEV_LAN = eth0;
@def $DEV_LOOPBACK = lo;
@def $DEV_VPN = tun0;

The same is true for port numbers. Although port numbers are also defined in /etc/services and can be referenced by their names, this script does not rely on the completeness of this file. If you are configuring custom applications or special port ranges, you need to define such variables anyways.

# Common application ports.
@def $PORT_DNS = 53;
@def $PORT_FTP = ( 20 21 );
@def $PORT_NTP = 123;
@def $PORT_SSH = 22;
@def $PORT_WEB = ( 80 443 );

# The ports we allow OpenVPN to connect to. IPredator allows you
# to connect on _any_ port. Simply add more ports if desired and only
# those that you really need.
@def $PORT_OPENVPN = (80 443 1194 1234 1337 2342 5060);

Conditionals  

Conditionals can be used to detect certain system or interface states and inject firewall rules as needed. In this example the existence of the tun0 VPN interface is checked. If it exists related rules are loaded.

@def $VPN_ACTIVE = `ip link show tun0 >/dev/null 2>/dev/null && echo 1 || echo`;

@if $VPN_ACTIVE {
    ...
}

$VPN_ACTIVE can either be 0 or 1. If the VPN is active, its value is 1, if it is inactive, the value is 0. The check is executed as a sub shell, thus the test command is put into backticks. ip link show tun0 outputs the status of the tun0 interface if available. >/dev/null 2>/dev/null redirects all regular output and errors to /dev/null which effectively suppresses any output during the command execution.

Per definition all UNIX commands return 0 when exiting cleanly, every other return value is considered an error number. This is exactly opposite to what this test should return, because the if conditional considers results to be true if they are 1. The result needs to be inverted.

The command after the logical and operator && only gets executed if the previous command exited cleanly with 0. The value 1 is echoed in that case, otherwise an empty string. || is the logical or operator.

Translated into spoken language: See if the VPN device is available, throw away all text output and return 1 if the command succeeds or return nothing if it fails.

Domains  

Communication is generally categorized into IPv4 or IPv6 when processed with iptables. Respectively, there are two domains defined in the configuration file:

domain ip {
    # IPv4 filter and NAT tables
}

domain ip6 {
    # IPv6 filter tables
}

Filter tables and chains  

Filter tables consist of three basic rule chains.

INPUTRules for processing packets reaching your machine from the outside
OUTPUTRules for processing packets leaving your machine
FORWARDRules to forward packets, e.g. on a router doing NAT

Within each chain you define a default action taken if no other rule matches. This default action is called a policy. A policy can either drop, reject or accept packets. The difference between dropping and rejecting is that dropping discards packets silently and rejecting sends a RST TCP packet or port unreachable message to the source of the incoming packet.

The IPv4 INPUT chain  

The default policy should be to drop all incoming communication. Only open ports you really need. ICMP echo requests should always be permitted to be able to diagnose your network and communication on the loopback interface is always allowed. These rules are defined by using match keywords.

Rules defined inside an iptables chain that to not apply to a certain interface apply to all packets on all interfaces processed by this chain (e.g. the ICMP rule).

chain INPUT {
    # The default policy for the chain. Usually ACCEPT or DROP or REJECT.
    policy DROP;

    # Connection tracking
    mod state state INVALID DROP;
    mod state state (ESTABLISHED RELATED) ACCEPT;

    # Allow local traffic to loopback interface
    daddr $NET_LOOPBACK ACCEPT;
 
    # Allow inbound SSH on your LAN interface _only_.
    interface $DEV_LAN {
        proto tcp dport $PORT_SSH ACCEPT;
    }

    # Respond to ping ... makes debugging easier.
    proto icmp icmp-type echo-request ACCEPT;

    # Log dropped packets.
    NFLOG nflog-group 1;
    DROP;
}

The IPv4 OUTPUT chain  

Again, the default policy is to drop all communication. This results in fine-grained control over the communication possible on each interface.

Similar to the INPUT chain ICMP echo-requests and communication on the loopback device are allowed.

Note the use of the outerface parameter. On the the LAN device, only DNS and NTP queries as well as OpenVPN connections are permitted. For cryptographic applications it is important that your system clock is properly synchronized - this is why you should also allow NTP without the VPN being active. FTP, SSH and Web communication only works via the VPN interface.

chain OUTPUT {
    policy DROP;

    # Connection tracking
    mod state state INVALID DROP;
    mod state state (ESTABLISHED RELATED) ACCEPT;

    # Allow local traffic from the loopback interface.
    saddr $NET_LOOPBACK ACCEPT;
  
    # Respond to ping
    proto icmp icmp-type echo-request ACCEPT;

    # Allowed services on the LAN interface.
    outerface $DEV_LAN {
        proto (tcp udp) daddr $IP_DNS_PUBLIC dport $PORT_DNS ACCEPT;
        proto udp dport $PORT_NTP ACCEPT;
        proto (tcp udp) dport $PORT_OPENVPN ACCEPT;
        proto tcp dport $PORT_SSH ACCEPT;
    }

    # Log dropped packets
    NFLOG nflog-group 1;
    DROP;
}

VPN specific outbound rules  

Scroll up to the conditional that determines if the VPN is active or not. As mentioned above, this conditional opens outbound ports for DNS, FTP, NTP, SSH and web access.

@if $VPN_ACTIVE {
    domain ip {
        table filter {
            chain OUTPUT {
                # Default allowed outbound services on the VPN interface.
                # If you need more simply add your rules here.
                outerface $DEV_VPN {
                    proto (tcp udp) daddr ( $IP_DNS_VPN $IP_DNS_IPR_PUBLIC ) dport $PORT_DNS ACCEPT;
                    proto tcp dport $PORT_FTP ACCEPT;
                    proto udp dport $PORT_NTP ACCEPT;
                    proto tcp dport $PORT_SSH ACCEPT;
                    proto tcp dport $PORT_WEB ACCEPT;
                }
            }
        }
    }
}

IPv6  

ferm also supports IPv6. Rules are configured inside the domain ipv6 statement. There is no other difference than the notation format of IPv6 addressed in comparison to IPv4 addresses. As long as you do not have IPv6 connectivity and only need the IPv6 loopback for compatibility reasons, the IPv6 part should look as simple as this:

domain ip6 {
    table filter {
        chain INPUT {
            policy DROP;

            # allow local packet
            interface lo ACCEPT;

            # log dropped packets
            NFLOG nflog-group 1;
            DROP;
        }
        chain OUTPUT {
            policy DROP;

            # log dropped packets
           NFLOG nflog-group 1;
           DROP;
        }
        chain FORWARD {
            policy DROP;

            # log dropped packets
           NFLOG nflog-group 1;
           DROP;
        }
    }
}

Interactive test run  

After having edited your rule set, you need to load the new firewall rules. ferm is intelligent and inserts changes to the running firewall ruleset in the order of their occurrence in the configuration file. This is one of the main differences in comparison to editing iptables firewall rules directly. You do not need to take care of proper rule insertion.

$ sudo ferm --interactive -l /etc/ferm/ferm.conf

Note the use of the --interactive (or the shorthand -i) command line parameter. This is your safety net when it comes to editing firewall rules on a remote Linux machine. When loading rules interactively you are asked if you want to use the current ruleset. If you do not answer fast enough, the old ruleset is restored.

Imagine you are connected to a remote machine via SSH. If you load a ruleset that kills your SSH session at the same time due to misconfiguration, your keyboard inputs will not reach the remote machine anymore and you are locked out. ferm will gracefully restore the old ruleset which in turn gives you back SSH access.

Check for the ruleset being loaded correctly by invoking sudo iptables -nL -v:

$ sudo iptables -nL -v
Chain INPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target  prot opt in    out    source       destination         
    0     0 DROP    all  --  *     *      0.0.0.0/0    0.0.0.0/0     state INVALID
   37  2725 ACCEPT  all  --  *     *      0.0.0.0/0    0.0.0.0/0     state RELATED,ESTABLISHED
    8   548 ACCEPT  all  --  *     *      0.0.0.0/0    127.0.0.0/8         
    0     0 ACCEPT  tcp  --  eth0  *      0.0.0.0/0    0.0.0.0/0     tcp dpt:22
    0     0 ACCEPT  icmp --  *     *      0.0.0.0/0    0.0.0.0/0     icmptype 8
    0     0 NFLOG   all  --  *     *      0.0.0.0/0    0.0.0.0/0     nflog-group 1
    0     0 DROP    all  --  *     *      0.0.0.0/0    0.0.0.0/0           

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target  prot opt in    out    source       destination         
    0     0 NFLOG   all  --  *     *      0.0.0.0/0    0.0.0.0/0     nflog-group 1
    0     0 DROP    all  --  *     *      0.0.0.0/0    0.0.0.0/0           

Chain OUTPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target  prot opt in    out    source       destination         
    0     0 DROP    all  --  *     *      0.0.0.0/0    0.0.0.0/0     state INVALID
   30  3033 ACCEPT  all  --  *     *      0.0.0.0/0    0.0.0.0/0     state RELATED,ESTABLISHED
    8   548 ACCEPT  all  --  *     *      127.0.0.0/8  0.0.0.0/0          
    0     0 ACCEPT  icmp --  *     *      0.0.0.0/0    0.0.0.0/0     icmptype 8
    0     0 ACCEPT  tcp  --  *     eth0   0.0.0.0/0    0.0.0.0       tcp dpt:53
    0     0 ACCEPT  udp  --  *     eth0   0.0.0.0/0    0.0.0.0       udp dpt:53
    0     0 ACCEPT  udp  --  *     eth0   0.0.0.0/0    0.0.0.0/0     udp dpt:123
    0     0 ACCEPT  udp  --  *     eth0   0.0.0.0/0    0.0.0.0/0     udp dpt:1194
    0     0 ACCEPT  udp  --  *     eth0   0.0.0.0/0    0.0.0.0/0     udp dpt:1234
    0     0 ACCEPT  udp  --  *     eth0   0.0.0.0/0    0.0.0.0/0     udp dpt:1337
    0     0 ACCEPT  udp  --  *     eth0   0.0.0.0/0    0.0.0.0/0     udp dpt:2342
    0     0 ACCEPT  udp  --  *     eth0   0.0.0.0/0    0.0.0.0/0     udp dpt:5060
    0     0 ACCEPT  tcp  --  *     eth0   0.0.0.0/0    0.0.0.0/0     tcp dpt:22
   19  2485 NFLOG   all  --  *     *      0.0.0.0/0    0.0.0.0/0     nflog-group 1
   19  2485 DROP    all  --  *     *      0.0.0.0/0    0.0.0.0/0   

The rules for $DEV_LAN (eth0) and the loopback are loaded into the INPUT and the OUTPUT chains. The FORWARD chain is empty.

Logging dropped packages  

All dropped packets should be written to a .pcap file. Such a file can be read with tcpdump or with Wireshark if you prefer output in a GUI.

The previosly installed ulogd2 daemon is responsible for writing all packets from the nflog-group 1 into /var/log/ulog/ulogd.pcap. With the described firewall configuration this relates to all dropped packets from the INPUT, FORWARD and OUTPUT chains.

There are three changes you need to do to /etc/ulogd.conf:

  1. Enable the PCAP output plugin
  2. Enable the PCAP stack
  3. Change the default output file for syslog emulation to /dev/null

The third step disables the output to the syslog emulation file. It is not recommended to log to textual syslog files. PCAP logging extracts the real packet from the kernel and writes it in a standardized file format (PCAP). If you ever need to look into malicious packets, it is better to diagnose real packets instead of working with textual descriptions only.

The sections that need to be changed in /etc/ulogd.conf are outlined here:

 

In case you installed ulogd because ulogd2 is not available on your Linux distribution, please refer to the installation section in the blog article Configuring Debian/GNU Linux as an OpenVPN router where the configuration of ulogd is outlined.

[global]

..

plugin="/usr/lib/x86_64-linux-gnu/ulogd/ulogd_output_PCAP.so"

..

# This is a stack for NFLOG packet-based logging to PCAP
stack=log2:NFLOG,base1:BASE,pcap1:PCAP

..

[emu1]
# Only enable syslog output if your mental parser is touring complete.
#file="/var/log/ulog/syslogemu.log"
file="/dev/null"
sync=1

Restart ulogd.

$ sudo /etc/init.d/ulogd2 restart
 

When using ulogd, the above command simply translates to $ sudo /etc/init.d/ulogd restart

PCAP output is written to /var/log/ulog/ulogd.pcap. To watch dropped packets, execute tail -F /var/log/ulog/ulogd.pcap | tcpdump -nr -. To stop the logging output hit CTRL-C.

$ tail -F /var/log/ulog/ulogd.pcap | tcpdump -nr -
reading from file -, link-type RAW (Raw IP)
03:24:44.931870 IP 46.246.43.48.52667 > 224.0.0.1.8612: UDP, length 16
03:24:44.931900 IP 46.246.43.42.54283 > 224.0.0.1.8612: UDP, length 16
03:25:45.379883 IP 218.2.22.118.6000 > 46.246.43.70.22: Flags [S], seq 2071855104, win 16384, length 0
03:25:48.683834 IP 46.246.43.48.61024 > 224.0.0.1.8612: UDP, length 16
03:25:48.683868 IP 46.246.43.42.63221 > 224.0.0.1.8612: UDP, length 16
^C
$

Do not attempt to open /var/log/ulog/ulogd.pcap directly with Wireshark. Wireshark does not work well with files that grow bigger while they are opened. Always create a copy of /var/log/ulog/ulogd.pcap in your home directory and open the copied file with Wireshark to analyze it.

System start-up  

After having made sure that your ruleset works as expected you need to enable the ferm init script. Edit /etc/default/ferm and set

ENABLED=yes

So far only half of the work is done. When enabling the ferm init script during system start-up, it is only possible to start a VPN connection. Accessing the Internet like browsing websites will not work. The next step is to automate the firewall rule update when the VPN interface is added or removed to your system, e.g. OpenVPN is started or stopped.

Adding udev rules and fermreload.sh  

udev observes the device creation and destruction on your system. It controls many actions that are already in place in your system, e.g. mounting an USB stick when it is attached.

Create the file /etc/udev/rules.d/81-vpn-firewall.rules with the following contents:

KERNEL=="tun0", ACTION=="add", RUN+="/usr/local/bin/fermreload.sh add"
KERNEL=="tun0", ACTION=="remove", RUN+="/usr/local/bin/fermreload.sh remove"

This defines two rules that apply when the kernel interface tun0 is added or removed. In both cases /usr/local/bin/fermreload.sh gets invoked. A parameter passing the action to the script is appended.

Next create /usr/local/bin/fermreload.sh:

#!/bin/bash
#
# fermreload.sh
# V: 0.1
#
# Reloads the ferm firewall ruleset and is invoked by
# the udev via /etc/udev/rules.d/81-vpn-firewall.rules.
#
# IPredator 2014
# Released under the Kopimi license.
#
# Blog post:   https://blog.ipredator.se/linux-firewall-howto.html
#

LOGGER=/usr/bin/logger
LOGGER_TAG=$0

UDEV_ACTION=$1

FERM=/usr/sbin/ferm
FERM_CONF=/etc/ferm/ferm.conf

MSG_FW_RULE_ADD="Adding VPN firewall rules."
MSG_FW_RULE_REMOVE="Removing VPN firewall rules."
MSG_UDEV_ACTION_UNKNOWN="Unknown udev action."

case "$UDEV_ACTION" in
    add)
        $LOGGER -t $LOGGER_TAG $MSG_FW_RULE_ADD
        $FERM $FERM_CONF
        ;;
    remove)
        $LOGGER -t $LOGGER_TAG $MSG_FW_RULE_REMOVE
        $FERM $FERM_CONF
        ;;
    *)
        $LOGGER -t $LOGGER_TAG $MSG_UDEV_ACTION_UNKNOWN
        exit 1
esac

This script logs the current udev action and actually reloads the firewall. You need to make it executable and reload the udev deamon:

$ sudo chmod 555 /usr/local/bin/fermreload.sh
$ sudo /etc/init.d/udev reload

Seeing it all in action  

Now open a new terminal and use the tail command to view the system log:

$ sudo tail -F /var/log/syslog

Start the OpenVPN connection from the Gnome menu as you are used to. Observe the log output. Besides the OpenVPN log messages, you will also see that shortly after the tun0 interface is created, the following output gets written to the log file:

Jan 28 21:18:06 ubuntu-client /usr/local/bin/fermreload.sh: Adding VPN firewall rules.

Compared to the previous output, iptables -nL -v now also lists the rules for the tun0 interface.

$ sudo iptables -nL -v
Chain INPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target  prot opt in    out    source       destination         
    0     0 DROP    all  --  *     *      0.0.0.0/0    0.0.0.0/0     state INVALID
  124 16071 ACCEPT  all  --  *     *      0.0.0.0/0    0.0.0.0/0     state RELATED,ESTABLISHED
   10   620 ACCEPT  all  --  *     *      0.0.0.0/0    127.0.0.0/8         
    0     0 ACCEPT  tcp  --  eth0  *      0.0.0.0/0    0.0.0.0/0     tcp dpt:22
    0     0 ACCEPT  icmp --  *     *      0.0.0.0/0    0.0.0.0/0     icmptype 8
   31  1404 NFLOG   all  --  *     *      0.0.0.0/0    0.0.0.0/0     nflog-group 1
   31  1404 DROP    all  --  *     *      0.0.0.0/0    0.0.0.0/0           

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target  prot opt in    out    source       destination         
    0     0 NFLOG   all  --  *     *      0.0.0.0/0    0.0.0.0/0     nflog-group 1
    0     0 DROP    all  --  *     *      0.0.0.0/0    0.0.0.0/0           

Chain OUTPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target  prot opt in    out    source       destination         
    0     0 ACCEPT  tcp  --  *     tun0   0.0.0.0/0    0.0.0.0       tcp dpt:53
    0     0 ACCEPT  tcp  --  *     tun0   0.0.0.0/0    46.246.46.46  tcp dpt:53
    0     0 ACCEPT  tcp  --  *     tun0   0.0.0.0/0    194.132.32.23 tcp dpt:53
    0     0 ACCEPT  udp  --  *     tun0   0.0.0.0/0    0.0.0.0       udp dpt:53
    3   186 ACCEPT  udp  --  *     tun0   0.0.0.0/0    46.246.46.46  udp dpt:53
   10   620 ACCEPT  udp  --  *     tun0   0.0.0.0/0    194.132.32.23 udp dpt:53
    0     0 ACCEPT  tcp  --  *     tun0   0.0.0.0/0    0.0.0.0/0     tcp dpt:20
    0     0 ACCEPT  tcp  --  *     tun0   0.0.0.0/0    0.0.0.0/0     tcp dpt:21
    0     0 ACCEPT  udp  --  *     tun0   0.0.0.0/0    0.0.0.0/0     udp dpt:123
    0     0 ACCEPT  tcp  --  *     tun0   0.0.0.0/0    0.0.0.0/0     tcp dpt:22
    0     0 ACCEPT  tcp  --  *     tun0   0.0.0.0/0    0.0.0.0/0     tcp dpt:80
    0     0 ACCEPT  tcp  --  *     tun0   0.0.0.0/0    0.0.0.0/0     tcp dpt:443
    0     0 DROP    all  --  *     *      0.0.0.0/0    0.0.0.0/0     state INVALID
   75  8386 ACCEPT  all  --  *     *      0.0.0.0/0    0.0.0.0/0     state RELATED,ESTABLISHED
   10   620 ACCEPT  all  --  *     *      127.0.0.0/8  0.0.0.0/0           
    0     0 ACCEPT  icmp --  *     *      0.0.0.0/0    0.0.0.0/0     icmptype 8
    0     0 ACCEPT  tcp  --  *     eth0   0.0.0.0/0    0.0.0.0       tcp dpt:53
    0     0 ACCEPT  udp  --  *     eth0   0.0.0.0/0    0.0.0.0       udp dpt:53
    0     0 ACCEPT  udp  --  *     eth0   0.0.0.0/0    0.0.0.0/0     udp dpt:123
    0     0 ACCEPT  udp  --  *     eth0   0.0.0.0/0    0.0.0.0/0     udp dpt:1194
    0     0 ACCEPT  udp  --  *     eth0   0.0.0.0/0    0.0.0.0/0     udp dpt:1234
    0     0 ACCEPT  udp  --  *     eth0   0.0.0.0/0    0.0.0.0/0     udp dpt:1337
    0     0 ACCEPT  udp  --  *     eth0   0.0.0.0/0    0.0.0.0/0     udp dpt:2342
    0     0 ACCEPT  udp  --  *     eth0   0.0.0.0/0    0.0.0.0/0     udp dpt:5060
    0     0 ACCEPT  tcp  --  *     eth0   0.0.0.0/0    0.0.0.0/0     tcp dpt:22
    1    73 NFLOG   all  --  *     *      0.0.0.0/0    0.0.0.0/0     nflog-group 1
    1    73 DROP    all  --  *     *      0.0.0.0/0    0.0.0.0/0

After stopping the OpenVPN connection, the log informs you that the firewall rules got reloaded again:

Jan 28 21:19:40 ubuntu-client /usr/local/bin/fermreload.sh: Removing VPN firewall rules.

Again, use iptables -nL -v to verify that the VPN related rules are gone:

$ sudo iptables -nL -v
Chain INPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target  prot opt in    out    source       destination         
    0     0 DROP    all  --  *     *      0.0.0.0/0    0.0.0.0/0     state INVALID
   37  2725 ACCEPT  all  --  *     *      0.0.0.0/0    0.0.0.0/0     state RELATED,ESTABLISHED
    8   548 ACCEPT  all  --  *     *      0.0.0.0/0    127.0.0.0/8         
    0     0 ACCEPT  tcp  --  eth0  *      0.0.0.0/0    0.0.0.0/0     tcp dpt:22
    0     0 ACCEPT  icmp --  *     *      0.0.0.0/0    0.0.0.0/0     icmptype 8
    0     0 NFLOG   all  --  *     *      0.0.0.0/0    0.0.0.0/0     nflog-group 1
    0     0 DROP    all  --  *     *      0.0.0.0/0    0.0.0.0/0           

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target  prot opt in    out    source       destination         
    0     0 NFLOG   all  --  *     *      0.0.0.0/0    0.0.0.0/0     nflog-group 1
    0     0 DROP    all  --  *     *      0.0.0.0/0    0.0.0.0/0           

Chain OUTPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target  prot opt in    out    source       destination         
    0     0 DROP    all  --  *     *      0.0.0.0/0    0.0.0.0/0     state INVALID
   30  3033 ACCEPT  all  --  *     *      0.0.0.0/0    0.0.0.0/0     state RELATED,ESTABLISHED
    8   548 ACCEPT  all  --  *     *      127.0.0.0/8  0.0.0.0/0          
    0     0 ACCEPT  icmp --  *     *      0.0.0.0/0    0.0.0.0/0     icmptype 8
    0     0 ACCEPT  tcp  --  *     eth0   0.0.0.0/0    0.0.0.0       tcp dpt:53
    0     0 ACCEPT  udp  --  *     eth0   0.0.0.0/0    0.0.0.0       udp dpt:53
    0     0 ACCEPT  udp  --  *     eth0   0.0.0.0/0    0.0.0.0/0     udp dpt:123
    0     0 ACCEPT  udp  --  *     eth0   0.0.0.0/0    0.0.0.0/0     udp dpt:1194
    0     0 ACCEPT  udp  --  *     eth0   0.0.0.0/0    0.0.0.0/0     udp dpt:1234
    0     0 ACCEPT  udp  --  *     eth0   0.0.0.0/0    0.0.0.0/0     udp dpt:1337
    0     0 ACCEPT  udp  --  *     eth0   0.0.0.0/0    0.0.0.0/0     udp dpt:2342
    0     0 ACCEPT  udp  --  *     eth0   0.0.0.0/0    0.0.0.0/0     udp dpt:5060
    0     0 ACCEPT  tcp  --  *     eth0   0.0.0.0/0    0.0.0.0/0     tcp dpt:22
   19  2485 NFLOG   all  --  *     *      0.0.0.0/0    0.0.0.0/0     nflog-group 1
   19  2485 DROP    all  --  *     *      0.0.0.0/0    0.0.0.0/0   

BAZINGA!

If you experience any problems after following this Howto, please contact support@ipredator.se. For error corrections or feedback please write an email to feedback@ipredator.se. Of course we are also available via our Online Chat.