logo

Building a FreeBSD Firewall

Introduction

A firewall is a computer that monitors traffic between two networks. Firewalls are generally used to restrict access to computers on private networks from external networks such as the global Internet.

A computer that runs as a firewall is basically a glorified router. A normal router would route two networks by directing packets from one network to the other. A packet filtering router is one that examines the packets before letting them through. Packet filtering routers can decide whether to allow or deny access based on source and destination address, or the source and destination port number, or the type of protocol used (TCP, UDP, ICMP).

The goal of this article is to build a firewall based on FreeBSD 4.

1. Kernel Configuration

The /sys/i386/config/LOCAL file.

Enable IPv4 networking, but disable IPv6:

options         INET                    # InterNETworking
#options        INET6                   # IPv6 communications protocols
Strict IP networking:
options         TCP_DROP_SYNFIN         # drop TCP packets with SYN+FIN
options         TCP_RESTRICT_RST        # restrict emission of TCP RST
options         ICMP_BANDLIM            # rate limit ICMP replies
Enable firewalling code:
options         IPFIREWALL           # firewall
options         IPFIREWALL_VERBOSE   # print information about dropped packets
#options        IPFIREWALL_FORWARD   # enable transparent proxy support
options         IPFIREWALL_VERBOSE_LIMIT=100    # limit verbosity
#options        IPFIREWALL_DEFAULT_TO_ACCEPT    # allow everything by default
options         IPDIVERT             # divert(4) sockets for NAT
Disable loadable kernel modules (security issues):
options	NO_LKM
Disable BPF devices, used by tcpdump and other network sniffers:
# The `bpf' pseudo-device enables the Berkeley Packet Filter.
#pseudo-device   bpf             #Berkeley packet filter
And remove all unused device drivers. Use dmesg to see which ones are in use, and only keep those. Comment out all the others.

Rebuild a kernel with:

# config LOCAL
# cd /sys/compile/LOCAL 
# make depend && make && make install

2. Networking Configuration

The /etc/rc.conf file.

This machine is a router:

gateway_enable=YES                              
forward_sourceroute=NO
accept_sourceroute=NO
No-frills TCP:
tcp_extensions=NO
tcp_keepalive=YES
tcp_drop_synfin=YES
tcp_restrict_rst=YES
Tight ICMP control:
icmp_bmcastecho=NO
icmp_drop_redirect=YES
icmp_log_redirect=YES

Marc Falesse <marc.falesse@libertysurf.fr> also recommends adding the following to /etc/sysctl.conf:

net.inet.tcp.blackhole=2
net.inet.udp.blackhole=1
net.inet.tcp.log_in_vain=1
net.inet.udp.log_in_vain=1
A backhole port is a closed port that never responds, not even to say it's closed. This gives out even less information to port scanners such as nmap and slows them down. The log_in_vain options are used to trigger logging of those events, i.e. it allows packets destined for non-listening ports on a server to be logged to syslog. (Note that this may open your firewall to a DoS attack by flooding it with bogus packets. On the other hand, this may alert you of an ongoing portscan.)

3. Services Configuration

All unnecessary services should be disabled by default, and troublesome binaries deleted from the filesystem altogether (particularly named, sendmail, and gcc).

portmap_enable=NO
sendmail_enable=NO
named_enable=NO
Syslog is very useful on a firewall but we must close it to external DoS attacks:
syslogd_enable=YES
syslogd_flags="-ss"
But keep the essentials:
sshd_enable=YES
xntpd_enable=YES

3.1. Inet

Controls many other services, e.g. FTP, telnet, etc. Best to remove it entirely with:

inetd_enable=NO
Another possibility is to have run on the internal interface only:
# bind inetd to the internal interface only
inetd_enable=YES
inetd_flags="$inetd_flags -l -a interface_address"
If you do decide to leave inetd running anyway, then make sure to enable logging and to increase the number of times a service can be invoked in one minute. (The default is 256, I recommend 1024 - adjust it yourself as you see fit). If you are connecting with a slow link (a modem for example), this will not matter, but if you have a fast connection this "feature" can be used to create a DoS (Denial of Service) attack. Someone can create a simple shell script to invoke more then 256 connections to your computer which will cause your inetd service to shut down. On the other hand, if you want to support 1024 simultaneous connection to your box make sure you have hardware to support that. Or else someone can also cause DoS and crash your computer by opening 1024 telnet connections at one time. Hence, in the file /etc/rc.conf the line right below:
inetd_enable=YES
inetd_flags="-l -R 1024"
this will turn on logging (-l switch) and increase maximum connection number to 1024 from the default 256. You will also need to change your syslog.conf file in /etc directory.

3.2. NTP Time Protocol

NTP is a time protocol used to synchronize a computer to an external clock. The firewall can be used to broadcast time announcements to the internal network, keeping the correct time on all machines.

Enable NTP in the configuration file with:

xntpd_enable=YES
Find below a sample NTP configuration file for a host located in California.

#
# NTP configuration file (ntpd)
#

###################
# global options

# file storing permanent clock parameters
driftfile /var/db/ntp.drift

# default permission is to deny everything
restrict default ignore

# except from localhost
restrict 127.0.0.1

#################      
# time servers      

# ntp.pbi.net
server 206.13.7.12 prefer
restrict 206.13.7.12 nomodify noquery

# ns.scruz.net
server 165.227.1.1
restrict 165.227.1.1 nomodify noquery

# ntp.ucsd.edu
server 132.239.254.5
restrict 132.239.254.5 noquery nomodify

# time.five-ten-sg.com 
server 205.147.40.50
restrict 205.147.40.50 noquery nomodify

# ntp2.mainecoon.com 
server 63.192.96.3 
restrict 63.192.96.3 noquery nomodify

################      
# time service

# broadcast time to local networks
broadcast 10.1.255.255
Ntpd Configuration

4. Security Levels

Enabling security levels will slow down an attacker by preventing him from tampering with system files.

Set the immutable flag on all system binaries with:

# zsh
# chflags schg /bin/*(*) /sbin/*(*) /usr/bin/*(*) /usr/sbin/*(*)
Enable security levels with:
# use security levels
kern_securelevel_enable=YES
kern_securelevel=3
The kernel runs with four different levels of security. Any super-user process can raise the security level, but no process can lower it. The security levels are:
-1
Permanently insecure mode - always run the system in level 0 mode. This is the default initial value.
0
Insecure mode - immutable and append-only flags may be turned off. All devices may be read or written subject to their permissions.
1
Secure mode - the system immutable and system append-only flags may not be turned off; disks for mounted filesystems, /dev/mem, and /dev/kmem may not be opened for writing.
2
Highly secure mode - same as secure mode, plus disks may not be opened for writing (except by mount(2)) whether mounted or not. This level precludes tampering with filesystems by unmounting them, but also inhibits running newfs(8) while the system is multi-user.
In addition, kernel time changes are restricted to less than or equal to one second. Attempts to change the time by more than this will log the message ``Time adjustment clamped to +1 second''.
3
Network secure mode - same as highly secure mode, plus IP packet filter rules (see ipfw(8) and ipfirewall(4)) cannot be changed and dummynet(4) configuration cannot be adjusted.

Secure levels are so powerful that they might get in the way of the system administrator; enable only on a production machine which configuration does not evolve anymore.

5. Network Address Translation

Natd, a userland program, is used to multiplex and demultiplex connections from internal hosts to the external network, acting as a "masquerading router" for the internal network.

Enable NAT with:

natd_enable=YES           # Enable natd (if firewall_enable == YES)
natd_interface=eth0       # Public interface or IP address to use
natd_flags="–log_denied –use_sockets"
Natd uses a divert socket to intercept the packet flow and perform the address translation:
/sbin/ipfw add divert natd all from any to any via eth0
After translation by natd, packets re-enter the firewall at the rule number following the rule number that caused the diversion (not the next rule if there are several at the s ame number).

Because natd is a userland program, its use is not without impacting performance!

6. IP Filtering

6.1. How It Works

Each packet that is destined for a host located behind the firewall is compared to a set of rules. Every packet filtering router must have a default rule that denies or allows all traffic to pass.

When a packet arrives at the firewall, it will be compared to a "rule chain", a set of rules that describes which types of packets are allowed or not allowed. If the packet doesn't match any of the rules in the chain, the default accept or deny rule will be used.

In other words, all packets go through the rules list, starting at the top (the lowest-numbered rule), to the bottom (the highest-numbered rule, which describes the default behavior). A packet is matched against each rule till one rule matches. This match decides what whether the packet is allowed through or dropped. Only the first match matters; once a packets matched a rule, its fate is decided and the processing of that packet terminates.

Every packet potentially goes through the entire ruleset. Crafting the ruleset intelligently, i.e. ordering rules by match probability, minimizes the processing done for each packet. Ideally the most common packets should be matched at the very beginning of the ruleset.

6.2. TCP/IP Background

An IP connection is a 5-tuple: [protocol, IP-source-address, IP-destination-address, source-port, destination-port]

At least one element of the tuple must be different to identify a new connection. In other words, two connections cannot exist on the network that share these identifiers.

6.2.1. ICMP

Error and testing protocol.

Code Explanation
0 Echo Reply
3 Destination Unreachable
4 Source Quench
5 Redirect
8 Echo
11 Time Exceeded
12 Parameter Problem
13 Timestamp
14 Timestamp Reply
15 Information Request
16 Information Reply
ICMP Message Types

6.2.2. UDP = connection less

  • no guarantee of delivery
  • no preservation of order
  • no congestion control
  • guarantees data integrity
  • UDP = IP + checksum + demultiplexing to userland by port number

6.2.3. TCP = connection oriented protocol

  • guarantees data delivery and its integrity
  • guarantees preservation of order in packets
  • congestion control
  • enables a "virtual circuit" between two applications

Connection-oriented protocols necessitate the establishment of a connection. A TCP connection is created with special TCP packets, using TCP options such as SYN, FIN, RST, URG, ACK, PSH.

A connection progresses through a series of states during its lifetime. The states are: LISTEN, SYN- SENT, SYN-RECEIVED, ESTABLISHED, FIN-WAIT-1, FIN-WAIT-2, CLOSE-WAIT, CLOSING, LAST-ACK, TIME-WAIT, and the fictional state CLOSED. CLOSED is fictional because it represents the state when there is no connection. Briefly the meanings of the states are:

LISTEN
represents waiting for a connection request from any remote TCP and port.
SYN-SENT
represents waiting for a matching connection request after having sent a connection request.
SYN-RECEIVED
represents waiting for a confirming connection request acknowledgment after having both received and sent a connection request.
ESTABLISHED
represents an open connection, data received can be delivered to the user. The normal state for the data transfer phase of the connection.
FIN-WAIT-1
represents waiting for a connection termination request from the remote TCP, or an acknowledgment of the connection termination request previously sent.
FIN-WAIT-2
represents waiting for a connection termination request from the remote TCP.
CLOSE-WAIT
represents waiting for a connection termination request from the local user.
CLOSING
represents waiting for a connection termination request acknowledgment from the remote TCP.
LAST-ACK
represents waiting for an acknowledgment of the connection termination request previously sent to the remote TCP (which includes an acknowledgment of its connection termination request).
TIME-WAIT
represents waiting for enough time to pass to be sure the remote TCP received the acknowledgment of its connection termination request.
CLOSED
represents no connection state at all.

A TCP connection progresses from one state to another in response to events. The events are the user calls, OPEN, SEND, RECEIVE, CLOSE, ABORT, and STATUS; the incoming segments, particularly those containing the SYN, ACK, RST and FIN flags; and timeouts.

For example, this is the diagram for opening a connection:

      TCP A                                                TCP B

  1.  CLOSED                                               LISTEN

  2.  SYN-SENT    --> [SEQ=100][CTL=SYN]               --> SYN-RECEIVED

  3.  ESTABLISHED <-- [SEQ=300][ACK=101][CTL=SYN,ACK]  <-- SYN-RECEIVED

  4.  ESTABLISHED --> [SEQ=101][ACK=301][CTL=ACK]       --> ESTABLISHED

  5.  ESTABLISHED --> [SEQ=101][ACK=301][CTL=ACK][DATA] --> ESTABLISHED
Basic 3-Way Handshake for Connection Synchronization

Notice how the SYN bit is set on the first packet, then SYN+ACK on the second packet, resulting in a connection being in the ESTABLISHED state. All following packets have the ACK bit set.

FIN and RST packets are used to terminate a TCP connection.

6.3. Firewall Configuration

Enable firewall in /etc/rc.conf:

firewall_enable=YES
firewall_type=/usr/local/etc/firewall_rules
firewall_quiet=YES

firewall_flags="-p cpp			\
   -D INT_IF=eth0 			\
   -D INT_ADDR=192.168.1.1		\
   -D INT_NET=192.168.0.0/24	\
   -D EXT_IF=eth1 			\
   -D EXT_ADDR=207.238.131.190	\
   -D EXT_NET=207.238.131.180/27	\
   more options...			"
Create your own rules file to store site configuration.

The files implementing this configuration are also available separately.

6.3.1. Firewall Statements

Some things to watch out for.

The ipfw filter list (the ruleset) may not be modified if the system security level is set to 3 or higher (see init(8) for information on system security levels).

Packets are filtered both going in and coming out. Most connections need packets going in both directions. Remember that ipfw rules are checked both on incoming and outgoing packets.

in
Only match incoming packets.
out
Only match outgoing packets.
via ifX
Packet must be going through interface ifX.
via any
Packet must be going through some interface.
via ipno
Packet must be going through the interface having IP address ipno.

The via keyword causes the interface to always be checked. If recv or xmit is used instead of via, then the only receive or transmit interface (respectively) is checked. By specifying both, it is possible to match packets based on both receive and transmit interface, e.g.:

ipfw add 100 deny ip from any to any out recv ed0 xmit ed1
The recv interface can be tested on either incoming or outgoing packets, while the xmit interface can only be tested on outgoing packets. So out is required (and in is invalid) whenever xmit is used. Specifying via together with xmit or recv is invalid.

A packet may not have a receive or transmit interface: packets originating from the local host have no receive interface, while packets destined for the local host have no transmit interface.

Other options worth noting:

frag
Match if the packet is a fragment and this is not the first fragment of the datagram. frag may not be used in conjunction with either tcpflags or TCP/UDP port specifi- cations.
ipoptions spec
Match if the IP header contains the comma separated list of options specified in spec. The supported IP options are: ssrr (strict source route), lsrr (loose source route), rr (record packet route) and ts (timestamp). The absence of a particular option may be denoted with a `!'.
established
TCP packets only. Match packets that have the RST or ACK bits set.
setup
TCP packets only. Match packets that have the SYN bit set but no ACK bit.

6.3.2. Firewall Rules

A first and efficient way to limit access is the use of the following rules:

ipfw add allow tcp from any to any established
ipfw add allow tcp from net1 portlist1 to net2 portlist2 setup
ipfw add allow tcp from net3 portlist3 to net3 portlist3 setup
...
ipfw add deny tcp from any to any
The first rule will be a quick match for normal TCP packets, but it will not match the initial SYN packet, which will be matched by the setup rules only for selected source/destination pairs. All other SYN packets will be rejected by the final deny rule.

6.4. An ipfw Primer

Some useful tips to manage the ruleset.

Command Explanation
list Displays the current ruleset
del number Deletes rule numbered number
add number rule Adds a rule to the set
zero Resets counters
resetlog Resets logging
flush Deletes all rules; USE WITH CAUTION
Ipfw Commands

Here is how to temporarily disable the entire filter:

# ipfw add 1001 allow all from any to any
# (tests...)
# ipfw del 1001
We insert this rule at number 1001, right after the diversion command to natd.

Edit the rules file /usr/local/etc/firewall_rules then reload it with:

# source /etc/rc.firewall
This is better done at the console, since a mistake in the rules can easily lock you out of the system.

6.5. Firewall Rules File /usr/local/etc/firewall_rules

The complete version of this file is also available.

6.5.1. IP Level

First filter out all the bogus packets at the external interface. This is an optimization for natd: all packets that do not belong on this system are filtered out first.

00100 skipto 500 ip from any to EXT_ADDR in recv EXT_IF
00400 deny ip from any to any in recv EXT_IF
Hand off packets to natd. They will be reinjected with the address translation done, after this rule.
01200 divert natd ip from any to any via EXT_IF
Ensure that packets are really going where they should (no spoofing allowed here):
01400 deny ip from INT_NET to any in recv EXT_IF
01400 deny ip from EXT_NET to any in recv INT_IF
Stop martians at the outside interface (RFC1918), both from being received and being sent:
01500 deny ip from 192.168.0.0/16 to any in recv EXT_IF   
01500 deny ip from any to 192.168.0.0/16 out xmit EXT_IF  
01500 deny ip from 172.16.0.0/12 to any in recv EXT_IF    
01500 deny ip from any to 172.16.0.0/12 out xmit EXT_IF   
01500 deny ip from 10.0.0.0/8 to any in recv EXT_IF      
01500 deny ip from any to 10.0.0.0/8 out xmit EXT_IF     
Allow this host to communicate with the internal network:
02100 allow ip from INT_NET to INT_NET via any

6.5.2. TCP Connections

Allow all established connections:

07000 allow tcp from any to any established
By allowing all established connections, we now only need to look at connection setups.

Allow outgoing TCP setups from the local host, and from the private network:

07100 allow tcp from EXT_ADDR to any out xmit EXT_IF setup
07200 allow tcp from INT_NET to any in recv INT_IF setup
This machine runs a SMTP server. Allow data to flow to/from it.
07500 allow tcp from any to EXT_ADDR smtp setup
Allow and log SSH connections:
07600 allow log tcp from any to EXT_ADDR ssh setup
Reject & log all setup of incoming connections from the outside. Be aware that this may open the firewall to DoS attacks by flooding.
08000 reset log tcp from any to any in recv EXT_IF setup
That should be all for TCP.

6.5.3. UDP Connections

Allow DNS requests:

08100 allow udp from any domain to EXT_ADDR
08200 allow udp from EXT_ADDR to any domain
Allow NTP packets to/from the firewall:
08500 allow udp from any 123 to EXT_ADDR ntp in recv EXT_IF
08600 allow udp from EXT_ADDR 123 to any ntp out xmit EXT_IF
and to the local network:
08700 allow udp from INT_ADDR ntp to any ntp out xmit INT_IF
Reject & log UDP packets trying to sneak in through the external interface:
08900 unreach port log udp from any to any in recv EXT_IF
Or just use "deny" instead of "unreach port".

6.5.4. ICMP Packets

Very important! ICMP is an essential component of the TCP/IP stack that cannot be entirely filtered out. Safest choice is to allow everything, although this may open to ICMP fingerprinting and network scanning.

To/from the firewall:

10000 allow icmp from EXT_ADDR to any
10100 allow icmp from any to EXT_ADDR
to/from the internal network:
10200 allow icmp from INT_NET to any 
10300 allow icmp from any to INT_NET
A more restrictive choice that still allows ping & traceroute from the firewall and the internal network would be:
allow udp from EXT_ADDR to any 33434-33534
allow udp from INT_NET to any 33434-33534

allow icmp from EXT_ADDR to any icmptype 0,3,4,8,11,12
allow icmp from any to EXT_ADDR icmptype 0,3,4,8,11,12
allow icmp from INT_NET to any icmptype 3,4,8,11,12
allow icmp from any to INT_NET icmptype 0,3,4,11,12
At the very least message types 3, 4, 11 and 12 must be allowed:
allow icmp from EXT_ADDR to any icmptype 3,4,11,12
allow icmp from any to EXT_ADDR icmptype 3,4,11,12
allow icmp from INT_NET to any icmptype 3,4,11,12
allow icmp from any to INT_NET icmptype 3,4,11,12
You may want to finish with:
unreach filter-prohib icmp from any to any in recv EXT_IF
or:
deny icmp from any to any in recv EXT_IF

7. Testing

Generates copious outputs to the log files!!

For natd:

natd_flags += -verbose
For the IP filtering code:
sysctl -w net.inet.ip.fw.verbose=1

About the author

Renaud Waldura is a freelance consultant specializing in Internet applications. With a Master's in Software Engineering and 7 years of experience in the business, Renaud is able to tell a good piece of software when he sees one: FreeBSD is one of them. Visit Renaud's Web site at http://renaud.waldura.com/ to learn more about how he can help you with your business.


Copyright © 2000-2007 Renaud Waldura <renaud@waldura.com>