Thursday, January 5, 2012

OpenBSD pf and Voice over IP

OpenBSD pf and Voice over IP

Background

In a typical home network, a NAT device hides a number of internal devices behind a single globally addressable IP address within the network provider's IP space. While VOIP is readily available to end consumers via the SIP protocol, SIP isn't directly usable behind a NAT device.Most VOIP providers utilize what is called a "media proxy", a set of servers that exist to assist with this issue by redirecting media streams from consumers to the VOIP provider's SIP servers. This workaround introduces two problems: The media proxies need to have ample bandwidth and low latency, but also end up disallowing more than one SIP device per customer IP address.
To allow for a home network based multi-line multi-device SIP setup, media proxy use is not possible. Instead, the home network NAT device should be configured to redirect SIP control and media streams to the appropriate IP phones within the home network. Packet filter from OpenBSD can fulfill that role. You could also run a local PBX or SIP router, but that solution adds moving parts and is beyond the scope of this note.

Operating System

This setup has been tested with FreeBSD 5.2 and above, with the integrated pf implementation. A fatal bug with the binat directive exists with pf(4) in FreeBSD 5.3 and below, and ALTQ was not completely integrated. Use FreeBSD 5.4 or above.

System Configuration

On OpenBSD everything you need is there by default, however on FreeBSD you need a custom kernel. The below kernel options, when appended to your kernel configuration file will enable pf(4) packet filtering, as well as several packet queueing algorithms.
device          pf                                                              
device          pflog                                                           
device          pfsync                                                          
options         ALTQ
options         ALTQ_CBQ
options         ALTQ_RED
options         ALTQ_RIO
options         ALTQ_HFSC
options         ALTQ_CDNR
options         ALTQ_PRIQ

Phone configuration

This configuration has been tested with the Cisco 7960 phone.Do not use NAT proxy or outbound_proxy. Define each call appearance with its distinct SIP proxy information, and the same control port of 5060/udp can be used for all. The STUN phone feature should be enabled, although some commercial SIP proxies can function without it.

pf Configuration

pf(4) uses /etc/pf.conf as its configuration file. Here is a basic subset of a ruleset that also uses ALTQ to guarantee bandwidth to the voice uplink, since upload bandwidth is usually restricted.While packet queueing is not always necessary, the occasional voice quality degradation associated with link bandwidth being unavailable is undesirable. It is a very useful capability to have at your disposal and allows for reliable, superior to PSTN voice quality.
# Return error codes for ports that are blocked. Allows faster error recovery
set block-policy return

# udp session timeout should be equal to or larger than your smallest SIP registration
# timer timeout. For a typical SIP timeout of 300 seconds, this should suffice.
set timeout { udp.first 300, udp.single 150, udp.multiple 900 }

# definitions
int_if = "fxp0"
ext_if = "fxp1"
int_net = "192.168.1.0/24"
ipphone1 = "192.168.1.18"
ipphone2 = "192.168.1.19"

# enable queueing on the external interface. Separate voice traffic and data first, then
# classify the data
altq on $ext_if hfsc bandwidth 512Kb queue { q_voice, q_other }
queue q_voice bandwidth 3.84Kb priority 6 hfsc(realtime 96Kb)
queue q_other bandwidth 416Kb hfsc { q_pri, q_std, q_low }
queue q_pri   bandwidth 200Kb priority 3 hfsc(red realtime 64Kb)
queue q_std   bandwidth 200Kb priority 2 hfsc(default red )
queue q_low   bandwidth 3.84Kb priority 1 hfsc(red )

# One translation line per IP phone. static-port is necessary to make pf retain the UDP
# ephemeral port, so that the remote SIP proxy knows what session we belong to
nat on $ext_if proto udp from $ipphone1 to any -> ($ext_if) static-port
nat on $ext_if proto udp from $ipphone2 to any -> ($ext_if) static-port

# Generic NAT rule for all internal network devices
nat on $ext_if from $int_net to any -> ($ext_if)

pass in  quick on lo0 all
pass out quick on lo0 all

# Allow external SIP control traffic 
pass in  quick on $int_if proto udp from $ipphone1 to any tag VOIP keep state
pass in  quick on $int_if proto udp from $ipphone2 to any tag VOIP keep state

pass in  quick on $ext_if proto tcp from any to any port 22 keep state \
  queue(q_std,q_pri)
pass in  quick on $ext_if proto tcp from any to any port 80 keep state \
  queue q_low

pass out quick on $ext_if tagged VOIP queue q_voice keep state
pass out quick on $ext_if proto tcp from any to any port 22 keep state \
  queue(q_std,q_pri)
pass out quick on $ext_if proto tcp from any to any flags S/SA keep state \
  queue(q_std,q_pri)
pass out quick on $ext_if proto udp from any to any port 53 queue q_pri \
  keep state

# Outgoing traffic creates state entries
pass out quick on $ext_if proto { tcp, udp, icmp } all keep state

block in log all

Automatic startup

Append this or similar to /etc/rc.conf:
pf_enable="YES"                 # Set to YES to enable packet filter (pf)
pf_rules="/etc/pf.conf"         # rules definition file for pf
pf_program="/sbin/pfctl"        # where the pfctl program lives
pf_flags=""                     # additional flags for pfctl
pflog_enable="YES"              # Set to YES to enable packet filter logging
pflog_logfile="/var/log/pflog"  # where pflogd should store the logfile
pflog_program="/sbin/pflogd"    # where the pflogd program lives
pflog_flags=""                  # additional flags for pflogd

Troubleshooting and verification

To verify that the implementation works as expected, a media stream should be setup from the internal network, NATted and forwarded to the external SIP gateway. Source and destination ports for control traffic (destination port 5060) and media traffic (varies) should remain unchanged by the gateway. Finally, your phones should work :)To verify correct packet prioritization, saturate the uplink with a large upload and attempt to use the IP phone at the same time. The IP phone traffic should get mapped to the high priority queue and voice quality should be good at the remote end. Because of ample download bandwidth, queueing is usually not needed and FreeBSD regular packet forwarding is sufficient.
  • Check status of queues: pfctl -vsq -v
  • Flush state table: pfctl -F state (Needed when updating ruleset since queue tagging persists with state entries)
  • Check firewall rule hit count: pfctl -s rules -v

Reference:
http://www.bastard.net/~kos/pf-voip.html

No comments: