IPFILTER (IPF) Firewall
IPFILTER (IPF) Firewall
The author of IPFILTER is Darren Reed. IPFILTER is not FBSD operating system dependant. IPFILTER is an open source application and has been ported to FreeBSD, NetBSD, OpenBSD, Sun, HP, and Solaris operating systems. IPFILTER is actively being supported and maintained, with updated versions being released regularly.
The IPFILTER program runs in the kernel and consists of the firewall and separate NAT facilities. IPFILTER also has user-land front-end interactive interfaces for controlling the firewall rules, NAT, packet accounting, and the logging facility. Program IPF is used to load the firewall rules. Program IPNAT is used to load the firewall NAT rules. Program IPFSTAT reports on packet filter statistics and lists active rules sets. Program IPMON monitors IPFILTER for logged packets.
From this point on IPFILTER will be written as IPF and is intended to mean the same thing as IPFILTER.
IPF was originally written using a rules processing logic of ‘the last matching rule wins’ and used only stateless types of rules. Over time IPF has been enhanced to include a ‘quick’ option and a stateful ‘keep state’ option which drastically modernized the rules processing logic. IPF’s official documentation covers the legacy rule coding parameters and the legacy rule file processing logic, the modernized functions are only included as additional options, completely understating their benefits in producing a far superior secure firewall.
The instructions contained in this guide are based on using rules that contain the ‘quick' option and the stateful ‘keep state’ option. This is the basic framework for coding an inclusive firewall rule set.
An inclusive firewall only allows services matching the rules through. This way you can control what services can originate behind the firewall destined for the public internet and also control the services which can originate from the public internet accessing your private network. Everything else is blocked and logged by default design. Inclusive firewall rule sets are much more secure than exclusive firewall rule sets and are the only rule set type covered herein.
For detailed explanation of the legacy rules processing method, see http://www.obfuscation.org/ipf/ipf-howto.html#TOC_1
To see the FAQ: http://www.phildev.net/ipf/index.html
To search the open source IPFilter questions archives: http://marc.theaimsgroup.com/?l=ipfilter
Since all firewalls are based on interrogating the values of selected packet control fields, the creator of the firewall rules must have an understanding of how TCP/IP works, what the different values in the packet control fields are and how these values are used in a normal session conversation. For a good explanation go to http://www.ipprimer.com/overview.cfm
IPF is included in the basic FBSD install as a separate run time loadable module. IPF will dynamically load its kernel loadable module when the rc.conf statement ipfilter_enable="YES" is used. The loadable module was created with logging enabled and the ‘default pass all’ options. You do not need to compile IPF into the FBSD kernel just to change the default to ‘block all’; you can do that by just coding a block all rule at the end of your rule set.
Using the IPF run time loadable module is recommended.
It is not a mandatory requirement that you enable IPF by compiling the following options into the FBSD kernel. It’s only presented here as background information. Compiling IPF into the kernel causes the loadable module to never be used.
Sample kernel source IPF options statements are in the /usr/src/sys/i386/conf/LINT kernel source and are reproduced here.
options IPFILTER options IPFILTER_LOG options IPFILTER_DEFAULT_BLOCK
IPFILTER This tells the compile to include IPFILTER as part of it’s core kernel.
IPFILTER_LOG enables the option to have IPF log traffic by writing to the ipl packet logging pseudo-device for every rule that has the "log" keyword.
IPFILTER_DEFAULT_BLOCK This option changes the default behavior so any packet not matching a firewall ‘pass’ rule gets blocked.
To build a custom kernel see the Kernel Customizing section.
You need the follow statements in /etc/rc.conf to activate IPF at boot time.
ipfilter_enable="YES" # Start ipf firewall ipfilter_rules="/etc/ipf.rules" # loads rules definition text file # IE: not script file with rules in it ipmon_enable="YES" # Start IP monitor log ipmon_flags="-Ds" # D = start as daemon # s = log to syslog # v = log tcp window, ack, seq # n = map IP & port to names
If you have a LAN behind this firewall that uses the reserved private IP address ranges, then you need to add the following to enable NAT functionality.
gateway_enable="YES" # Enable as LAN gateway ipnat_enable="YES" # Start ipnat function ipnat_rules="/etc/ipnat.rules" # rules definition file for ipnat
The ipf command is used to load your rules file. Normally you create a file containing your custom rules and use this command to replace in mass the currently running firewall internal rules.
ipf –Fa –f /etc/ipf.rules
-Fa means flush all internal rules tables
-f means this is the file to read for the rules to load
This gives the user the ability to make changes to their custom rules file and run the above IPF command, thus updating the running firewall with a fresh copy of all the rules without having to reboot the system. This method is very convenient for testing new rules as the procedure can be executed as many times as needed.
See man IPF(8) for details on the other flag options available with this command.
The ipf command expects the rules file to be a standard text file. It will not accept a rules file written as a script with symbolic substitution.
There is a way to build IPF rules that utilities the power of script symbolic substitution. See the Building Rule Script section.
The default behavior of ipfstat is to retrieve and display the totals of the accumulated statistics gathered as a result of applying the user coded rules against packets going in and out of the firewall since it was last started or since the last time the accumulators were reset to zero by the ipf –Z command.
See ‘man ipfstat’ for details.
This is what the ipfstat command displays without any flags:
input packets: blocked 99286 passed 1255609 nomatch 14686 counted 0 output packets: blocked 4200 passed 1284345 nomatch 14687 counted 0 input packets logged: blocked 99286 passed 0 output packets logged: blocked 0 passed 0 packets logged: input 0 output 0 log failures: input 3898 output 0 fragment state(in): kept 0 lost 0 fragment state(out): kept 0 lost 0 packet state(in): kept 169364 lost 0 packet state(out): kept 431395 lost 0 ICMP replies: 0 TCP RSTs sent: 0 Result cache hits(in): 1215208 (out): 1098963 IN Pullups succeeded: 2 failed: 0 OUT Pullups succeeded: 0 failed: 0 Fastroute successes: 0 failures: 0 TCP cksum fails(in): 0 (out): 0 Packet log flags set: (0)
When supplied with either -i for inbound or –o for outbound, it will retrieve and display the appropriate list of filter rules currently installed and in use by the kernel.
Ipfstat –in displays the inbound internal rules table with rule numbers
Ipfstat –on displays the outbound internal rules table with rule numbers
Rules will be displayed like this:
@1 pass out on xl0 from any to any @2 block out on dc0 from any to any @3 pass out quick on dc0 proto tcp/udp from any to any keep state
Ipfstat –ih displays the inbound internal rules table, each rule prefixed with count of times the rule was matched
Ipfstat –oh displays the outbound internal rules table, each rule prefixed with count of times the rule was matched
Rules will be displayed like this:
2451423 pass out on xl0 from any to any
354727 block out on dc0 from any to any 430918 pass out quick on dc0 proto tcp/udp from any to any keep state
Ipfstat –t [ -C | -D | -P | -S | -T ]
The most important function of the ipfstat command is the –t flag which activates the display state table in a way similar to the way the ‘top’ command shows the FBSD running process table. When your firewall is under attack this function gives you the ability to identify, drill down to, and see the attacking packets. The optional sub-flags give the ability to select destination IP and port, or source IP and port, or protocol that you want to monitor in real time. See man ipfstat for details.
In order for ipmon to properly work, the kernel option IPFILTER_LOG must be turned on. This command has two different modes it can be used in. Native mode is the default mode when you type the command on the FBSD console command line without the –D flag.
Daemon mode is for when you want to have a continuous system log file available so you can review logging of past events. This is how FBSD and IPFILTER are configured to work together. FBSD has a built in facility to automatically rotate syslogs. That is why outputting the log information to syslogd is better than the default of a regular file. In the rc.conf file you see the ipmon_flags statement uses the "-Ds" flags:
ipmon_flags="-Ds" # D = start as daemon # s = log to syslog # v = log tcp window, ack, seq # n = map IP & port to names
The benefits of logging are obvious. Logging provides the ability to review after the fact information like what packets have been dropped, what addresses they came from, and where they were going, giving you a significant edge in tracking down attackers.
Even with the logging facility enabled, IPF will not generate any rule logging on its own. The firewall administrator decides what rules in the rule set he wants to log and adds the log keyword to those rules. Normally only deny rules are logged.
It’s very customary to include a default deny everything rule with the log keyword included as your last rule in the rule set. This way you get to see all the packets that did not match any of the rules in the rule set.
Syslogd uses its own special method for segregation of log data. It uses special groupings called ‘facility’ and ‘level’. IPMON in –Ds mode uses local0 as the ‘facility’ name. All IPMON logged data goes to local0.
You have to manually configure the /etc/syslog.conf file by adding the statements to direct the Local0 'facility' to the log file name recording the log records. FBSD keeps all of its syslog files in /var/log/ directory.
First allocate the new named log file for the IPFMON logged data.
touch /var/log/ipfilter.log # will allocate the file
The syslog function is controlled by definition statements in the /etc/syslog.conf file.
You will have to edit the /etc/syslog.conf file.
Add the following statement to syslog.conf:
The local0.* means to write all the logged messages to the coded file location.
To activate the changes to /etc/syslog.conf you can reboot or force the syslogd task into re-reading /etc/syslog.conf by issuing this console command /etc/rc.d/syslogd reload
Don’t forget to change /etc/newsyslog.conf to rotate the new named IPFILTER log you just created above.
Format of Logged Messages
Fields common to all messages are:
1. The date of packet receipt.
2. The time of packet receipt. This is in the form HH:MM:SS.F, for hours, minutes, seconds, and fractions of a second (which can be several digits long).
3. The name of the interface the packet was processed on, e.g., dc0.
4. The group and rule number of the rule, e.g., @0:17.
These can be viewed with ipfstat -in.
5. The action: p for passed, b for blocked, S for a short packet, n did not match any rules, L for a log rule. The order of precedence in showing flags is:
S, p, b, n, L. A capital P or B means that the packet has been logged due to a global logging setting, not a particular rule.
6. The addresses. This is actually three fields: the source address and port (separated by a comma), the -> symbol, and the destination address and port. 126.96.36.199,80 -> 188.8.131.52,1722.
7. PR followed by the protocol name or number, e.g., PR tcp.
8. len followed by the header length and total length of the packet,
e.g., len 20 40.
If the packet is a TCP packet, there will be an additional field starting with a hyphen followed by letters corresponding to any flags that were set. See the ipf.conf manual page for a list of letters and their flags.
If the packet is an ICMP packet, there will be two fields at the end, the first always being `ICMP' and the next being the ICMP message and sub-message type, separated by a slash, (e.g., ICMP 3/3 for a port unreachable message).
Building Rule Script
Some experienced IPF users create a file containing the rules and code them in a manner compatible with running them as a script with symbolic substitution. The major benefit of doing this is you only have to change the value associated with the symbolic name, and when the script is run all the rules containing the symbolic name will have the value substituted in the rules. Being a script, you can use symbolic substitution to code frequently used values and substitute them in multiple rules. You will see this in the following example.
The script syntax used here is compatible with the ‘sh’, ‘csh’, and ‘tcsh’ shells.
Symbolic substitution fields are prefixed with a dollar sign $.
Symbolic fields do not have the $ prefix
The value to populate the Symbolic field must be enclosed with "double quotes".
Start your rules file with this.
############# Start of IPF rules script ######################## oif="dc0" # name of the outbound interface odns="192.0.2.11" # ISP's dns server IP address Symbolic> myip="192.0.2.7" # My Static IP address from ISP ks="keep state" fks="flags S keep state" # You can use this same to build the /etc/ipf.rules file #cat >> /etc/ipf.rules << EOF # exec ipf command and read inline data, stop reading # when word EOF is found. There has to be one line # after the EOF line to work correctly. /sbin/ipf -Fa -f - << EOF # Allow out access to my ISP's Domain name server. pass out quick on $oif proto tcp from any to $odns port = 53 $fks pass out quick on $oif proto udp from any to $odns port = 53 $ks # Allow out non-secure standard www function pass out quick on $oif proto tcp from $myip to any port = 80 $fks # Allow out secure www function https over TLS SSL pass out quick on $oif proto tcp from $myip to any port = 443 $fks EOF ################## End of IPF rules script ########################
That’s all there is to it. The rules are not important in this example; how the Symbolic substitution field are populated and used are. If the above example was in the /etc/ipf.rules.script file, I could reload these rules by entering on the FBSD command line:
There is one problem with using a rules file with embedded symbolics. IPF has no problem with it, but the rc.conf
statement will not load the rules if the file this statement is pointing at contains symbolics. This is a FBSD rc.conf launch problem.
The solution is to delete the following statement in the rc.conf
and put the following script in this directory:
FBSD looks in this directory for scripts that have names ending in ‘.sh’ to automatically launch during the boot process. Apache and DHCP place their launch scripts there.
Your launch script should look like this.
#!/bin/sh sh /etc/ipf.rules.script
The permission on this script file must be read, write, exec for owner root.
chmod 700 /usr/local/etc/rc.d/ipf.loadrules.sh
Now when you system boots your IPF rules will be loaded using the script.
IPF Rule Sets
A rule set is a group of IPF rules coded to pass or block packets based on the values contained in the packet. The bi-directional exchange of packets between hosts comprises a session conversation. The firewall rule set processes the packet two times, once on its arrival from the public Internet host and again as it leaves for its return trip back to the public Internet host. Each TCP/IP service (IE: telnet, www, mail, etc;) is predefined by its protocol, source and destination IP address, or the source and destination port number. This is the basic selection criteria used to create rules which will pass or block services.
IPF was originally written using a rules processing logic of ‘the last matching rule wins’ and used only stateless types of rules. Over time IPF has been enhanced to include a ‘quick’ option and a stateful ‘keep state’ option which drastically modernized the rules processing logic.
The instructions contained in this section are based on using rules that contain the ‘quick' option and the stateful ‘keep state’ option. This is the basic framework for coding an inclusive firewall rule set.
An inclusive firewall only allows services matching the rules through. This way you can control what services can originate behind the firewall destined for the public Internet and also control the services which can originate from the public Internet accessing your private network. Everything else is blocked and logged by default design. Inclusive firewall rule sets are much more secure than exclusive firewall rule sets and are the only rule set type covered herein.
Installers Note: Warning, when working with the firewall rules, always, always do it from the root console of the system running the firewall or you can end up locking yourself out.
The rule syntax presented here has been simplified to only address the modern stateful rule context and ‘first matching rule wins’ logic. For the complete legacy rule syntax description see the online ‘man ipf’ page at http://www.freebsd.org/cgi/man.cgi?query=ipf&apropos=0&sektion=0&manpath=FreeBSD+6.0-RELEASE+and+Ports&format=html
- is used to mark the start of a comment and may appear at the end of a rule line or on its own line. Blank lines are ignored.
Rules contain keywords. These keywords have to be coded in a specific order from left to right on the line. Keywords are identified in bold type. Some keywords have sub-options which may be keywords themselves and also include more sub-options. Each of the headings in the below syntax has a bold section header which expands on the content.
Syntax = ACTION IN-OUT OPTIONS SELECTION STATEFUL
ACTION = block | pass
IN-OUT = in | out
OPTIONS = log | quick | on interface-name
SELECTION = proto value | source/destination IP | port = number | flags flag-value
Where value = tcp/udp | udp | tcp | icmp
Where source/destination IP = all | from object to object
Where object = IP address | any
Where number = port number
Where flag-value = S
STATEFUL = keep state
The | symbol used in the above syntax means ‘or’.
The action indicates what to do with the packet if it matches the rest of the filter rule. Each rule MUST have an action. The following actions are recognized:
block indicates that the packet should be dropped if the selection parameters match the packet.
pass indicates that the packet should exit the firewall if the selection parameters match the packet.
IN-OUT It is a mandatory requirement that each filter rule explicitly states which side of the I/O it is to be used on. The next keyword must be either in or out and one or the other has to be coded or the rule will not pass syntax check.
in means this rule is being applied against an inbound packet which has just been received on the interface facing the public Internet.
out means this rule is being applied against an outbound packet destined for the interface facing the public Internet.
OPTIONS Must be used in the order shown here.
log indicates that the packet header will be written to the ipl log (as described in the LOGGING section below) if the selection parameters match the packet.
quick indicates that if the selection parameters match the packet, this rule will be the last rule checked, allowing a "short-circuit" path to avoid processing any following rules for this packet. This option is a mandatory requirement for the modernized rules processing logic.
on indicates the interface name to be incorporated into the selection parameters. Interface names are as displayed by ifconfig. Using this option, the rule will only match if the packet is going through that interface in the specified direction (in/out). This option is a mandatory requirement for the modernized rules processing logic.
When a packet is logged, the headers of the packet are written to the IPL packet logging pseudo-device. Immediately following the log keyword, the following qualifiers may be used (in this order):
body indicates that the first 128 bytes of the packet contents will be logged after the headers.
first If the ‘log’ keyword is being used in conjunction with a "keep state" option, it is recommended that this option is also applied so that only the triggering packet is logged and not every packet thereafter which matches the ‘keep state’ information.
The keywords described in this section are used to describe attributes of the packet to be interrogated when determining whether rules match or don't match. There is a keyword subject, and it has sub-option keywords, one of which has to be selected. The following general-purpose attributes are provided for matching and must be used in this order:
proto value Proto is the subject keyword; it must be coded along with one of it’s corresponding keyword sub-option values. The value allows a specific protocol to be matched against it. This option is a mandatory requirement for the modernized rules processing logic.
Valid sub-option value keywords are:
tcp/udp | udp | tcp | icmp or any protocol names found in /etc/protocols are recognized and may be used. The special protocol keyword tcp/udp may be used to match either a TCP or a UDP packet, and has been added as a convenience to save duplication of otherwise identical rules.
source/destination IP =
all keyword is essentially a synonym for "from any to any" with no other match parameters.
from src to dst The from and to keywords are used to match against IP addresses. Rules must specify BOTH source and destination parameters. ‘any’ is a special keyword that matches any IP address. As in "from any to any" or
"from 0.0.0.0/0 to any" or "from any to 0.0.0.0/0 " or
"from 0.0.0.0 to any" or "from any to 0.0.0.0 "
IP addresses may be specified as a dotted IP address numeric form/mask-length, or as a single dotted IP address numeric form.
There isn't a way to match ranges of IP addresses which do not express themselves easily as mask-length. See this link for help on writing mask-length. http://jodies.de/ipcalc
port If a port match is included, for either or both of the source and destination, then it is only applied to TCP and UDP packets. When composing port comparisons, either the service name from /etc/services or an integer port number may be used. When the port appears as part of the from object, it matches the source port number; when it appears as part of the to object, it matches the destination port number. The use of the port option with the ‘to’ object is a mandatory requirement for the modernized rules processing logic.
As in ‘from any to any port = 80’
Port comparisons may be done in a number of forms, with a number of comparison operators, or port ranges may be specified.
port "=" | "!=" | "<" | ">" | "<=" | ">=" | "eq" | "ne" | "lt" | "gt" | "le" | "ge".
To specify port ranges, port "<>" | "><" .
Following the source and destination matching parameters, the following two parameters are mandatory requirements for the modernized rules processing logic.
flags is only effective for TCP filtering. The letters represents one of the possible flags that can be interrogated in the TCP packet header.
The modernized rules processing logic uses the ‘flags S’ parameter to identify the tcp session start request.
keep state indicates that on a pass rule, any packets that match the rules selection parameters are to activate the stateful filtering facility.
This option is a mandatory requirement for the modernized rules processing logic.
Stateful filtering treats traffic as a bi-directional exchange of packets comprising a session conversation. When activated keep-state dynamically generates internal rules for each anticipated packet being exchanged during the bi-directional session conversation. It has the interrogation abilities to determine if the session conversation between the originating sender and the destination are following the valid procedure of bi-directional packet exchange. Any packets that do not properly fit the session conversation template are automatically rejected as impostors.
Keep state will also allow ICMP packets related to a TCP or UDP session through. So if you get ICMP type 3 code 4 in response to some web surfing allowed out by a keep state rule, they will be automatically allowed in. Any packet that IPF can be certain is part of an active session, even if it's a different protocol, will be let in.
What happens is:
Packets destined to go out through the NIC connected to the public Internet are first checked against the dynamic state table. If the packet matches the next expected packet of that active session conversation, then it exits the firewall and the state of the session conversation flow is updated in the dynamic state table. The remaining packets get checked against the outbound rule set.
Packets coming in through the NIC connected to the public Internet are first checked against the dynamic state table. If the packet matches the next expected packet of that active session conversation, then it exits the firewall and the state of the session conversation flow is updated in the dynamic state table. The remaining packets get checked against the inbound rule set.
When the conversation completes, it's removed from the dynamic state table.
Stateful filtering allows you to focus on blocking/passing new sessions. If the new session is passed, all its subsequent packets will be allowed through automatically and any impostors automatically rejected. If a new session is blocked, none of its subsequent packets will be allowed through. Stateful filtering has technically advanced interrogation abilities capable of defending against the flood of different attack methods currently employed by attackers.