Despite what some people think, you dont need to be some kind of network guru to run a dedicated routing firewall at home. It just looks complicated because the details are spread among various places. This is kind of a walkthrough for those with basic knowledge of Linux and networking, showing how to build a simple routing firewall for a typical small home network like my own: A couple servers, some Linux and Windows workstations, printer, webcam and stuff filling that 24-port switch for some reason. I do not give a complete configuration, which had to be adapted to your system anyway, but point out the vital parts and how things play together:
Like many, I got a T-DSL line. I run a few
services connected to the net, so I need a static IP, and since DTAG
does not want to give me one, I terminate my traffic at
manitu.de
for small money. For being able to use them, insist on a line that
can be used for ZISP. For DTAG, that means a "Call & Surf", not
an "Entertain" contract, no matter if salesdroids claim otherwise.
If you lease a fast line, you always get a flatrate account with a dynamic IP along with it. The guys from Manitu are nice, so I am too, and route traffic that does not need to origin from a static IP over DTAG.
And here is the first thing to configure: The interfaces. At first, of course the LAN interface(s) must be configured. I use multiple VLANs for switches, servers, workstations, Windows and "stuff that needs a port". By using VLAN-tagging on the way to the router, I save physical ports. First, enable the interface:
ip link set dev eth1 up
Now add virtual VLAN-tagged interfaces and assign networks to them, like shown for the first VLAN:
vconfig add eth1 1
ip link set eth1.1 up
ip -f inet addr add 10.127.0.1/24 broadcast 10.127.0.255 dev eth1.1
And so on for all other VLANs. That way the broadcast domains are kept small and firewalling things nicely is easy. You can use 4095 VLANs, that's more than enough for home.
Now let's get to remote connections. At first, the Ethernet link to the DSL modem should be up:
ip link set eth0 up
Most modern modems can be managed and have a pre-configured IP for that. My modem listens on 192.168.1.1, so I configure a /24 and set my IP in its middle, which is unlikely to be used by future modems:
ip -f inet addr add 192.168.1.127/24 dev eth0
Now that the modem can be reached and the line is up, let's
move on to internet connectivity. I want to run Manitu on
ppp0 and apart from the standard PPPoE configuration, my
/etc/ppp/peers/tdsl-manitu file contains the following lines.
The first logs the PPP configuration frames to recognize mistakes and
sets a unique prefix for logfile and configuration:
debug
logfile /etc/ppp/peers/tdsl-manitu.log
ipparam tdsl-manitu
This configures the underlying Ethernet interface and sets the PPP interface number:
eth0
unit 0
Finally, this group will create a new connection if it is closed:
persist
maxfail 0
holdoff 6
lcp-echo-interval 10
lcp-echo-failure 4
You may notice that there is no defaultroute
configured. That's right, I'll come back to that soon.
Linux uses
one independent PPP daemon per connection, even though
both use the same underlying interface.
ppp1 is the interface to T-Online, so it needs these
differences to the above in its own
/etc/ppp/peers/tdsl-tonline file:
logfile /etc/ppp/peers/tdsl-tonline.log
ipparam tdsl-tonline
unit 1
user '<Anschlusskennung><T-Online Nummer>#0001@t-online.de'
No we have a system with connections to multiple LANs and two paths to the internet. That's where routing comes into play.
First enable routing at all and for all interfaces:
echo 1 > /proc/sys/net/ipv4/ip_forward
echo 1 > /proc/sys/net/ipv4/conf/default/forwarding
echo 1 > /proc/sys/net/ipv4/conf/all/forwarding
The wish to route packets belonging to certain network services differently than others is commonly called "policy routing", but "advanced routing" in Linux. Instead of a single routing table, you have multiple tables, and a new table of rules to select which routing table to use. Packets not routed by one table fall through to the next rules that may select a new table.
The default rules refer to three tables: local for the local side
of the configured interfaces, main for the networks directly
reachable by those interfaces and default for networks reachable
through gateways. Unfortunately, there is no room between main
and default, so first let's remove the rule that selects routing
table main and then add it earlier:
ip -f inet rule del from all lookup main
ip -f inet rule add from all lookup main pref 32700
The default table has a confusing name, because there will be two default
tables soon. Go ahead and edit /etc/iproute2/rt_tables, renaming
the default table to
253 default-dynamic
and adding a new table:
1 default-static
That was easy, now let's add the rules themselves:
ip -f inet rule del from all lookup default-dynamic
ip -f inet rule add from all fwmark 1 lookup default-static pref 32766
ip -f inet rule add from all lookup default-dynamic pref 32767
That deletes the old default table rule and adds two
new rules: All packets marked with fwmark 1 will
be routed out the interface with the static IP and everything else
over the interface with the dynamic IP. The next section covers
how those marks are set.
What's still missing at this point are the actual routes in the
tables. The up script for the static IP interface needs these
lines:
echo 0 >/proc/sys/net/ipv4/conf/ppp0/rp_filter
/sbin/ip -f inet route replace default dev ppp0 table default-static
First, it disables the reverse path unicast filter. With policy routing,
the routing policy no longer depends on the address, and rp_filter
only acts on the address. Then it sets the route for the interface.
If PPP interfaces were like ethernet interfaces, they would exist all the time and you could configure routes statically. But the are created when a session starts and you can not configure a route for an interface that does not exist, just for interfaces that are down. Worse, PPP interfaces disappear after the session ends, removing any routes that refer to them. After that happened, the table is empty, not routing packets that fall through to the next rule. You would not want them routed to the interface with the dynamic IP, so don't let the table get empty and use a lower priority static route to catch them:
ip -f inet route replace default dev lo metric 255 table default-static
If the PPP interface is up, its implicit metric 0 gives its
default route a higher priority. If it is down, packets are routed to
lo and trashed there, not leaving the site through
the wrong path. Certainly static PPP interfaces would have been too easy
to use.
The interface with the dynamic IP does not need that, because there is no further table.
Things work fine with this in its up script:
echo 0 >/proc/sys/net/ipv4/conf/ppp1/rp_filter
/sbin/ip -f inet route replace default dev ppp1 table default-dynamic
At this point, ppp0 would be idle, because no packets carry a
firewall mark, an annoyingly stupid name for a 32-bit number that can be
assigned to a packet for many reasons, firewalling just being one of them.
Although we could try to match all packets that need those marks set, the
result would be much work and still not perfect. Connection tracking assigns
all packets to a connection and just marking the connection saves much work
and delivers a great result.
Due to limitations in Linux, the routing layer has no clue of connection tracking and its connection marks. Instead if uses firewall marks, but both marks have nothing to do with each other and must not be confused. The trick is to assign connection marks to express the policy and finally store the connection mark of the connection a packet belongs to into the packets firewall mark.
Let's start assigning connection marks with initializing a few iptables and three environment variables for reability:
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
STATIF=ppp0
STATMARK=1
DYNIF=ppp1
The first rules are obvious: Mark all connections from $STATIF
with $STATMARK, because we want responses to packets coming in
from that interface to be sent back the same way.
iptables -t mangle -A PREROUTING -i $STATIF -m state --state NEW -j CONNMARK --set-mark $STATMARK
iptables -t mangle -A PREROUTING -i $STATIF -m state --state ESTABLISHED -j CONNMARK --set-mark $STATMARK
iptables -t mangle -A PREROUTING -i $STATIF -m state --state RELATED -j CONNMARK --set-mark $STATMARK
About as obvious is that connections bound to the static IP as source should
be treated just the same:
iptables -t mangle -A OUTPUT -s 85.116.193.48 -j CONNMARK --set-mark $STATMARK
Of course mail should be sent originating from the static IP in order to get accepted, so SMTP connections from my smarthost must be marked, too:
iptables -t mangle -A PREROUTING -s 10.128.0.3 -p tcp --dport smtp -m state --state NEW -j CONNMARK --set-mark $STATMARK
iptables -t mangle -A PREROUTING -s 10.128.0.3 -p tcp --dport smtp -m state --state ESTABLISHED -j CONNMARK --set-mark $STATMARK
iptables -t mangle -A PREROUTING -s 10.128.0.3 -p tcp --dport smtp -m state --state RELATED -j CONNMARK --set-mark $STATMARK
I guess you get the pattern now. Mark all connections that you want to use the interface with the static IP with a connection mark, no matter if they are incoming or outgoing. Once that is done, assign the connection mark to the firewall mark:
iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark
iptables -t mangle -A OUTPUT -j CONNMARK --restore-mark
The second line is required, because packets originating from the router pass through different chains.
And while we are at it, although it belongs into different iptables chains, change the TCP MSS to the PMTU, because PPPoE means we have a smaller MTU than 1500 and sites running broken load balancers expect that the whole world will cure the symptoms for them, and that's what the world does:
iptables -A FORWARD -o ppp+ -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
If ISPs (aka internet service preventers)
would not act like IP space was a highly valuable resource reserved for large
companies that pay unreasonable money for it, I would
have got a few public IPs and saved that NAT crap. Instead I got one public address and use
destination NAT to route incoming connections to specific servers. This is needed
to forward SMTP packets to my smarthost:
iptables -t nat -A PREROUTING -i $STATIF -p tcp --dport smtp -j DNAT --to-destination 10.128.0.3
Again, repeat such rules for all services you run. Finally, the packets leaving the site
have to be NATed, too:
iptables -t nat -A POSTROUTING -o $STATIF -j MASQUERADE
iptables -t nat -A POSTROUTING -o $DYNIF -j MASQUERADE
There are many good documents explaining firewalling details, but not as many descriptions that target operability, so I will keep this section short by showing how my firewall is structured.
Very small installations know two sides: The internet before the firewall and the site behind. Larger sites introduce the concept of a DMZ, which is another network for servers behind the firewall with the advantage to restrict communication between those two networks behind the firewall as well. For some reason, people think it would be cool to put the DMZ next to the firewall, protecting servers by one firewall and users by two. DMZ is a good word if your users are armed and your admins are not, and great to sound cool. But how cool is a concept for 2 networks, if you can have 4095 VLANs, and how cool is one mistake in the outer firewall that exposes more of your valuable servers than you would like to?
Let's look at it from another perspective: Think of multiple networks, each connected with its own firewall to a backbone that connects all firewalls, with the internet happening to be one of those networks. Each packet has to pass through one firewall to leave its source network and through a second firewall to enter the destination network. With more smaller networks the amount of rules per network decreases to a manageable amount while giving fine granular control. A mistake in one rule, and even firewall admins make mistakes, requires a second identical mistake to cause a risk, because all packets pass two filters. Mistakes in the rules for leaving a network are even caught and can be logged by rules controlling entry to a network.
I consolidated
all filters and the backbone into a single system, keeping the structure.
All packets in the FORWARD chain are first sent to the
SRC-FILTER, a dispatcher that distributes packets depending
on their source to chains for individual networks. If the source
filter for the network accepts the packet, it passes control to the
DST-FILTER, again a dispatcher that distributes packets
depending on their destination to chains for individual networks.
If the destination filter for the network accepts the packet, it jumps
to the ACCEPT target. All in all, you have two dispatchers
that are easy to understand and two chains per network, one to leave it
and one to enter it. Given 4095 VLANs and VLAN tagged interfaces, you
can have lots of networks and always tell exactly what the implemented
policy for a network is.
Start by defining the two dispatcher chains:
iptables -N DST-FILTER
iptables -N SRC-FILTER
Now defines chains for all networks, e.g. an network for printers and the like by creating a new chain and first allowing existing connections to be kept:
iptables -N SRC-MEDIA
iptables -A SRC-MEDIA -m state --state ESTABLISHED -j DST-FILTER
iptables -A SRC-MEDIA -m state --state RELATED -j DST-FILTER
Having access to the two DNS servers and to NTP is nice:
iptables -A SRC-MEDIA -p udp --dport 53 -d 10.128.0.2 -j DST-FILTER
iptables -A SRC-MEDIA -p tcp --dport 53 -d 10.128.0.2 -j DST-FILTER
iptables -A SRC-MEDIA -p udp --dport 53 -d 10.128.0.3 -j DST-FILTER
iptables -A SRC-MEDIA -p tcp --dport 53 -d 10.128.0.3 -j DST-FILTER
iptables -A SRC-MEDIA -p udp --dport 123 -d 10.128.0.2 -j DST-FILTER
And that's it:
iptables -A SRC-MEDIA -j LOG --log-level info --log-prefix "DENY SRC-MEDIA: "
iptables -A SRC-MEDIA -m limit --limit 2/s -j REJECT
iptables -A SRC-MEDIA -j DROP
All violations are logged, and if not too many, rejected, and silently dropped else.
Now the entry to that network is to be defined:
iptables -N DST-MEDIA
iptables -A DST-MEDIA -m state --state ESTABLISHED -j ACCEPT
iptables -A DST-MEDIA -m state --state RELATED -j ACCEPT
iptables -A DST-MEDIA -s 10.128.0.0/24 -j ACCEPT
iptables -A DST-MEDIA -s 10.160.0.5 -p tcp --dport ftp -j ACCEPT
iptables -A DST-MEDIA -j LOG --log-level info --log-prefix "DENY DST-MEDIA: "
iptables -A DST-MEDIA -m limit --limit 2/s -j REJECT
iptables -A DST-MEDIA -j DROP
All systems from one net have full access, but only a single system from a different net has FTP access to a single host. Guess which OS runs in each network. :)
Each network has an associated SRC- and DST- filter chain.
What remains are the dispatchers:
iptables -A SRC-FILTER -i $DYNIF -j SRC-INTERNET
iptables -A SRC-FILTER -i $STATIF -j SRC-INTERNET
iptables -A SRC-FILTER -i eth1.6 -j SRC-MEDIA
iptables -A SRC-FILTER -m limit --limit 2/s --limit-burst 20 -j LOG --log-level info --log-prefix "DENY SRC-FILTER: "
iptables -A SRC-FILTER -j DROP
If you see anything logged by this chain, you forgot a network. The destination dispatcher
looks much the same:
iptables -A DST-FILTER -o $DYNIF -j DST-INTERNET
iptables -A DST-FILTER -o $STATIF -j DST-INTERNET
iptables -A DST-FILTER -o eth1.6 -j DST-MEDIA
iptables -A DST-FILTER -m limit --limit 2/s --limit-burst 20 -j LOG --log-level info --log-prefix "DENY DST-FILTER: "
iptables -A DST-FILTER -j DROP
Now start the source dispatcher and keep a last precaution against mistakes in it:
iptables -A FORWARD -j SRC-FILTER
iptables -A FORWARD -j LOG --log-level info --log-prefix "DROP BUGGY FORWARD: "
iptables -A FORWARD -j DROP
Finally, packets destined to and originating from the firewall need
filtering. It would be great if chains could be run as subchains,
acting on their result, to run the SRC-FILTER from
INPUT and DST-FILTER from OUTPUT,
but iptables does not allow that. So those need to be secured with a
single chain, violating the concept. At least the firewall itself only
needs administrative access and the ruleset will be tiny.