Packet Filtering with OpenBSD

Jason Dixon

DixonGroup Consulting

February 8, 2006

Introduction

OpenBSD's Packet Filter (PF)

Filters TCP/IP traffic

Network Address Translation

Packet Normalization

Bandwidth control & prioritization

pfctl usage

Troubleshooting

History of OpenBSD/PF

A leading secure UNIX-like operating system

Emphasize code robustness and security

Open licensing is crucial

OpenBSD Packet Filter (PF) born out of IPFilter license change

Getting Started

Activation

Macros, Lists and Tables

Packet Filtering

Network Address Translation

Activation

Enable in /etc/rc.conf.local (overrides rc.conf

Control with pfctl

# grep pf /etc/rc.conf.local

pf=YES

# pfctl -e

pf enabled

# pfctl -d

pf disabled

Macros and Lists

Macros are user-defined variables

Context expandable (nesting)

ext_if = ”dc0”

int_if = ”xl0”

all_ifs = ”{“ $ext_if, $int_if “}”

websrv = “192.168.0.100”

dnssrvs = “{“ $websrv, 192.168.0.200 “}”

# Call with $ext_if, $int_if, etc...

Tables

Named structures optimized for IP/CIDR

Faster than large lists

Can be used with NAT, filter rules and much more

table <private> { 10/8, 172.16.12, 192.168/16 }

table <badhosts> persist

block on $ext_if from { <private>, <badhosts> }

Tables – cont'd

Can be populated via external file

Manipulate with pfctl

# grep file /etc/pf.conf

table <spammers> persist file “/etc/spammers”

# pfctl -t spammers -T add 218.70.0.0/16

# pfctl -t spammers -T show

# pfctl -t spammers -T delete 218.70.0.0/16

Packet Filtering

Filter layers 2, 3 and 4

Evaluated sequentially, first to last

Last match wins (except for “quick”)

Skip steps

Basic Syntax:

action [direction] [log] [quick] [on interface] [af] [proto protocol] \

[from src_addr [port src_port]] [to dst_addr [port dst_port]] \

[flags tcp_flags] [state]

block log on $ext_if

pass out on $ext_if inet proto tcp from ! $ext_if to any flags S/SA keep state

Packet Filtering – cont'd

action = [ block | pass ]

direction = [ in | out ]

log

quick

interface name (dc0, xl0, em0...) or group (ppp, vlan...)

af = [ inet | inet6 ]

protocol can be any valid protocol name (/etc/services) or number

Packet Filtering – cont'd

Addresses can be dotted quad, CIDR, FQDN, interface name, table, list, macro, “any”, or “all” (“any to any”)

Address modifiers:

Parentheses ()

:network

:broadcast

:peer

Negation !

Packet Filtering – cont'd

Ports can be any valid service name (/etc/services), number (1-65535), or a list

Port modifiers:

!= (not equal)

< (less than)

> (greater than)

<= (less than or equal)

>= (greater than or equal)

>< (range non-inclusive)

: (range inclusive)

<> (inverse range)

Packet Filtering – cont'd

TCP flags specifies the check/mask for TCP bits to check (SAFRUPEW)

State specifies the type of session tracking to perform:

keep state (TCP, UDP or ICMP)

modulate state (TCP only, strong ISNs)

synproxy state (proxies TCP handshake)

Packet Filtering – cont'd

State tracking options:

max number

source-track rule

source-track global

max-src-nodes number

max-src-states number

max-src-conn number

max-src-conn-rate number / interval

overload <table>

flush [ global ]

Packet Filtering – cont'd

Really Silly Example (TM)

table <abusive_hosts> persist

block in quick from <abusive_hosts>

pass in on $ext_if proto tcp to $web_server \

port www flags S/SA keep state \

(max-src-conn 100, max-src-conn-rate 15/5, \

overload <abusive_hosts> flush)

Packet Filtering – cont'd

Passive OS Fingerprinting (/etc/pf.os)

block drop on $ext_if from any os “Windows ME”

block drop in quick on $ext_if from \

any os Linux to any port ssh

pass in on $ext_if from any os OpenBSD \

flags S/SA keep state

Network Address Translation

nat – Standard translation, “many-to-one”

binat – Bidirectional mapping, “one-to-one”

rdr – Redirect (port-based)

Translation occurs before filtering

Basic Syntax:

nat [pass [log]] on interface [af] from src_addr [port src_port] to \

dst_addr [port dst_port] -> ext_addr [pool_type] [static-port]

nat on $ext_if from any to any -> ($ext_if)

binat on $ext_if from $foo_inside to any -> $foo_outside

rdr on $ext_if inet proto tcp from any to ($ext_if) port 80 -> $www port 8080

Network Address Translation (cont'd)

pool_type describes the address pool to use for translation (bitmask, random, source-hash, round-robin); used with nat or rdr

static-port – do not translate the source port

Don't forget to enable IP forwarding!

# sysctl -w net.inet.ip.forwarding=1

net.inet.ip.forwarding 0 -> 1

Network Address Translation (cont'd)

The “no” keyword can be used to create exceptions

rdr on $int_if inet proto tcp from ($int_if:network) to $www_ext port 80 -> $www_int

no nat on $int_if inet proto tcp from ($int_if:0) to ($int_if:network)

nat on $int_if inet proto tcp from ($int_if:network) to $www_int port 80 -> ($int_if:0)

# First rule redirects any internal traffic destined for our “external” webserver \

# to the internal webserver address

# Second rule negates any translation from the firewall itself to the internal \

# webserver

# Last rule translates all traffic with an internal source address to use the \

# firewall's internal source address

# This results in the webserver “seeing” the traffic as coming from the firewall, \

# so the traffic gets routed properly back through the gateway

Advanced Topics

Runtime options

Packet Normalization

Anchors

Tagging

FTP

QoS

Runtime Options

set block-policy [ drop | return ]

set debug [ none | urgent | misc | loud ]

set fingerprints filename

set limit [ frags | src-nodes | states ] value

set loginterface interface

set optimization option

normal – default, suitable for most networks

high-latency – e.g., satellite connections

aggressive – aggressively expire connections

conservative

set skip on interface

Runtime Options (cont'd)

set state-policy option

floating – states can match packets on any interface, as long as the packet matches a state entry and is passing the same direction as it was on the interface when the state was created.

if-bound – states are bound to the interface they are created on.

group-bound – same behavior as if-bound except states can match for any interface group (e.g., ppp)

set timeout [ interval | frag | src.track ] value

Runtime Options (cont'd)

set timeout interval 10

set timeout frag 30

set limit { frags 5000, states 2500 }

set optimization high-latency

set block-policy return

set loginterface $ext_if

set fingerprints /etc/pf.os.test

set skip on lo0

set state-policy if-bound

Packet Normalization

Scrub directive normalizes packets, making them “correct” for analysis

Reassembles fragments

Drops TCP packets with invalid flag combinations

Similar to filtering syntax

Exceptions can be preceeded with “no” keyword

Packet Normalization (cont'd)

Scrub options

no-df

random-id

min-ttl value

max-mss value

fragment reassemble

fragment crop

fragment drop-ovl

reassemble tcp

Anchors

Allow sub rulesets

Can be manipulated “on the fly”

Holds filter and translation rules

Can be loaded from an external file

Use same syntax as filter rules, so they can be applied to specific connection types

Four anchor types

anchor name

binat-anchor name

nat-anchor name

rdr-anchor name

Anchors (cont'd)

# grep anchor /etc/pf.conf

anchor badguys

load anchor badguys from “/etc/pf.d/anchor-badguys”

# cat > /etc/pf.d/anchor-badguys

block in log quick on $ext_if from <badguys> to any

^D

# pfctl -a badguys -f /etc/pf.d/anchor-badguys

# pfctl -a badguys -s rules

block in log quick on fxp0 from <badguys> to any

Tagging

Mark packets with an identifying string

Can be referred to in filter and translation rules

Useful for creating “trusts” or “zones”

Bridges can tag at L2, allowing PF to filter on tags at L3

Tags are “sticky”

One tag per packet, although they can be replaced by another tag

Internal only – tags are never sent over the wire

Tagging (cont'd)

pass in on $int_if tag INT_NET keep state

pass in quick on $int_if proto tcp to port 80 \

tag INT_NET_HTTP keep state

pass in quick on int_if from 192.168.1.5 keep state

# All packets coming in $int_if will be tagged INT_NET

# Packets destined for port 80 will replace any existing \

# tags with INT_NET_HTTP

# Packets from 192.168.1.5 will pass, but will still have \

# one of these tags since it's a prior matching rule

Tagging (cont'd)

pass in on $ext_if inet proto tcp from any to port 80 \

flags S/SA tag EXT_HTTP keep state

pass quick on $dmz1_if to port 80 tagged EXT_HTTP

pass quick on $dmz2_if to port 80 tagged EXT_HTTP \

modulate state

# Packets destined for *:80 will be tagged EXT_HTTP and \

# allowed through $ext_if

# Packets tagged as EXT_HTTP will be allowed to any HTTP \

# servers in $dmz1_if:network

# Packets tagged as EXT_HTTP will be allowed to any HTTP \

# servers in $dmz2_if:network, with state modulation

Tagging (cont'd)

# brconfig bridge0 rule pass in on fxp0 \

> src 0:de:ad:be:ef:0 tag SALLY

# grep SALLY /etc/pf.conf

pass in on $wlan_if tagged SALLY

FTP

# Typical client FTP proxy...

# pf.conf

rdr pass on $int_if inet proto tcp from any to any \

port 21 -> 127.0.0.1 port 8021

pass in on $ext_if inet proto tcp from port 20 \

to ($ext_if) user proxy flags S/SA keep state

# inetd.conf

127.0.0.1:8021 stream tcp nowait root \

/usr/libexec/ftp-proxy ftp-proxy -n

# kill -HUP `cat /var/run/inetd.pid`

FTP (cont'd)

# FTP proxy protecting DMZ server

# pf.conf

rdr on $ext_if inet proto tcp from any to any \

port 21 -> $ftpd

rdr on $ext_if inet proto tcp from any to any \

port 49152:65535 -> $ftpd

pass in on $ext_if inet proto tcp from any to $ftpd \

port 21 flags S/SA keep state

pass in on $ext_if inet proto tcp from any to $ftpd \

port > 49151 flags S/SA keep state

Quality of Service

Schedulers

Priority Queueing (priq)

Flat structure

Service oriented

Class Based Queueing (cbq)

Multiple queues or classes

Child queues can borrow from parent

Algorithms

Random Early Detection (red)

Explicit Congestion Notification (ecn)

Quality of Service (cont'd)

Basic Syntax

altq on interface scheduler bandwidth value [qlimit value] [tbrsize value] \

queue { queue_list }

queue name [on interface] bandwidth value [priority value] [qlimit value] \

scheduler ( sched_options ) { queue_list }

Quality of Service (cont'd)

Priq Example:

Root Queue (2Mbps)

Queue A (priority 1)

Queue B (priority 2)

Queue C (priority 3)

etc...

Quality of Service (cont'd)

CBQ Example:

Root Queue (2Mbps)

UserA (1Mbps)

ssh (50Kbps)

bulk(950Kbps)

User B (1Mbps)

audio (250Kbps)

bulk (750Kbps)

http (100Kbps)

other (650Kbps)

Quality of Service (cont'd)

altq on $ext_if priq bandwidth 384Kb queue \

{ std_out, www_out, ssh_out, dns_out, ack_out }

queue ack_out priority 7

queue dns_out priority 6

queue ssh_out priority 5

queue www_out priority 4

queue std_out priority 1

altq on $int_if priq bandwidth 2.5Mb queue \

{ std_in, www_in, ssh_in, dns_in }

queue dns_in priority 7

queue ssh_in priority 6

queue www_in priority 5

queue std_in priority 1

Quality of Service (cont'd)

block on $ext_if

block on $int_if

pass in on $int_if from ($int_if:network)

pass out on $ext_if inet proto tcp from ($ext_if) to any \

flags S/SA keep state queue(std_out, ack_out)

pass out on $ext_if inet proto tcp from ($ext_if) to any \

port { http https } flags S/SA keep state queue www_out

pass out on $ext_if inet proto { udp icmp } from ($ext_if) \

to any keep state

pass out on $ext_if inet proto { tcp udp } from ($ext_if) \

to any port domain keep state queue dns_out

pass out on $ext_if inet proto tcp from ($ext_if) to any \

port ssh flags S/SA keep state queue(std_out, ssh_out)

# continued...

Quality of Service (cont'd)

pass out on $int_if from any to ($int_if:network)

pass out on $int_if inet proto { tcp udp } from any \

port domain to ($int_if:network) queue dns_in

pass out on $int_if inet proto tcp from any port ssh \

to ($int_if:network) queue(std_in, ssh_in)

pass out on $int_if inet proto tcp from any port \

{ http https } to ($int_if:network) keep state queue www_in

Commands

Test syntax

# pfctl -nf /etc/pf.conf

Enable and load ruleset

# pfctl -ef /etc/pf.conf

Flush and load entire ruleset

# pfctl -Fa && pfctl -f /etc/pf.conf

Flush and load filter rules only

# pfctl -Fr && pfctl -Rf /etc/pf.conf

Flush and load translation rules only

# pfctl -Fn && pfctl -Nf /etc/pf.conf

Flush and load queueing rules only

# pfctl -Fq && pfctl -Af /etc/pf.conf

Commands (cont'd)

Show filter rules

# pfctl -s rules

Show translation rules

# pfctl -s nat

Show queueing rules

# pfctl -s queue

Show anchors

# pfctl -s Anchors

Show contents of dmz_rulez anchor

# pfctl -sA -a dmz_rulez

Show state table

# pfctl -s state

Commands (cont'd)

Show filter information (stats and counters)

# pfctl -s info

Show per-rule statistics of rules with labels

# pfctl -s labels

Show global timeouts

# pfctl -s timeouts

Show pool memory hard limits

# pfctl -s memory

Show tables

# pfctl -s Tables

Show contents of spammers table

# pfctl -t spammers -T show

Commands (cont'd)

Show all values

# pfctl -s all

Monitor logged traffic real-time

# tcpdump -nettti pflog0

Disable PF

# pfctl -d

References

OpenBSD -- http://www.openbsd.org/

PF -- http://www.benzedrine.cx/pf.html

OpenBSD FAQ -- http://www.openbsd.org/faq/index.html

PF User's Guide -- http://www.openbsd.org/faq/pf/index.html

2005-07-06T20:37:04