Notes From My Linux Firewall Presentation.

Saturday, 15. August 2009

Linux Firewall Presentation Notes…

Here are the notes regarding what I covered in my presentation to the Simi Conejo Linux User Group. The actual presentation covered these subjects in more detail then I was able to fit in here, but you should get the general idea of the concepts covered by reading this all the way through. If you would like me to present this presentation for your organization or group, please contact me via email.

The Linux OS.

Why use Linux for a router / firewall? Because all the tools you need to do Network Address Translation (NAT) and stateful packet inspection are built directly into the kernel. Also, Quality of Service (QoS) and policy routing is native as well. Linux also has a long history of stability, and can be easily used in embedded systems.

Why does my presentation use Debian as the base installation? Because I like Debian, and it’s my presentation… Well, also, because Debian has what I believe to be the best package management system around. And because Debian is the upstream distribution for many of the most popular end user distributions in the wild.

Hardware specific to routers and firewalls.

Most of the hardware used for desktop Linux PCs can work well for firewalls too. In most cases, when I setup a firewall for a client, it will be on a low end server platform, but you can use almost anything you want for the base hardware.

What makes a computer a router / firewall is that is has more then one network interface in it. You can’t very well route if you only have one interface. In most cases, I use 2 or 4 port network cards by the following manufacturers:

Planning your network.

What kind of connectivity do you really need? This is always a tough question, and many times, your answer doesn’t matter, because the local providers are only willing to give you certain alternatives.

One of the bigger choices is whether to pay for a static IP address or not. All I’m going to say is this: If you can get a static IP address, and it isn’t going to break you budget, do it. It will save you a ton of headaches down the road.

Now that we have an idea of what we have on the outside of our router / firewall, let’s talk about what we need for the inside. This is not as cut and dry as one might think, let’s look at some examples.

You have a couple computers, none of which are wireless, none of which need to have ports open to the Internet, and you trust traffic between those computers. Then all you really need to worry about is traffic from the outside world, so you can get away with an “inside” and an “outside” interface on your router / firewall.

Now let’s add to that couple of computers, a web and email server that you will be using to host your personal web page and a family email domain. You will be forwarding ports 25, 80, 110, 143 and 993 from the firewall back to this server. Now, you could put the server on the same physical network as your other two PCs, but that might not be a great idea. What if someone is able to compromise the server and gain access to a shell on it? What would be standing between them and you other two PCs? In this case, I would strongly recommend using a Demilitarized Zone (DMZ) interface on the firewall / router. This would mean that you would have two networks behind the firewall, and create rules to protect your “inside” network from both the “outside” and the “DMZ” network.

Ok, now we are going to add some room mates into the mix. So you decide you are going to include Internet access in the rent you charge your room mates. Now, I don’t know about you, but the last person I wanted on my personal network are my room mates. So, let’s add another interface so we can totally isolate their network from mine!

Hopefully you have gotten the idea… You need to protect your data from all different directions. Now, let’s look at how to do that!

How to configure your interfaces.

Depending on your distribution, this can be done via a configuration program, or by editing a file.

In Debian, and distributions based on Debian, you want to edit your /etc/network/interfaces file to setup your interfaces:

# 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

# The primary network interface
auto eth0
iface eth0 inet dhcp

auto eth1
iface eth1 inet static
        address 10.0.0.1
        netmask 255.255.255.0
        network 10.0.0.0
        broadcast 10.0.0.255

#auto eth2
iface eth2 inet dhcp

As you can see, we have three interfaces listed in the file, two of which are configured for DHCP, and one which is configured for a static IP address. Let’s give eth0 a static IP address and add a default route:

# 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

# The primary network interface
auto eth0
iface eth0 inet static
        address 4.1.254.18
        netmask 255.255.255.0
        network 4.1.254.0
        broadcast 4.1.254.255
        gateway 4.1.254.1

auto eth1
iface eth1 inet static
        address 10.0.0.1
        netmask 255.255.255.0
        network 10.0.0.0
        broadcast 10.0.0.255

As you can see, we have changed the word “dhcp” to “static” and have added the key words for a static address. We have also added the gateway keyword to specify a default gateway. You should only define one default gateway in this file.

I recommend you read the man page on interfaces and get familiar with the different key words and stanzas you can use. It will make your life easier.

Telling the kernel to act like a router.

By default, Debian does not forward packets between interfaces. This means you need to tell the kernel to allow packets to be forwarded. You can set this in the /etc/sysctl.conf file. The /etc/sysctl.conf file is where you will be setting some key switches later, but for now let’s just look at forwarding:

#
# /etc/sysctl.conf - Configuration file for setting system variables
# See /etc/sysctl.d/ for additonal system variables
# See sysctl.conf (5) for information.
#

[...]

# Uncomment the next line to enable packet forwarding for IPv4
#net.ipv4.ip_forward=1

[...]

more of the file...

Just uncomment the line #net.ipv4.ip_forward=1 and reboot the machine. You now have a router…

Setting up network Services.

Just having a router does not a network make… You need supporting services for your clients. Some are required, some are optional, but regardless. I would consider these the basics you will need to setup.

Directory Name Services (DNS).

Setting up bind as a cache only name server is extremely simple on Debian. Setting it up securely requires a few setting changes, but is certainly not hard. Here is the command to install Bind9:

gateway:~# apt-get install bind9

Now that we have it installed, let’s setup the the restrictions needed to keep you from acting as your neighbor’s name server.

gateway:/etc/bind# cd /etc/bind

gateway:/etc/bind# vi named.conf.options

options {
        directory "/var/cache/bind";
        allow-query {
                "localhost";
                "localnets";
        };

        allow-recursion {
                "localhost";
                "localnets";
        };

[...]    more file data

By adding the “allow-query” and the “allow-recursion” sections to the “options” section of the file, you will restrict other systems from using your name server for queries.

Add your inside IP address to your resolv.conf file as your name server, restart bind9, and you are all set.

For more info on all the things you can do with bind, check out The Bind9 Administrator Refference Manual.

Dynamic Host Configuration Protocol (DHCP).

Setting up a DHCP server is optional. You don’t have to have one. But DHCP can make your life easier if you have more then 3 or 4 workstations. If you want to set one up, just install the isc-dhcp-server.

gateway:~# apt-get install isc-dhcp-server

Now you need to edit the the /etc/dhcp/dhcpd.conf file:

gateway:~# cd /etc/dhcp

gateway:/etc/dhcp# vi dhcpd.conf 

####  Make sure you have the following settings
####  configured to match your internal network:

# Global Section
ddns-update-style none;
option domain-name "penguindroppings.org";
option domain-name-servers 10.0.0.1;
default-lease-time 600;
max-lease-time 7200;
authoritative;
log-facility local7;

# Subnet Definition Section
subnet 10.0.0.0 netmask 255.255.255.0 {
	range 10.0.0.51 10.0.0.200;
	option routers 10.0.0.1;
	option broadcast-address 10.0.0.255;
}

So, explaining all this is a bit beyond the scope of this writing, but the important things to note here, is that you have a global section, and a subnet definition section. Everything in the global section is used in the subnet section, if not overridden in the subnet section. At minimum, you must have one subnet section. The subnet section must contain a range statement.

I strongly recommend reading through the dhcp-options man page to see all of the different options available to you with the DHCP server.

Netfilter and IP Tables.

What is Netfilter? Netfilter is the name of the project that oversees the Linux kernel modules associated with IP protocol filtering and tracking. The Linux kernel is known for it’s robust networking support and hooks. The design concept of the IP stack has always been that every packet traverses the entire kernel stack as it passes through the system. This was a bit of an  issue in the Pentium 1 days, but with today’s processors, unless you do something extremely stupid setting the filters up, you can move 1gbit at line speed with reasonable assurance of maintaining the speed throughout the network conversation.

Which brings us to IPTables. IPTables in the command line tool that converts your simple command to something the kernel can interpet as a packet filter.

If you execute the following commands, you will be NAT’ing in no time:

gateway:~# iptables -F

gateway:~# iptables -F -t nat

gateway:~# iptables -A FORWARD -i eth1 -o eth0 -j ACCEPT

gateway:~# iptables -A POSTROUTING -t nat -i eth1 \
        -o eth0 -j SNAT --to-source 4.1.254.18

gateway:~# iptables -A FORWARD -i eth0 -o eth1 \
        -m state --state ESTABLISHED,RELATED -j ACCEPT

gateway:~# iptables -A FORWARD -j DROP

So, we are assuming that the inside interface is eth1 and the outside interface is eth0 and eth0’s static address is 4.1.254.18. What we are doing here is this:

  • Flushing the filter and nat tables
  • Allowing any traffic coming in the inside interface to go out the outside interface
  • Having all packets coming in from eth1 and going out eth0 source nat’ed to 4.1.254.18
  • Allowing packets that are established or related (have state) back in from eth0 to eth1
  • Dropping all other packets from being forwarded

I’ve included a script below that takes care of some other small things, but uses the same settings.

#!/bin/bash

outif="eth0"
inif="eth1"
outip="4.1.254.18"

iptables="/sbin/iptables"

modules="
ip_conntrack
ip_conntrack_ftp
ip_nat_ftp
"

for i in ${modules}; do
        /sbin/modprobe ${i}
done

/sbin/sysctl -w net.ipv4.netfilter.ip_conntrack_max=524288

${iptables} -F
${iptables} -F -t nat
${iptables} -F -t mangle
${iptables} -X

${iptables} -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP
${iptables} -A FORWARD -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP
${iptables} -A FORWARD -d poll.gotomypc.com -j DROP

${iptables} -A POSTROUTING -t nat -i ${inif} -o ${outif} \
        -j SNAT --to-source ${outip}

${iptables} -A FORWARD -i ${inif} -j ACCEPT
${iptables} -A FORWARD -i ${outif} -o ${inif} -m state \
        --state ESTABLISHED,RELATED -j ACCEPT

${iptables} -A FORWARD -j DROP

This loads some helper modules and gives you a place at the top to set your variables, but basically does the same thing as we were doing on the command line.

There are a ton of places to get information on Netfilter. Here are my favorite sources:

Setting up a second Internet connection.

This is pretty straght forward. You are just adding a second interface. So for now, I’ll refer you back to here to remind you how.

Once you have the interface configured, we’ll want to setup NAT for the new interface:

#!/bin/bash

outif="eth0"
inif="eth1"
## Add the new interface
outif2="eth2"

outip="4.1.254.18"
## Add the new IP address
outip2="216.95.15.37"

iptables="/sbin/iptables"

modules="
ip_conntrack
ip_conntrack_ftp
ip_nat_ftp
"

for i in ${modules}; do
        /sbin/modprobe ${i}
done

/sbin/sysctl -w net.ipv4.netfilter.ip_conntrack_max=524288

${iptables} -F
${iptables} -F -t nat
${iptables} -F -t mangle
${iptables} -X

${iptables} -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP
${iptables} -A FORWARD -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP
${iptables} -A FORWARD -d poll.gotomypc.com -j DROP

${iptables} -A POSTROUTING -t nat -i ${inif} -o ${outif} \
        -j SNAT --to-source ${outip}
## Add the new interface and address to a new NAT rule
${iptables} -A POSTROUTING -t nat -i ${inif} -o ${outif2} \
        -j SNAT --to-source ${outip2}

${iptables} -A FORWARD -i ${inif} -j ACCEPT

${iptables} -A FORWARD -i ${outif} -o ${inif} -m state \
        --state ESTABLISHED,RELATED -j ACCEPT
## Allow related packets in from the new interface
${iptables} -A FORWARD -i ${outif2} -o ${inif} -m state \
        --state ESTABLISHED,RELATED -j ACCEPT

${iptables} -A FORWARD -j DROP

To see my changes, look for the comments “##” and you should see them. Now that we have added the stuff for the second interface we can move on to the wonderful world of routing.

IPRoute2: For advanced routing.

We talked about the Netfilter group and all the work they do, but that is only part of the power of the Linux kernel when used for networking. The Linux Advanced Routing group does some really fantastic stuff as well to make the Linux kernel a world class routing and security platform. I’m touching on the big picture parts of IPRoute2, so you really should research the “ip” command on your own. I have some suggested reading at the end of this section.

Making sure “iproute2” is installed, and examining things with the “ip” command.

#### Making sure the iproute2 package is installed
gateway:~# apt-get install iproute 

#### Ethernet Link Information
gateway:~# ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN
 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
 link/ether 00:00:24:c5:9a:6c brd ff:ff:ff:ff:ff:ff
3: eth1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000
 link/ether 00:00:24:c5:9a:6d brd ff:ff:ff:ff:ff:ff
4: eth2: <BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000
 link/ether 00:00:24:c5:9a:6e brd ff:ff:ff:ff:ff:ff
5: eth3: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000
 link/ether 00:00:24:c5:9a:6f brd ff:ff:ff:ff:ff:ff

#### Neighbor (arp) information
gateway:~# ip neigh show
4.1.254.1 dev eth0 lladdr 00:00:24:c4:5c:ce REACHABLE

#### IP Address and Network information
gateway:~# ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN
 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
 inet 127.0.0.1/8 scope host lo
 inet6 ::1/128 scope host
 valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
 link/ether 00:00:24:c5:9a:6c brd ff:ff:ff:ff:ff:ff
 inet 4.1.254.18/24 brd 4.1.254.255 scope global eth0
 inet6 fe80::200:24ff:fec5:9a6c/64 scope link
 valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000
 link/ether 00:00:24:c5:9a:6e brd ff:ff:ff:ff:ff:ff
 inet 10.0.0.1/24 brd 10.0.0.255 scope global eth1

#### Routing Information
gateway:~# ip route show
4.1.254.0/24 dev eth0  proto kernel  scope link  src 4.1.254.18
10.0.0.0/24 dev eth2  proto kernel  scope link  src 10.0.0.1
default via 4.1.254.1 dev eth0

#### Routing rule information and priorities
gateway:~# ip rule show
0:    from all lookup local
32766:    from all lookup main
32767:    from all lookup default

#### Display all active routing tables
gateway:~# for i in $(ip rule show \
        | gawk '{ print $5 }'); do
        echo ""
        echo "# ${i}"
        ip route show table ${i}
done

# local
broadcast 127.255.255.255 dev lo  proto kernel  scope link  src 127.0.0.1
local 10.0.0.1 dev eth2  proto kernel  scope host  src 10.0.0.1
broadcast 208.83.99.127 dev eth0  proto kernel  scope link  src 208.83.99.71
broadcast 10.0.0.0 dev eth2  proto kernel  scope link  src 10.0.0.1
broadcast 208.83.99.64 dev eth0  proto kernel  scope link  src 208.83.99.71
broadcast 10.0.0.255 dev eth2  proto kernel  scope link  src 10.0.0.1
local 208.83.99.71 dev eth0  proto kernel  scope host  src 208.83.99.71
broadcast 127.0.0.0 dev lo  proto kernel  scope link  src 127.0.0.1
local 127.0.0.1 dev lo  proto kernel  scope host  src 127.0.0.1
local 127.0.0.0/8 dev lo  proto kernel  scope host  src 127.0.0.1 

# main
208.83.99.64/26 dev eth0  proto kernel  scope link  src 208.83.99.71
10.0.0.0/24 dev eth2  proto kernel  scope link  src 10.0.0.1
default via 208.83.99.65 dev eth0 

# default

Nothing shocking here. The only things you may not be familiar with are the “rule” command and showing the routing “table”. Advanced routing in linux operates in the following model:

  • Rules are consulted to place connections into tables
  • Tables contain specific routes that direct the connection

Design choices using rules and route tables.

As I said above, tables contain routes, If a connection matches a rule that specifies a table lookup, it will traverse through the table to see if it hits a match. If there is a matching route that will complete the connection, it stops reading rules and routes, and completes the connection as specified. So, a connection could travel through multiple tables before actually leaving the router, or only travel though one table.

By crafting the way we prioritize our rules, and what routes you specify in your tables, you can have a connection alway leave when in hits a table, or you can have all connections pass through multiple tables.

You probably noticed that the tables listed above have names. The names are optional, the default is to use numbers for your tables. I like to use descriptive names for my tables, but you have to add them to the /etc/iproute2/rt_tables file. Here is an example:

gateway:~# cat /etc/iproute2/rt_tables
#
# reserved values
#
255    local
254    main
253    default
0    unspec
#
# local
#
#1    inr.ruhep

Just add a line like “200 mytablename” to the file, and it will always use that name for table 200.

Pulling All The Pieces Together.

Let’s lay down the settings we are going to use for the next part of this. From here on, these are the configuration settings I’m going to use as reference. If you see a setting used from here forward, and are unsure what it’s use is, use the following cheat sheet as reference:

eth0              ISP 1 Interface
4.1.254.18        ISP 1 Interface Address
255.255.255.0     ISP 1 Netmask
4.1.254.1         ISP 1 Default Route

eth1              ISP 2 Interface
216.18.59.22      ISP 2 IP Address
255.255.255.0     ISP 2 Netmask
216.18.59.1       ISP 2 Default Route

eth2              Inside Interface
10.0.0.1          Inside Interface Address
255.255.255.0     Inside Netmask

ispone            Routing table for ISP 1
isptwo            Routing table for ISP 2

This information will be used for all settings shown from here forward.

Now, let’s setup our interface file with the info from above:

gateway:~# vi /etc/network/interfaces

auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
        address 4.1.254.18
        netmask 255.255.255.0
        network 4.1.254.0
        broadcast 4.1.254.255
        gateway 4.1.254.1

auto eth1
iface eth1 inet static
        address 216.18.59.22
        netmask 255.255.255.0
        network 216.18.59.0
        broadcast 216.18.59.255

auto eth2
iface eth2 inet static
        address 10.0.0.1
        netmask 255.255.255.0
        network 10.0.0.0
        broadcast 10.0.0.255

Notice that I’ve used the “gateway” key word in the eth0 stanza to set the route of last resort. This route will be used if none of the rules we define are met by the connection.

Let’s go ahead and name our table:

gateway:/etc/network# (echo "200 ispone"; echo "201 isptwo") \
        >> /etc/iproute2/rt_tables

gateway:/etc/network# cat /etc/iproute2/rt_tables #
# reserved values
#
255    local
254    main
253    default
0    unspec
#
# local
#
#1    inr.ruhep
200 ispone
201 isptwo

The table numbers don’t matter here, just so long as they are unique, and are a 8 bit (between 0-255) number.

Now, we have the names specified, so let’s take a look at two ways of doing our specific rules.

All routes in one table Method, make sure all routes including locally attached networks are applied to a connection in a single table:

gateway:/etc/network# ip route add 216.18.59.0/24 \
        dev eth1 src 216.18.59.22 table ispone

gateway:/etc/network# ip route add 10.0.0.0/24 \
        dev eth2 src 10.0.0.1 table ispone

gateway:/etc/network# ip route add 4.1.254.0/24 \
        dev eth0 src 4.1.254.18 table ispone

gateway:/etc/network# ip route add default \
        via 4.1.254.1 table ispone

gateway:/etc/network# ip route add 216.18.59.0/24 \
        dev eth1 src 216.18.59.22 table isptwo

gateway:/etc/network# ip route add 10.0.0.0/24 \
        dev eth2 src 10.0.0.1 table isptwo

gateway:/etc/network# ip route add 4.1.254.0/24 \
        dev eth0 src 4.1.254.18 table isptwo

gateway:/etc/network# ip route add default \
        via 216.18.59.1 table isptwo

Now any connection that is directed to one of the ISP tables, will find the locally attached destinations as well as a final destination via the ISP desired.

But how will our connections get to these tables? All we need do is add some rules to send them there.

What rules are, and how to define them.

Rules are nothing more then qualifiers that when met, cause a connection to look-up routes from a specified table. Here are the available qualifiers:

  • from
  • to
  • tos
  • fwmark

So, we can look at the address something is from or to, or the tos (Type Of Service) flag, or even a fwmark (FireWall MARK). We can even combine these qualifiers to make compound rules.

For our little example, we’ll just take all the Internet’s lower address space and send it to ISP 1 and all the Upper space to ISP 2.

gateway:~# ip rule add to 0.0.0.0/1 prio 501 lookup ispone

gateway:~# ip rule add to 128.0.0.0/1 prio 502 lookup isptwo

#### Let's list our rules:

gateway:~# ip rule show
0:        from all lookup local
501:      from all to 0.0.0.0/1 lookup ispone
502:      from all to 128.0.0.0/1 lookup isptwo
32766:    from all lookup main
32767:    from all lookup default

As you can see, we have two new rules. See the prio? In our case, it doesn’t really matter what order the rules are accessed as long as we access our rules before the rule pointing to main. Why? Because we have a route of last resort in there. and if it went to main first, it would never make it back out!

For more reading on policy routing and the advanced routing tools in Linux, try these sites:

Linux’s dirty little networking secrets…

Linux utilizes a route cache to optimize routing lookups. The cache provides a quick lookup table that bypasses just about everyting in the routing model we have talked about. This in most cases, is a great thing! As a matter of fact, it allows for us to do some neat things, such as make sure that a connection maintains the same path out the network. But there is a darker side….

You would think that the cache would be called before netfilter preforms NAT translations, but for whatever reason, this is not always true. This can cause some really wierd things to happen if you are trying to use two outbound connections running NAT to different providers. In many cases, it will cause connectivity to fail for the end user.

Also, connections are always up with ethernet, even if the far end gateway is unreachable. This makes it impossible to maintain fall over redundancy with two outside connections.

So, Multi Path Load Balancing is Impossible with Linux?

No, thanks to Julian Anastasov, there is a set of kernel patches that solve most of these problems! The patches are available here. The patch is available for kernels all the way through 2.6.30. Here is a list of what the patch provides:

  • Invalidates static routes that hove unreachable gateways
  • Changes the way alternative routes are activated
  • Corrects NAT problems when dealing with nexthop routes

Setting up routes that will take advantage of these fixes:

##### Specifying a route that monitors unreachable messages:

gateway:~# ip route add default proto static via 4.1.254.1 table ispone

gateway:~# ip route add default proto static via 216.18.59.1 table isptwo

gateway:~# ip route add default proto static via 4.1.254.1

gateway:~# ip route append default proto static via 216.18.59.1

##### Taking advantage of session based load balancing:

gateway:~# ip route add default proto static \
        nexthop via 4.1.254.1 dev eth0 weight 1 \
        nexthop via 216.18.59.1 dev eth1 weigh 1

So, let’s review what we just did. The first group of routes are designed to setup fallover, and the next hop example is using equal weight route balancing.

What’s left to do…

All that’s left to do is to run tests to see if it all works! I hope this has helped you understand some of the finer points of the Linux routing and firewall tools, and I hope everyone that attended the actual talk, enjoyed it!

— Stu

Share

Leave a Reply

You must be logged in to post a comment.