Overview

Any basic home router is a combination firewall/switch/wifi device. If you don't need wifi, you can get much finer-grained control of your home network by buying a low-power computer with 2 ethernet ports to use as your firewall, and then a cheap 4 or 8 port switch to do your switching. This gives you far better control and flexibility.

Here's how you configure Ubuntu 8.04 on a low-power firewall machine.

Be warned that this is based on a first experience with Ubuntu, so it's a bit meandering and stream-of-consciousness and has some cursewords.

Install Ubuntu 8.04 server edition on your firewall computer.

Not covered here. As a general rule, use i386 version (on the assumption that you are using smaller; low-power hardware).

Kernel Settings

Do NOT skip this step or your internal computers will not be able to reach the internet.

Ubuntu 8.04 by default does not allow packet forwarding, which is a fantastically sane default setting. However, this needs to be changed for your 2-ethernet-port computer to act as a firewall. Therefore, ensure /etc/sysctl.conf looks like this:

I think I want low-level messages on console, so comment out printk settings to restore them to Fedora-like defaults:

#kernel.printk = 4 4 1 7

Syncookies are enabled in Fedora, so enable them in Ubuntu too, by uncommenting:

net.ipv4.tcp_syncookies=1

And obviously enable packet forwarding so you can use a firewall:

net.ipv4.ip_forward=1

(ipv6 packet forwarding is disabled; leave it that way for now.)

Ignore ping requests by uncommenting this:

net/ipv4/icmp_echo_ignore_broadcasts = 1

Now, reboot! (Only way for these settings to take effect.)

A nice change from Fedora

Unlike Fedora, there is no SELinux, so we don't have to disable it. Yay!

There's no NetworkManager bullshit to disable, unlike Fedora. Yay!

Now, where are the network settings kept? How to I set the hostname? Ah. It is kept in /etc/hostname. Right now, it is listed as ubunturouter, like I asked it to be. I wonder if ubunturouter.lan is OK? I guess I'll leave it alone for now.

dnsmasq

Now let's install dnsmasq

This is something Fedora's Yum does automatically when you run any other command; update the repository db:

root@ubunturouter:~# apt-get update

This seems to be equivalent of yum update, but where's the option to check first?

This seems to be the only way to check for updates without doing anything else (-s puts it in 'dry run' mode):

root@ubunturouter:~# apt-get -s upgrade

OK, but let's update:

root@ubunturouter:~# apt-get upgrade

Ugly output compared to yum, but seems to have worked. Updated the kernel this time, so, rebooting!

OK, rebooted. Seems to have worked OK.

Let's see if dnsmasq and logwatch are installed.

root@ubunturouter:~# which dnsmasq
root@ubunturouter:~# which logwatch
root@ubunturouter:~# 

Nope. apt-get time!

root@ubunturouter:~# apt-get search dnsmasq
E: Invalid operation search

OK, that blows. Yum can do search.

Let's try this:

root@ubunturouter:~# apt-cache search dnsmasq
dnsmasq-base - A small caching DNS proxy and DHCP/TFTP server
dnsmasq - A small caching DNS proxy and DHCP/TFTP server

OK. Now let's try to install:

root@ubunturouter:~# apt-get install dnsmasq

That worked, but a little too well: at the end of the install messages, it said:

 * Starting DNS forwarder and DHCP server dnsmasq

Erm, thanks, but I didn't ask you to start dnsmasq. Now I have to find out how to shut it down until I'm ready to use it.

root@ubunturouter:~# /etc/init.d/dnsmasq stop
 * Stopping DNS forwarder and DHCP server dnsmasq                [ OK ] 
root@ubunturouter:~# 

Holy shit, that worked very similarly to Fedora.

It looks like update-rc.d is Deb/untu's flavour of chkconfig.

Let's see if we can show the runlevels for dnsmasq:

OK, there seem to be two command-line tools for this: update-rc.d, which seems workable but very low-level, and sysv-rc-conf, which seems to be much more like Fedora's chkconfig, but which isn't installed by default. Let's see if we can find it:

root@ubunturouter:~# apt-cache search sysv-rc-conf
sysv-rc-conf - SysV init runlevel configuration tool for the terminal
root@ubunturouter:~# 

Looks promising.

root@ubunturouter:~# apt-get install sysv-rc-conf

Seems to have worked.

root@ubunturouter:~# sysv-rc-conf --list

Yup. Woot!

root@ubunturouter:~# sysv-rc-conf --level 2 dnsmasq off
root@ubunturouter:~# sysv-rc-conf --list dnsmasq
dnsmasq      1:off	2:off	3:on	4:on	5:on
root@ubunturouter:~# 

That seems to have turned it off for runlevel 2 (the silly default in Ubuntu.)

Install logwatch

OK, let's see if logwatch exists for Ubuntu.

root@ubunturouter:~# apt-cache search logwatch
logwatch - log analyser with nice output written in Perl
fwlogwatch - Firewall log analyzer
root@ubunturouter:~# 

Yup. And, oooh, fwlogwatch definitely merits my attention too! I could turn on firewall logging and then get e-mail updates!

root@ubunturouter:~# apt-get install logwatch

That worked. It installed a lot of fucked up dependancies (like postfix) but whatever.

Oh: except it looks like postfix is started with the server. That's a no-no. Fix now:

root@ubunturouter:~# sysv-rc-conf --level 2 postfix off
root@ubunturouter:~# /etc/init.d/postfix stop
root@ubunturouter:~# ls /etc/cron.daily/
00logwatch  apt  aptitude  bsdmainutils  logrotate  man-db  mlocate  standard  sysklogd

Seems to have already added logwatch to cron, so that's nice.

Install libgmail

I know I'll need libgmail for my daily updates. Let's see if that's available:

root@ubunturouter:~# apt-cache search libgmail
gmailfs - Use your GMail account as a filesystem
python-libgmail - Python bindings to access Gmail accounts
root@ubunturouter:~# 

Yup. Time to install it.

root@ubunturouter:~# apt-get install python-libgmail

That was easy.

Configure networking

May as well start configuring stuff now. Are there any iptables rules currently running after booting?

root@ubunturouter:~# iptables --list
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         

Nope. So, unlike Fedora, I guess there's no firewall script to disable or override on startup. Handy. But, scary.

One thing we need to do is tie our ethernet ports to their respective MAC addresses. On Fedora, this is done in /etc/sysconfig/network-scripts, but not on Ubuntu, you create the file /etc/iftab, like so:

eth0 mac 00:08:9b:b2:cb:a1                             
eth1 mac 00:08:9b:b2:cb:a0

And that should keep our ethernet ports from "drifting".

In fact, let's reboot and ensure that is so.

Yes it is.

Configure firewall

Let's copy the firewall from our current router so that we will have it ready to go when we need it. But where should our firewall live? It looks like the equivalent of Fedora's /etc/rc.d/rc.local is /etc/rc.local, and we keep our firewall scripts in the same dir as rc.local, so let's put our firewall script right in /etc.

root@ubunturouter:/etc# scp mwood@192.168.1.1:/etc/rc.d/iptables* .

Seems to have worked. We are concerned with the file named /etc/iptables_nat_router.sh. Now in Fedora, some of the kernel modules needed by the firewall script are compiled right into the kernel, such that loading them produces errors. To figure out which ones were compiled in, I downloaded the source for the Fedora kernels and looked at the kernel config files. Do I need to do the same for Ubuntu? Let's first just try the firewall with all the module requests uncommented and see how that works out.

Let's just execute the module loading part of the script and see how that works out! Yes, that was a smart idea. I tried to load all of them, and there were no errors, so it looks like we're good to go leaving all of the kernel module loaders uncommented.

So here's what /etc/iptables_nat_router.sh looks like after crafting it for Ubuntu's kernel modules:

#!/bin/sh
#
# iptables firewall script for sharing
# broadband Internet, with no public services
#
# From Linux Networking Cookbook, by Carla Schroder, O'Reilly, 2007
#
# NOTE!!! Forwarding will not work unless
# /proc/sys/net/ipv4/ip_forward == '1'
# preferably set not here but through /etc/sysctl.conf

# define variables
ipt="/sbin/iptables"
mod="/sbin/modprobe"
LAN_IFACE="eth0"
WAN_IFACE="eth1"

#basic set of kernel modules
$mod ip_tables  # now compiled into kernel as of f10
$mod ip_conntrack  # now compiled into kernel as of f10 
$mod iptable_filter  # now compiled into kernel as of f10 
$mod iptable_nat
$mod iptable_mangle
$mod ipt_LOG
$mod ipt_limit
$mod ipt_state  # now compiled into kernel as of f10 
$mod ipt_MASQUERADE

#add these for IRC and FTP
$mod ip_nat_ftp
$mod ip_nat_irc
$mod ip_conntrack_ftp
$mod ip_conntrack_irc

# Flush all active rules and delete all custom chains
$ipt -F
$ipt -t nat -F
$ipt -t mangle -F
$ipt -X
$ipt -t nat -X
$ipt -t mangle -X

#Set default policies
$ipt -P INPUT DROP
$ipt -P FORWARD DROP
$ipt -P OUTPUT ACCEPT
$ipt -t nat -P OUTPUT ACCEPT
$ipt -t nat -P PREROUTING ACCEPT
$ipt -t nat -P POSTROUTING ACCEPT
$ipt -t mangle -P PREROUTING ACCEPT
$ipt -t mangle -P POSTROUTING ACCEPT

#this line is necessary for the loopback interface
#and internal socket-based services to work correctly
$ipt -A INPUT -i lo -j ACCEPT

#Enable IP masquerading
$ipt -t nat -A POSTROUTING -o $WAN_IFACE -j MASQUERADE

#Enable unrestricted outgoing traffic, incoming
#is restricted to locally-initiated sessions only
$ipt -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
$ipt -A FORWARD -i $WAN_IFACE -o $LAN_IFACE -m state --state ESTABLISHED,RELATED -j ACCEPT
$ipt -A FORWARD -i $LAN_IFACE -o $WAN_IFACE -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT

# Allow ssh access from LAN, but not WAN
$ipt -A INPUT -i $LAN_IFACE -p tcp -s 192.168.1.0/24 --dport 22 --syn -m state --state NEW -j ACCEPT

# Allow DNS access from LAN
$ipt -A INPUT -i $LAN_IFACE -p udp -s 192.168.1.0/24 --dport 53 -j ACCEPT
$ipt -A INPUT -i $LAN_IFACE -p tcp -s 192.168.1.0/24 --dport 53 -j ACCEPT

# Allow DHCP access from LAN
$ipt -A INPUT -i $LAN_IFACE -p udp --dport 67 -j ACCEPT
$ipt -A INPUT -i $LAN_IFACE -p udp --dport 68 -j ACCEPT


# Accept important ICMP messages
$ipt -A INPUT -p icmp --icmp-type echo-request  -j ACCEPT
$ipt -A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT
$ipt -A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT

#Reject connection attempts not initiated from inside the LAN
$ipt -A INPUT -p tcp --syn -j DROP
# XXX: what about dropping unwanted UDP?

Let's call that from our /etc/rc.local script:

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

/etc/iptables_nat_router.sh

exit 0

That should do it.

dnsmasq configuration

Now let's configure dnsmasq. First, back up the original file (it's fucking huge).

root@ubunturouter:/etc# cp dnsmasq.conf dnsmasq.conf.orig

and lay this down as the new /etc/dnsmasq.conf:

domain-needed
bogus-priv
local=/lan/
expand-hosts
domain=lan
interface=eth0
listen-address=127.0.0.1

# upstream nameservers
server=68.87.71.226
server=68.87.73.242
server=68.87.64.146

dhcp-range=lan,192.168.1.100,192.168.1.149,12h
dhcp-lease-max=100

I must confess to having scp'd that from my old router; so much easier!

Configure LAN port (eth0)

Looks like the only thing to do now is configure eth0 to come up as my LAN port, and all should be good!

Looks like the file to configure is /etc/network/interfaces, and that this should do it:

# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

# WAN interface
auto eth1
iface eth1 inet dhcp

# LAN interface
auto eth0
iface eth0 inet static
    address 192.168.1.1
    netmask 255.255.255.0
    network 192.168.1.0
    broadcast 192.168.1.255

OK. Time to plug in our router and see if the ethernet cards come up OK and if the firewall comes up OK. Then, we can start up dnsmasq and see if that's happy.

(Note: shut down VOIP box; shut down old router; plugged in new router; recycled cable modem; brought up new router.)

Well, shit, it started up just fine! And host lookup to yahoo.com worked.

Start dnsmasq

So now let's start dnsmasq so that I can have dns lookups from my LAN clients:

root@ubunturouter:~# /etc/init.d/dnsmasq start

Well, shit, that worked fine!

Let's make that change permanent:

root@ubunturouter:~# sysv-rc-conf --level 2 dnsmasq on
root@ubunturouter:~# sysv-rc-conf --list dnsmasq
dnsmasq      1:off	2:on	3:on	4:on	5:on

Woot!

Fix router hostname

My old router was just called router, so let's change the hostname from ubunturouter to router:

root@ubunturouter:/etc# hostname router

and put 'router' in /etc/hostname so that it's there for reboot.

Install ntpd

Install the ntp daemon so that our clocks are correct:

root@router:~# apt-get install ntp

That worked well.

Configure logwatch

Let's configure logwatch.

Ensure cron is installed, and python, and libgmail, and logwatch.

copy /usr/share/logwatch/default.conf/logwatch.conf to /etc/logwatch/conf/logwatch.conf

make these settings in /etc/logwatch/conf/logwatch.conf:

#MailTo =  # notice how this is commented out
#MailFrom = Logwatch   # notice how this is commented out
#Print =   # notice how this is commented out too
Save = /tmp/logwatch.txt  # notice how this is not commented out
#TmpDir = /var/cache/logwatch
TmpDir = /tmp

Everything else should already be in place for cron to launch logwatch daily.

Now let's set up a cron job to check for package updates.

PLEASE NOTE the bug noted at https://bugs.launchpad.net/ubuntu/+source/debianutils/+bug/38022 and described at https://help.ubuntu.com/community/CronHowto says your shell scripts in /etc/cron.daily cannot have periods in their names! I guess cron is hooked up to something called run-parts, which will silently fail instead of running the scripts with periods in their names. That is therefore reflected in the names I have chosen below.

Create the file /etc/cron.daily/98_check_for_package_updates:

#!/bin/sh
apt-get update > /dev/null 2>&1
apt-get -s upgrade > /tmp/check_for_package_updates.txt 2>&1
root@router:/etc/cron.daily# chmod +x 98_check_for_package_updates

create /etc/cron.daily/99_email (named 99* because jobs in cron.daily are executed in alphabetical order and we need e-mail to be sent AFTER logwatch and check yum check have run):

#!/usr/bin/python

import libgmail

f = open('/tmp/logwatch.txt', mode='rt')
logwatch = f.read()
f.close()

f = open('/tmp/check_for_package_updates.txt', mode='rt')
check_for_package_updates = f.read()
f.close()

message = logwatch + '\n\n' + check_for_package_updates

ga = libgmail.GmailAccount('my.gmail.username', 'mypassword')
ga.login()
gmsg = libgmail.GmailComposedMessage('my.gmail.username@gmail.com', 'router logwatch', message)
ga.sendMessage(gmsg)
root@router:/etc/cron.daily# chmod +x 99_e-mail

Done!