OpenBSD pf and Voice over IP
by Brian on Feb.08, 2006, under OpenBSD
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.
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 CBQ queueing on the external interface. Define 3 queues
altq on $ext_if cbq bandwidth 1000Kb queue { q_voice, q_pri, q_std }
queue q_voice bandwidth 192Kb priority 7 cbq(borrow)
queue q_pri bandwidth 50% priority 6 cbq(borrow)
queue q_std bandwidth 80% priority 1 cbq(default borrow)
# 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)
# Allow external SIP control traffic
pass in quick on $ext_if proto udp from any to any port 5060 keep state
# Allow media traffic, place in voice queue (guaranteed b/w)
# This assumes standard media stream configuration with a Cisco IP phone. Modify as
# necessary.
pass out quick on $ext_if proto udp from $ext_if to any port 16384:32768
tos 0xb8 queue q_voice keep state
# Outgoing traffic creates state entries
pass out quick on $ext_if proto { tcp, udp, icmp } all keep state
block in log all
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. Now, 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 regular packet forwarding is sufficient.
- Check status of queues: pfctl -s queue -v
- Flush state table: pfctl -F state (queue tagging persists with state entries)
- Check firewall rule hit count: pfctl -s rules -v
July 23rd, 2009 on 4:05 am
Just a quick note, looks like the example doesn’t fully compile as a cut-n-paste example:
% pfctl -n -vvv -f pf.conf
set skip on { lo }
altq on bce0 cbq bandwidth 1Mb tbrsize 1500 queue { q_voice q_pri q_std }
queue q_voice bandwidth 192Kb priority 7 cbq( borrow )
queue q_pri bandwidth 50% priority 6 cbq( borrow )
pfctl: the sum of the child bandwidth higher than parent “root_bce0”
queue q_std bandwidth 80% cbq( borrow default )
July 23rd, 2009 on 10:52 am
Good eye, Matt.. This was scraped from the web back in 06 just because I needed to know it. I never post any of *my* actual config, so I never noticed it. Thanks!