ONLamp.com    
 Published on ONLamp.com (http://www.onlamp.com/)
 See this if you're having trouble printing code examples


Securing Small Networks with OpenBSD Changes in pf: More on NAT

by Jacek Artymiak
05/08/2003

Welcome back!

In the previous installment of this series, we had a close look at macros, options, scrub rules, and NAT rules. Today we continue our adventures with NAT. We'll take on board a sample ruleset, discuss its implementation, and explore various ways of customizing it to your specific needs.

Like before, we'll be working with the DMZ design described in one of my earlier articles. Implemented in practice, a generic ruleset that follows that design could contain the following NAT entries:


#################################################################
# macro definitions
#################################################################
# ext_if -- external interface, connects the firewall to the
#           outside world
# prv_if -- private interface, connects to the private network 
#           segment to the firewall
# dmz_if -- DMZ interface, connects to the DMZ network segment 
#           to the firewall
#################################################################

ext_if = "ne1"
prv_if = "ne2"
dmz_if = "ne3"

#################################################################
# ext_ad -- the IPv4 address of the ext_if external interface
# prv_ad -- the range of IPv4 addresses on the private network
# dmz_ad -- the range of IPv4 addresses on the DMZ network 
#################################################################

ext_ad = "x.x.x.x/32"
prv_ad = "192.168.1.0/24"
dmz_ad = "192.168.2.0/24"

#################################################################
# dmz_domain_ad -- the internal IPv4 address of the externally 
#                  accessible DNS server residing in the DMZ
# dmz_www_ad    -- the internal IPv4 address of the externally
#                  accessible HTTP server residing in the DMZ
# dmz_smtp_ad   -- the internal IPv4 address of the externally
#                  accessible SMTP server residing in the DMZ
# dmz_ftp_ad    -- the internal IPv4 address of the externally
#                  accessible FTP server residing in the DMZ
# dmz_nntp_ad   -- the internal IPv4 address of the externally
#                  accessible NNTP server residing in the DMZ
#################################################################

dmz_domain_ad  = "192.168.2.2/32"
dmz_www_ad     = "192.168.2.3/32"
dmz_smtp_ad    = "192.168.2.4/32"
dmz_ftp_ad     = "192.168.2.5/32"
dmz_nntp_ad    = "192.168.2.6/32"

#################################################################
# dmz_domain_pr -- protocols used by the DMZ DNS server
# dmz_www_pr    -- protocols used by the DMZ HTTP server 
# dmz_smtp_pr   -- protocols used by the DMZ SMTP server 
# dmz_ftp_pr    -- protocols used by the DMZ FTP server 
# dmz_nntp_pr   -- protocols used by the DMZ NNTP server 
#################################################################

dmz_domain_pr = "{tcp, udp}"
dmz_www_pr    = "{tcp}"
dmz_smtp_pr   = "{tcp}"
dmz_ftp_pr    = "{tcp}"
dmz_nntp_pr   = "{tcp}"

#################################################################
# dmz_domain_pt -- the port that DMZ DNS server is listening on
# dmz_www_pt    -- the port that DMZ HTTP server is listening on 
# dmz_smtp_pt   -- the port that DMZ SMTP server is listening on 
# dmz_ftp_pt    -- the port that DMZ FTP server is listening on 
# dmz_nntp_pt   -- the port that DMZ NNTP server is listening on 
#################################################################

dmz_domain_pt = "2053"
dmz_www_pt    = "8080"
dmz_smtp_pt   = "2025"
dmz_ftp_pt    = "2020"
dmz_nntp_pt   = "2119"

#################################################################
# options: "set"
#################################################################

set limit { frags 10000, states 10000 }
set loginterface $ext_if
set optimization default

#################################################################
# scrub rules: "scrub"
#################################################################

scrub in  all fragment reassemble
scrub out all fragment reassemble

#################################################################
# NAT rules: "rdr", "nat", "binat"
#################################################################

nat on $ext_if from $prv_ad to any -> $ext_ad
nat on $ext_if from $dmz_ad to any -> $ext_ad

#################################################################
# establish redirection rules for the hosts on the external,
# private, and DMZ networks
#################################################################

rdr on $ext_if proto $dmz_domain_pr from any \
 to $ext_ad port domain -> $dmz_domain_ad port $dmz_domain_pt

rdr on {$ext_if, $prv_if} proto $dmz_www_pr from any \
 to $ext_ad port www    -> $dmz_www_ad port $dmz_www_pt

rdr on {$ext_if, $prv_if} proto $dmz_smtp_pr from any \
 to $ext_ad port smtp   -> $dmz_smtp_ad port $dmz_smtp_pt

rdr on {$ext_if, $prv_if} proto $dmz_ftp_pr from any \
 to $ext_ad port ftp    -> $dmz_ftp_ad port $dmz_ftp_pt

rdr on $prv_if proto $dmz_nntp_pr from any \
 to $ext_ad port nntp   -> $dmz_nntp_ad port $dmz_nntp_pt

These rules are for a network with two segments: one private network with hosts not accessible from the outside, and one DMZ network with hosts partially accessible from the outside world and from the private network.

Rolling Your Own NAT Rules

The DMZ network segment is only needed when you plan to make some services available to hosts outside of your network. You will have to adapt the rules shown earlier to your own network setup. The following guide should help you in that respect:

If you have limited resources, you could run multiple servers on the same machine residing in the DMZ network segment, but that increases the odds of someone finding a vulnerability, so you must weigh the odds for yourself.

Because the list shown above is rather lengthy, I will list the rules that you do not need if you are not offering any services to the public and have just one private network segment behind the firewall:

Things get more complicated when you have multiple external interfaces or multiple IP addresses assigned to the same interface. In such cases, you will be dealing with a more complex set of rules, and it is difficult to give a single answer that fits all possible questions. For example, if you have two external IP addresses, you can assign them to the external interface, using one for NAT of the private network addresses and one for NAT of the DMZ network addresses. In such case, you need to replace ext_ad with:

ext1_ad = "x.x.x.x/32"
ext2_ad = "y.y.y.y/32"

then, replace

nat on $ext_if from $prv_ad to any -> $ext_ad
nat on $ext_if from $dmz_ad to any -> $ext_ad

with

nat on $ext_if from $prv_ad to any -> $ext1_ad 
nat on $ext_if from $dmz_ad to any -> $ext2_ad

And, finally, replace:

rdr on $ext_if proto $dmz_domain_pr from any \
 to $ext_ad port domain -> $dmz_domain_ad port $dmz_domain_pt

rdr on {$ext_if, $prv_if} proto $dmz_www_pr from any \
 to $ext_ad port www    -> $dmz_www_ad port $dmz_www_pt

rdr on {$ext_if, $prv_if} proto $dmz_smtp_pr from any \
 to $ext_ad port smtp   -> $dmz_smtp_ad port $dmz_smtp_pt

rdr on {$ext_if, $prv_if} proto $dmz_ftp_pr from any \
 to $ext_ad port ftp    -> $dmz_ftp_ad port $dmz_ftp_pt

rdr on $prv_if proto $dmz_nntp_pr from any \
 to $ext_ad port nntp   -> $dmz_nntp_ad port $dmz_nntp_pt

with

rdr on $ext_if proto $dmz_domain_pr from any \
 to $ext2_ad port domain -> $dmz_domain_ad port $dmz_domain_pt

rdr on {$ext_if, $prv_if} proto $dmz_www_pr from any \
 to $ext2_ad port www    -> $dmz_www_ad port $dmz_www_pt

rdr on {$ext_if, $prv_if} proto $dmz_smtp_pr from any \
 to $ext2_ad port smtp   -> $dmz_smtp_ad port $dmz_smtp_pt

rdr on {$ext_if, $prv_if} proto $dmz_ftp_pr from any \
 to $ext2_ad port ftp    -> $dmz_ftp_ad port $dmz_ftp_pt

rdr on $prv_if proto $dmz_nntp_pr from any \
 to $ext2_ad port nntp   -> $dmz_nntp_ad port $dmz_nntp_pt

What if you have two external interfaces? Assuming that you have one IP address assigned to one external interface, the changes to the original ruleset would be as follows. Replace:

ext_if = "ne1"

with

ext1_if = "ne1"
ext2_if = "ne4"

(where the actual interface names will differ), and

ext_ad = "x.x.x.x/32"

with

ext1_ad = "x.x.x.x/32"
ext2_ad = "y.y.y.y/32"

If your external IP addresses are dynamically allocated by your ISP, delete these lines and use $ext1_if/32 and $ext2_if/32 instead of $ext1_ad and $ext2_ad shown below. Next, change:

nat on $ext_if from $prv_ad to any -> $ext_ad
nat on $ext_if from $dmz_ad to any -> $ext_ad

to

nat on $ext1_if from $prv_ad to any -> $ext1_ad
nat on $ext2_if from $dmz_ad to any -> $ext2_ad

And, finally, replace:

rdr on $ext_if proto $dmz_domain_pr from any \
 to $ext_ad port domain -> $dmz_domain_ad port $dmz_domain_pt

rdr on {$ext_if, $prv_if} proto $dmz_www_pr from any \
 to $ext_ad port www    -> $dmz_www_ad port $dmz_www_pt

rdr on {$ext_if, $prv_if} proto $dmz_smtp_pr from any \
 to $ext_ad port smtp   -> $dmz_smtp_ad port $dmz_smtp_pt

rdr on {$ext_if, $prv_if} proto $dmz_ftp_pr from any \
 to $ext_ad port ftp    -> $dmz_ftp_ad port $dmz_ftp_pt

rdr on $prv_if proto $dmz_nntp_pr from any \
 to $ext_ad port nntp   -> $dmz_nntp_ad port $dmz_nntp_pt

with

rdr on $ext_if proto $dmz_domain_pr from any \
 to $ext2_ad port domain -> $dmz_domain_ad port $dmz_domain_pt

rdr on {$ext_if, $prv_if} proto $dmz_www_pr from any \
 to $ext2_ad port www    -> $dmz_www_ad port $dmz_www_pt

rdr on {$ext_if, $prv_if} proto $dmz_smtp_pr from any \
 to $ext2_ad port smtp   -> $dmz_smtp_ad port $dmz_smtp_pt

rdr on {$ext_if, $prv_if} proto $dmz_ftp_pr from any \
 to $ext2_ad port ftp    -> $dmz_ftp_ad port $dmz_ftp_pt

rdr on $prv_if proto $dmz_nntp_pr from any \
 to $ext2_ad port nntp   -> $dmz_nntp_ad port $dmz_nntp_pt

While you might be tempted to add another ADSL to your network to increase throughput, you should not fall into thinking that this is an easy way to implement load balancing. It doesn't work that way -- if you were thinking of connecting a dozen DSL modems to your firewall to increase bandwidth, you can forget it. To make a long story short, you need to talk to your ISP and see if they can help. Having said that, if you use two DSL modems you can separate traffic generated by hosts in the private network segment from the traffic generated by external hosts connecting to the DMZ network. This is not true load balancing, but it can help, in some cases.

The nat rules that work so well when there are only two network segments, DMZ and private, can be a problem when you add more private network segments that ought to communicate freely with each other. The rdr rules that do such a fine job in simpler setups cannot be used in this case. There is a simpler solution: add no nat rules before nat rules for each private network pair, e.g.:

no nat on $ext_if from $prv_ad to $prv2_ad
no nat on $ext_if from $prv2_ad to $prv_ad

nat on $ext_if from $prv_ad to any  -> $ext_ad
nat on $ext_if from $prv2_ad to any -> $ext_ad
nat on $ext_if from $dmz_ad to any  -> $ext_ad

Well, that's is for today. Next time we'll look at pf(4) filtering rules.

Until next time.

Jacek Artymiak started his adventure with computers in 1986 with Sinclair ZX Spectrum. He's been using various commercial and Open Source Unix systems since 1991. Today, Jacek runs devGuide.net, writes and teaches about Open Source software and security, and tries to make things happen.


Read more Securing Small Networks with OpenBSD columns.

Return to the BSD DevCenter.


Copyright © 2009 O'Reilly Media, Inc.