Generating Firewall Rules with Perl
Listing 2. interfaces.conf
lo = lo gig = eth0 lan = eth1 wifi = eth2 voip = eth3 wan = eth5 tun = tun0
The init() function does the initial setup for the iptables environment. First, we flush, or remove, all of the rules and user chains. Then, we clear all of the rule counters. Later on, these counters will allow us to determine how many packets were caught by each rule in our firewall. Then, the script sets up the IP masquerading. I also put in a rule that allows traffic that is related to an already established connection to pass through the firewall without further evaluation. This keeps the entire rule set from having to be evaluated for each incoming packet. The rules apply only to each new connection request.
The set_default_policy() function configures how we treat network traffic in the absence of any other firewall rules. In this case, I'm interested only in policing incoming traffic; the policy accepts outbound traffic and traffic that the kernel has to route to its final destination. Trivial modifications to this script would allow you to configure policies for each direction. By default, my script denies traffic and requires the administrator to list explicitly all of the allowable traffic. This is the safest way to build a firewall, as opposed to a firewall that allows traffic by default and relies on the administrator to deny dangerous traffic specifically. You can never know in advance all of the dangerous traffic, so denying everything but well-understood traffic is a good idea.
The add_good_hosts() function creates firewall rules that allow all traffic from hosts or networks listed in the good_hosts.conf file. Note that I don't tie these rules to any particular interface. Traffic from whitelisted hosts or networks can come in on any interface. I usually put an entry in this file for my workstation at my home office as well as the network at work. This way, even if I make a silly mistake that would have kept me from logging in to my router remotely, I can still get in from work or my office workstation to undo the change. Of course, this also assumes that my workstation and the network at work haven't been compromised. Usually, the contents of this file are quite short indeed.
Listing 3. good_hosts.conf
127.0.0.1 Loopback 126.96.36.199/8 Multicast 10.4.0.0/16 VPN 10.0.1.1/32 Home office
Conversely, the add_bad_hosts() function creates firewall rules that block all traffic from hosts or networks listed in the bad_hosts.conf file. This function works almost exactly like the add_good_hosts() function with one important difference. When traffic from a blacklisted host comes to the router, the router will not only log this fact, it will also include the comment from the bad_hosts.conf file in the log. This way, I can look at my log file and see why a particular host was blocked. A useful improvement to this function would be to have it place the bad host rules in a separate chain and have that chain called early on in the rule set. This would give you the ability to add and delete hosts conveniently from this chain from an external program, perhaps in response to entries in your server log files.
Listing 4. bad_hosts.conf
188.8.131.52 My_comment www.microsoft.com Microsoft
The build_chains() function builds a series of firewall rule chains. I build a separate chain for each combination of interface and protocol. For example, if I had a Linux router with eth0, eth1, eth2 and eth3, I would create chains for eth0-tcp, eth0-udp, eth1-tcp, eth1-udp and so on. Then, I build the rules necessary to send the decision-making process down the appropriate chain. What we end up with is a decision tree that determines what to do with each packet entering the router. Unlike a linear list of firewall rules, the decision tree prevents the kernel from having to evaluate obviously irrelevant rules. For example, a TCP packet coming in on the WAN interface will never be tested against the rules meant for UDP packets on the Wi-Fi interface.
I haven't done any objective testing to see if this tree-pruning actually makes a significant performance improvement. On the other hand, once the program is written and debugged, it costs me nothing to change the configuration files and have this decision tree generated automatically. So even if it improves performance by only a small amount, it adds so little to the program's complexity that I think it makes sense to do it.
The add_rules() function is where most of the work is done. This function reads the contents of ports.conf, which is shown in Listing 5. Before we discuss the add_rules() function in detail, we should discuss the format and content of the ports.conf file.
The ports.conf file contains one line for each firewall rule. Each line contains three columns and an optional comment preceded by the # character. The first column is the user-defined label for the interface that the rule will be applied to. The second column is the protocol, that is, tcp, udp or the special case, all. Using all for the protocol creates a rule that allows all traffic on the interface in question. Finally, we have the port number. For example, the first line of the file creates a rule that allows SSH traffic to come in on the wan interface.
Mike Diehl is a freelance Computer Nerd specializing in Linux administration, programing, and VoIP. Mike lives in Albuquerque, NM. with his wife and 3 sons. He can be reached at email@example.com
Practical books for the most technical people on the planet. Newly available books include:
- Agile Product Development by Ted Schmidt
- Improve Business Processes with an Enterprise Job Scheduler by Mike Diehl
- Finding Your Way: Mapping Your Network to Improve Manageability by Bill Childers
- DIY Commerce Site by Reven Lerner
Plus many more.