Port Triggering Using A NAT Firestarter Firewall And Specter In Debian/Ubuntu
Many that play PC games, such as battle.net, need to be able to set up port triggering. Typical "hardware" routers have the ability to set this up from online menus. However, using a Linux PC to perform your router functions can provide much more control and versatility than can be realized with a "hardware" router. All of your NAT (network address translation), firewall, and port forwarding functions can be implemented by iptables, the de facto firewall in Linux versions 2.4.x and 2.6.x.
I looked online quite a lot to try to find a linux- or iptables-based port triggering setup. There was plenty on port forwarding using iptables, but not really anything on port triggering. This article provides the details to implement simple port triggering using iptables, firestarter, ULOG, Specter, and a small amount of bash scripting. ULOG is the user space logging facility that can be added onto iptables, allowing for multicasting of packets. Specter is a program that runs a daemon that allows you to initiate actions as a result of subscribing to ULOG streams. Firestarter is a firewall GUI front-end to iptables, which makes interfacing to your firewall easy. Although it appears that there has not been recent development activity of firestarter, it is still a viable, user-friendly, easily-installed package that really does not require upgrades once properly configured. The only software that you need to install to be able to implement port triggering with this method is firestarter, Specter, and a few bash scripts. My configuration uses a PC running Debian Linux (although the following instructions will work for Ubuntu as well) as the router/firewall for my home LAN, and most of the PCs in my LAN are running Windows (the gaming PCs).
I use as an example port triggering for battle.net. The main reason for this is that my son likes to host games for this, so I have a decent tester, and he has proven the setup for many months. Battle.net primarily uses tcp port 6112; the concept here is that when a PC on the internal LAN (for example, 192.168.0.102) initiates a tcp connection to the battle.net server site to start hosting a game, the PC initiates this on tcp destination port 6112. This action then "triggers" a sequence of commands that reconfigure the firewall temporarily: it enables port 6112 to be opened on the firewall, and forwards connections aimed at this port to machine 192.168.0.102, the "triggering" (hosting) machine. After a reasonable period of time, the port is closed and stealthed, dropping any packets that are routed to it to thwart any outside meddling or attacks (as usual).
Port triggering was implemented by specifying which port would be involved. Two iptables rules were added to the OUTBOUND chain, which in my setup (firestarter) is the set of rules that are jumped to from the OUTPUT chain after some screening is done on the OUTPUT traffic on the LAN. The rules perform ULOG logging output to the nlgroup 20 (an arbitrary number), matching only on TCP packets in the NEW state. When those packets are output, then Specter executes a bash file. The bash file sets up additional iptables rules and also creates lock files, so that we ensure that we do not create redundant iptables rules. Aside from setting up the port forwarding rules to the initiating PC on the LAN for incoming connections, the new iptables rules also add another ULOG output, ulog-nlgroup 21. This subscribes to packets at a very limited rate, specifically only 1/minute. This checks to make sure that the outbound traffic that initiated the connection is still active; if it is inactive for a specified amount of time (15 minutes), then the forwarding rules are eliminated, and the triggering setup is abandoned. This distinguishes port triggering from port forwarding; in port forwarding, the forwarding is implemented indefinitely, which is less secure.
Before we begin, some assumptions:
- you are using two NICs in the linux box being used as the router/firewall,
- one connected to the internet via a cable / DSL modem (I am assuming it is eth1),
- one connected to your LAN (I am assuming it is eth0).
/sbin/ifconfig
/etc/firestarter/user-post
$IPT -I OUTBOUND 4 -s 192.168.0.0/24 -i eth0 -p tcp -m tcp --dport 6112 -j ULOG --ulog-prefix "trigger write" --ulog-nlgroup 20 -m hashlimit --hashlimit 1/minute --hashlimit-burst 2 --hashlimit-mode srcip,dstport --hashlimit-name w6112
$IPT -I OUTBOUND 5 -s 192.168.0.0/24 -i eth0 -p tcp -m state --state NEW --dport 6112 -j ULOG --ulog-nlgroup 21 --ulog-prefix "initial trigger" -m hashlimit --hashlimit 1/minute --hashlimit-burst 2 --hashlimit-mode srcip,dstport --hashlimit-name n6112
(replace 192.168.0.0/24 with your LAN address range, and eth0 with the appropriate interface name). In order to create the file, you will need to create it as root or with root privileges.
These two iptables rules watch your LAN for outgoing packets with a destination port of 6112 (the battle.net networking port). When it sees these packets, a limited number of them are logged to user space via netlink sockets. A package which we will install next, called "Specter", subscribes to these sockets and acts upon them. Initial packets are logged to nlgroup 21, and a script is used to initiate a battle.net session by setting up port forwarding to the initiating PC. Continuing packets (regardless of whether they are in a NEW state or not) are logged to nlgroup 20, and a script is used to keep track of whether there is still activity by the initiating PC or not. Since port forwarding to an internal LAN client opens ports to the internet, it is not something that you should leave in place when you are not playing a game. As long as packets have been sent from this PC within 15 minutes, the ports are left open and forwarded. After 15 minutes of inactivity, port forwarding is stopped, the session is closed, and it is assumed the game is over. A new session can then be immediately initiated.
To install Specter in Debian or Ubuntu, issue the following command:
sudo apt-get install specter specter-mysql specter-pgsql
We will need to edit the config file for Specter, /etc/specter.conf, to be able to act upon the user space logging. Add the following lines to the end of this file:
# nlgroup 20, write to file /tmp/forward.6112 if dest port 6112 is outbound from LAN
20 {
:BASE
:EXEC
command "/bin/echo %S %P %d > /tmp/forward.6112"
}
# nlgroup 21, run script initial_trigger_action if a new tcp connection to dest port 6112 is outbound from LAN
21 {
:BASE
:EXEC
command "/etc/firestarter/initial_trigger_action"
}
Again, you will need root privileges to save this file.
The home page for Specter is at: http://joker.linuxstuff.pl/specter/
Next, create the bash script file (it also needs root ownership) and save it as:
/etc/firestarter/initial_trigger_action
That file contains the following lines:
#!/bin/bash
# file initial_trigger_action %S %P %d
# script for initial trigger activity - run from specter for ulog-nlgroup 20
# check to verify lock file does not yet exist, if not, then
# create a lock file, /tmp/trigger.port.lock (port=port number to trigger on)
# add iptables rules for forwarding port number to source ip by using firestarter
# set up "at" file to check for continued activity from source on port
#
# find forward(date) file
# forwardfile=`ls -t1 forward* | grep -m 1 f`
# Source IP address
# Wait long enough for tmp file to be created by nlgroup 21 process
sleep 0.5
if [ -f /tmp/forward.6112 ]; then
while read inputline
do
SourceIP="$(echo $inputline | cut -d' ' -f1)"
Protocl="$(echo $inputline | cut -d' ' -f2)"
dest_port="$(echo $inputline | cut -d' ' -f3)"
done < /tmp/forward.6112
if [ -f /tmp/trigger.6112.lock ]; then echo "lock file already exists!! Get rid of it"; exit 1;
else
#
# write lock file
#
echo "$SourceIP $dest_port $Protocl" > /tmp/trigger.6112.lock
#
# add forwarding rules
# using firestarter
# add line in /etc/firestarter/inbound/forward
# of format:
# name, port, destIP, port, comment
# first copy forward to forward.backup
cat /etc/firestarter/inbound/forward > /etc/firestarter/inbound/forward.backup
# now add line to end and save forward
echo "f$dest_port, $dest_port, $SourceIP, $dest_port, fwd$dest_port-$SourceIP" | cat /etc/firestarter/inbound/forward - > /etc/firestarter/inbound/forward.tmp
cp /etc/firestarter/inbound/forward.tmp /etc/firestarter/inbound/forward
#
# now restart firestarter to activate forwarding
/etc/init.d/firestarter force-reload
# next step: run at command
at -f /etc/firestarter/trigger_test_6112 now + 5 minutes
fi
fi
# done
After creating it, you will need to make it executable with the command:
sudo chmod 755 /etc/firestarter/initial_trigger_action
Next, create the following bash script file (it also needs root ownership) and save it as:
/etc/firestarter/trigger_test_6112
Here is the file:
#!/bin/bash
# file trigger_test_6112
# script to test for continued trigger activity - run from at command
# lock file should be updated from specter ulog-nlgroup 21
# if tcp connections for game have not been established yet
# check to verify lock file exists, if not, then exit with error
# lock file is /tmp/trigger.6112.lock
# if lock file exists, check time of last modification (mod used touch)
# if less than 15 minutes, run another at command and exit, continuing chain
# if 15 minutes or more, remove iptables forwarding rules for forwarding port
# number to source ip, and remove iptables rulse for ulogging to nlgroup 21
#
# read lock file
# first step - check for lock file existence
if [ -f /tmp/trigger.6112.lock ]; then echo "OK, file exists"
else echo "lock file does not exist!!"; exit 1
fi
# second step - read variables from lock file
while read inputline
do
SourceIP="$(echo $inputline | cut -d' ' -f1)"
Protocl="$(echo $inputline | cut -d' ' -f2)"
dest_port="$(echo $inputline | cut -d' ' -f3)"
done < /tmp/forward.6112
# next step: check to see if outgoing port has been triggered recently
fwd_file_age=`ls -lt --time-style=+%s /tmp/forward.6112 | awk '{print $6}'`
now_time=`date +%s`
time_diff=$[$now_time-$fwd_file_age]
echo " time diff = $time_diff "
if [ "$time_diff" -ge "600" ]; then
# delete lock file
# else run at file and exit 0
# remove line of forward file containing $dest_port
# using sed
sed "/$dest_port/d" "/etc/firestarter/inbound/forward" > /etc/firestarter/inbound/forward.temp
cp /etc/firestarter/inbound/forward.temp /etc/firestarter/inbound/forward
#
# restart firestarter to de-activate forwarding
/etc/init.d/firestarter force-reload
rm /tmp/trigger.6112.lock
fi
# run at file to test whether forwarding should still be implemented
#
at -f /etc/firestarter/trigger_test_6112 now + 5 minutes
# done
After creating it, you will need to make it executable with the command:
sudo chmod 755 /etc/firestarter/trigger_test_6112
These scripts create a few files in the /tmp directory for debugging purposes; they can be disregarded aside from the lock file (or you can modify the scripts if you like to prevent them from being created). By default, mail is sent to root when a session is initiated, and during a session, announcing the status of the session. One useful feature of Firestarter is that you can monitor who is actually connected to your LAN PCs from the outside world; when a battle.net session is functioning properly, you will see several IP addresses connected on port 6112 to your LAN PC (for example, 192.168.0.102).
A couple of notes regarding this setup: with it, you can have multiple game participants behind the firewall on your LAN, at the same time that you have external connections to the hosting PC. The key issue is that the hosting PC needs to be the first one that contacts the battle.net website (the first one that actually makes an outgoing request to port 6112). This PC is identified as the host for 15 minutes after its last attempt to contact the website, and if that PC does not follow through and become the host, but instead a different LAN PC attempts to become the host, it will not be able to until the timers in the scripts expire and the firewall closes again. This can be a source of frustration if you have eager gamers on the LAN that are not heeding this requirement. This setup does not enable (or disable) pings from the internet. That can be done separately in Firestarter. Allowing pings (ICMP) is not necessary for battle.net to work; however, it is used sometimes for troubleshooting. I prefer to make my firewall entirely stealthed when a game is not active, and I haven't gone to the trouble to change the behavior of ICMP during a game.
Also, bash/sed gurus out there will note that I am no advanced scripter -- my goal was to get something working and document it sufficiently for someone else to know what I did. I welcome suggestions for improvement of the setup; as I mentioned, I found no setups (outside of OpenWrt, and it requires specialized hardware) that would allow you to do the type of automated port triggering needed for gaming and still close the ports at the end of the game for better security. Although this setup was written for Debian/Ubuntu, it should work equally well (with properly amended installation instructions) for other flavors of Linux. The hardware requirements of the router/firewall PC, aside from needing 2 NICs, is very modest. Mine has no terminal, it's in the closet collocated with the cable modem; I control it entirely by using remote VNC software and SSH (Putty and ultraVNC from a Windows PC).