Environment: OpenBSD 4.8, FreeBSD 7.1Objective: understanding how PF firewall works and varous important rules and parameters

Concepts:

PF is enabled by default on OpenBSD 4.6 and newer releases. In OpenBSD 4.1 and later, the keep state option became the implicit default for all filter rules
1. block in log all # default deny policy
a. Above rule doesn't have 'quick' option, which means it will still continue to traverse to the end of the rules in /etc/pf.conf until it meets one rule with quick option. If the traffic doesn't meet any rest of rules, then this block rule will take it, so the result is 'block'.
# if above rule becomes 'block in log quick all' , then it will block everything for incoming traffic due to 'quick' option, it won't look down anymore
b. By default, the PF rules will pass traffic unless it's blocked by default policy or specific rules
c.'log' option indicates that it will record those matching information to pflog file, if you use 'tcpdump -n -e -ttt -i pflog0' to monitor, it will see those matching information for this block rule.

The following message is from PF FAQ page at http://www.openbsd.org/faq/pf/filter.html
Each packet is evaluated against the filter ruleset from top to bottom. By default, the packet is marked for passage, which can be changed by any rule, and could be changed back and forth several times before the end of the filter rules. The last matching rule "wins". There is an exception to this: The quick option on a filtering rule has the effect of canceling any further rule processing and causes the specified action to be taken.

2. Keep state and modulate state
a. In OpenBSD 4.1 and later, the keep state option became the implicit default for all filter rules.You can use 'pfctl -sr' to check running rules for this option.
According to PF FAQ page, by storing information about each connection in a state table, PF is able to quickly determine if a packet passing through the firewall belongs to an already established connection. If it does, it is passed through the firewall without going through ruleset evaluation.

b. When a rule creates state, the first packet matching the rule creates a "state" between the sender and receiver. Now, not only do packets going from the sender to receiver match the state entry and bypass ruleset evaluation, but so do the reply packets from receiver to sender. pass out on fxp0 proto tcp from any to any

This rule allows any outbound TCP traffic on the fxp0 interface and also permits the reply traffic to pass back through the firewall. Keeping state significantly improves the performance of your firewall as state lookups are dramatically faster than running a packet through the filter rules.

c. The modulate state option works just like keep state except that it only applies to TCP packets. The modulate state option can be used in rules that specify protocols other than TCP; in those cases, it is treated as keep state.
Keep state on outgoing TCP, UDP, and ICMP packets and modulate TCP ISNs:pass out on fxp0 proto { tcp, udp, icmp } from any to any modulate state

Another advantage of keeping state is that corresponding ICMP traffic will be passed through the firewall.

d. keep state for UDP
PF simply keeps track of how long it has been since a matching packet has gone through. If the timeout is reached, the state is cleared.

e. TCP SYN Proxy
According to PF FAQ page at http://www.openbsd.org/faq/pf/filter.html.
Normally when a client initiates a TCP connection to a server, PF will pass the handshake packets between the two endpoints as they arrive. PF has the ability, however, to proxy the handshake. With the handshake proxied, PF itself will complete the handshake with the client, initiate a handshake with the server, and then pass packets between the two. The benefit of this process is that no packets are sent to the server before the client completes the handshake. This eliminates the threat of spoofed TCP SYN floods affecting the server because a spoofed client connection will be unable to complete the handshake.

The TCP SYN proxy is enabled using the synproxy state keywords in filter rules. Example:

pass in on $ext_if proto tcp to $web_server port www synproxy state

Here, connections to the web server will be TCP proxied by PF.

Because of the way synproxy state works, it also includes the same functionality as keep state and modulate state.

The SYN proxy will not work if PF is running on a bridge(4).
3. Flag S/SA (default for tcp)
a. To have PF inspect the TCP flags during evaluation of a rule, the flags keyword is used with the following syntax:

flags check/mask
flags any

The mask part tells PF to only inspect the specified flags and the check part specifies which flag(s) must be "on" in the header for a match to occur. Using the any keyword allows any combination of flags to be set in the header.

pass in on fxp0 proto tcp from any to any port ssh flags S/SA
pass in on fxp0 proto tcp from any to any port ssh

As flags S/SA is set by default, the above rules are equivalent, Each of these rules passes TCP traffic with the SYN flag set while only looking at the SYN and ACK flags. A packet with the SYN and ECE flags would match the above rules, while a packet with SYN and ACK or just ACK would not.
4. block drop in or block return in
a. by default, block uses 'drop', you can specify 'return' in
* drop - packet is silently dropped.
* return - a TCP RST packet is returned for blocked TCP packets and an ICMP Unreachable packet is returned for all others.
For example:$ tcptraceroute -n 10.0.5.226
traceroute to 10.0.5.226 on TCP port 80 (http), 30 hops max
1 10.0.5.226[closed] 0.344ms 0.307ms 0.287ms

for the following rule, if without 'return', it will print 30 rows of asterisk.
block return in log quick on $int_if proto tcp from 10.0.10.0/24 to any port 80

c. pass out quickmodulate state # We'll opt to filter the inbound traffic only. Outbound packets can avoid being checked, for improving performance.
d. antispoof quick for { lo $int_if } # It is good to use the spoofed address protection:
e. Now open the ports used by those network services that will be available to the Internet. First, the traffic that is destined to the firewall itself:

pass in on egress inet proto tcp from any to (egress) \
port $tcp_services

Specifying the network ports in the macro $tcp_services makes it simple to open additional services to the Internet by simply editing the macro and reloading the ruleset. UDP services can also be opened up by creating a $udp_services macro and adding a filter rule, similar to the one above, that specifies proto udp.

f. The next rule catches any attempts by someone on the Internet to connect to TCP port 80 on the firewall. Legitimate attempts to access this port will be from users trying to access the network's web server. These connection attempts need to be redirected to COMP3:

For an added bit of safety, we'll make use of the TCP SYN Proxy to further protect the web server.

g. ICMP traffic needs to be passed:

pass in inet proto icmp all icmp-type $icmp_types

Similar to the $tcp_services macro, the $icmp_types macro can easily be edited to change the types of ICMP packets that will be allowed to reach the firewall. Note that this rule applies to all network interfaces.

h. Now traffic must be passed to and from the internal network. We'll assume that the users on the internal network know what they are doing and aren't going to be causing trouble. This is not necessarily a valid assumption; a much more restrictive ruleset would be appropriate for many environments.

pass in on $int_if

TCP, UDP, and ICMP traffic is permitted to exit the firewall towards the Internet due to the earlier "pass out" line. State information is kept so that the returning packets will be passed back in through the firewall.

i. using synproxy pass in quick on $ext_if proto tcp to {10.0.4.7,10.0.4.8} port {80,443} synproxy state
pass in quick on $int_if proto tcp from {10.0.0.1,10.0.0.200} to $int_if port ssh synproxy state

# for dns server sitting on DMZ to serve internet, as well as web server
pass in quick on $ext_if proto udp to 10.0.0.2 port 53 keep state
pass in quick on $ext_if proto tcp to {10.0.0.1} port {80,443} synproxy state

# for internal admin pc to ssh into firewall
pass in quick on $int_if proto tcp from {10.0.0.20,10.0.0.21} to $int_if port ssh synproxy state