Filtering Rules and the State Table

OpenBSD tracks approved connections in the state table. Packets that are part of an approved connection are allowed to pass. Consider this rule from an earlier example:

pass in on egress proto tcp from any to 192.0.2.12 port 80

If a packet matches this rule, and it has the TCP/IP flags that indicate this is the start of a TCP connection, PF permits the connection. PF also makes an entry in the state table. If a packet arrives that matches the state table, PF passes the packet without consulting the rules.

TCP States

First, we’ll look at a state table entry for a TCP connection. To view the state table, enter pfctl -s states.

# pfctl -s states
1all 2tcp 3192.0.2.12:80 <- 4198.51.100.227:55635 5ESTABLISHED:ESTABLISHED
…

This state table entry represents one specific connection that the packet filter approved. This state applies to all interfaces 1. If a state applies to only one interface, you’ll see the interface name here.

This TCP connection 2 was bound for 192.0.2.12 port 80 3, and came from the host 198.51.100.227 port 55635 4. When the first SYN packet arrived from 198.51.100.227 port 55635, PF added this entry to the state table. When 192.0.2.12 sent a SYN+ACK packet back to 198.51.100.227 port 55635, PF consulted the state table. This was clearly a match to the permitted SYN packet, so PF permitted that packet, even though no explicit rule in pf.conf permitted that connection. Data exchange between these two hosts and these two ports proceeded.

PF knows what an actual TCP/IP data exchange looks like. There’s a three-way handshake in the beginning, and a similar dance when the connection is finished and PF tracks the state of the connection. This particular connection is established on both sides 5, meaning that the initial setup negotiation succeeded, and data can flow back and forth freely.

If your server is busy enough, and you keep refreshing the state table view, you’ll catch connections in other states. Here’s the same connection as the data exchange ends and is being torn down:

all tcp 192.0.2.12:80 <- 198.51.100.227:55635  FIN_WAIT_2:FIN_WAIT_2

Note

One possible problem with viewing the state table is that pfctl displays a snapshot. By the time your eyes scroll down the screen, the table has changed. Personally, I find that’s the only way I can cope with the information. If you need to view states in a constantly updating display, in near real time, run systat states.

The state table is very specific. A state table entry permitting 198.51.100.227 port 55635 to 192.0.2.12 port 80 does not permit traffic between other hosts and ports. PF knows how traffic should flow, and it won’t allow traffic that isn’t obviously part of an existing TCP/IP exchange. If a packet arrives from 198.51.100.227 that looks like it’s part of this data exchange, except that it comes from port 55634 instead of 55635, the state table entry won’t match. Similarly, if PF knows that the connection is in a FIN_WAIT_2 state, or almost finished, a subsequent data packet with an ACK flag set won’t match and will be discarded. This is because a SYN request from the same host, from the same port, should not arrive—the client should know that the port is busy closing the previous connection. A new connection should come from a different port on the client and create a new state table entry.

Without stateful inspection, you would need to write firewall rules that not only permitted incoming traffic, but also permitted the responses. Your firewall rules would need to permit outbound connections to thousands of high-numbered ports, instead of just the single ports attached to desirable connections. Filtering based on TCP flags would be nearly impossible.

Note

As a consultant in the 1990s, I made a couple of rent payments dismantling such rules that had been shoehorned into stateless packet filters because they just aren’t realistic without stateful inspection. Plus, carefully tracking data exchanges not only simplifies rules, but also prevents a whole slew of TCP/IP-based attacks. You don’t hear much about these attacks anymore, thanks to stateful inspection.

UDP States

The state entries for UDP connections are similar to those for TCP connections.

all udp 192.0.2.12:53 <- 198.51.100.227:38469       SINGLE:MULTIPLE

This is a DNS query, bound for 192.0.2.12 port 53 from 198.51.100.227 port 38469. The client sent a single packet, and the destination replied with multiple packets. While stateful inspection cannot identify the state of this connection by flags, it can track the source and destination addresses and ports. You would need to write only a single rule permitting access to 192.0.2.12 port 53, and stateful inspection would permit the matching reply packets.

ICMP States

ICMP falls somewhere in between TCP and UDP. PF is aware of ICMP types and knows legitimate responses to ICMP packets, and by using stateful inspection, you get all of these benefits automatically. Much as you could write rules that permit specific TCP flags, you can write rules that permit certain ICMP types and codes. Most of us cannot manage that, and those of us who can know better. (ICMP errors referring to an existing TCP or UDP state are matched to the state, and don’t need to be allowed separately.)

Note

OpenBSD’s stateful inspection actually tracks more detail than source and destination addresses and ports. Add -v to the pfctl command to see more information, including timing, the number of packets passed as a result of the state, and more.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.188.121