BSD DevCenter

oreilly.comSafari Books Online.Conferences.

We've expanded our LAMP news coverage and improved our search! Search for all things LAMP across O'Reilly!

Search
Search Tips

advertisement

Listen Print Discuss Subscribe to BSD Subscribe to Newsletters

NAT with pf
Pages: 1, 2, 3, 4

NAT rules

There are three kinds of NAT rules:



  • rdr: port redirection
  • nat: translation between groups of internal addresses and a single external address
  • binat: bidirectional translation between one internal address and one external address

While experimenting with your NAT setup, the following commands will make your life a little easier:

  • load only NAT rules:

    $ sudo pfctl -N -f /etc/pf.conf
  • show loaded NAT rules:

    $ sudo pfctl -s nat
  • flush NAT rules:

    $ sudo pfctl -F nat

rdr

The rdr rules redirect traffic from one port to another. A classic example of using traffic redirection is the case of an HTTP server hidden in a DMZ, yet accessible to hosts outside your network. Ordinarily, such a server must listen on a privileged port 80, and the machine it runs on must be directly accessible to external hosts. This setup is not very safe, so you might consider moving the server behind a firewall, into a DMZ network segment. However, if you do that, the server is inaccessible, because it is the firewall which receives requests for the HTTP server. The firewall must now redirect these packets to the web server residing inside the DMZ network segment. This is accomplished with the following rule:

#################################################################
# macro definitions

ext_if = "ne2"
ext_ad = "f.f.f.f/32"
dmz_ad = "w.w.w.w/32"

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

rdr on $ext_if proto tcp from any to $ext_ad port 80 -> 
   $dmz_ad port 8080

The above rule redirects all TCP (proto tcp) packets arriving at the firewall's external address (on $ext_if), originating from any source address (from any) and destined to the HTTP server, listening on port 80 (to $ext_ad port 80) to the network interface located in the DMZ (-> $dmz_ad). The server listens on port 8080 (port 8080). That port is unprivileged, and the attacker has less chance of breaking things, should the server be compromised.

As you will soon discover, this rule works for connections made from the outside to your web server, but not from your private network. This is solved by adding another rule:

#################################################################
# macro definitions

ext_if = "ne2"
prv_if = "ne1"
ext_ad = "f.f.f.f/32"
prv_ad = "f.f.f.f/24"
dmz_ad = "w.w.w.w/32"

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

rdr on $ext_if proto tcp from any to $ext_ad port 80 -> 
   $dmz_ad port 8080
rdr on $prv_if proto tcp from $prv_ad to $ext_ad port 80 -> 
   $dmz_ad port 8080

You can rewrite it in the following way (notice that I put interface names and addresses in curly braces):

#################################################################
# macro definitions

rdr_ifs = "{ ne2, ne1 }" 
rdr_ads = "{ any, p.p.p.p/24 }" 
ext_ad = "f.f.f.f/32"
dmz_ad = "w.w.w.w/32"

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

rdr on $rdr_ifs proto tcp from $rdr_ads to $ext_ad port 80 
   -> $dmz_ad port 8080

You can use curly braces to list interface names, protocol names, and addresses in rdr rules. You can also replace port numbers with their names, e.g. port www and port 80 are equivalent. These names and numbers can be found in /etc/services.

The above rule could be tightened a little. For example, if you want to redirect only IPv4 packets, add the inet keyword:

#################################################################
# macro definitions

rdr_ifs = "{ ne2, ne1 }" 
rdr_ads = "{ any, p.p.p.p/24 }" 
ext_ad = "f.f.f.f/32"
dmz_ad = "w.w.w.w/32"

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

rdr on $rdr_ifs inet proto tcp from $rdr_ads to $ext_ad port 80 
   -> $dmz_ad port 8080

Similarly, to redirect only IPv6 packets, use the inet6 keyword.

What if you wanted to redirect all queries to port 80 on all addresses to a web cache? Return to an earlier setup with two separate rules and change the second rule:

#################################################################
# macro definitions

ext_if = "ne2"
prv_if = "ne1"
ext_ad = "f.f.f.f/32"
prv_ad = "f.f.f.f/24"
www_ad = "w.w.w.w/32"
cch_ad = "c.c.c.c/32"

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

rdr on $ext_if proto tcp from any to $ext_ad port 80 
   -> $www_ad port 8080
rdr on $prv_if proto tcp from $prv_ad to any port 80 
   -> $cch_ad port 1080

In the example above, the web cache listens on port 1080. Note that this technique of forcing everyone on the internal network to connect to the Web through the cache server is controversial, and you must not impose it on your users without careful thought. For more information consult [Wessels 2001].

Web Caching

Related Reading

Web Caching
By Duane Wessels

What if you want to bypass the cache yourself? Use the no modifier, as in:

#################################################################
# macro definitions

ext_if = "ne2"
prv_if = "ne1"
ext_ad = "f.f.f.f/32"
prv_ad = "p.p.p.p/24"
bos_ad = "p.p.p.b/24"
www_ad = "w.w.w.w/32"
cch_ad = "c.c.c.c/32"

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

rdr on $ext_if proto tcp from any to $ext_ad port 80 -> $www_ad port 8080
rdr on $prv_if proto tcp from $prv_ad to any port 80 -> $cch_ad port 1080
no rdr on $prv_if proto tcp from $bos_ad to any port 80

As you can see, the no modifier makes the -> ... part of the rule unnecessary (and such rules do not parse, as they do not make sense).

Another modifier is !, which negates the values (interface names, source and target addresses) it precedes:

rdr on ! ne1 inet proto tcp from ! s.s.s.s/32 to ! 
   e.e.e.e/32 port 80 -> d.d.d.d/32 port 8080

The above rule redirects all IPv4 TCP packets arriving on any interface except ne1 from any address except s.s.s.s/32 and destined to any address except e.e.e.e/32.

The rdr rules are very handy because they can be used to configure proxies, redirect traffic from a dead host to a backup host. and so on. Recently, rdr rules have been used to fight spam. If a suspicious host tries to connect to the smtp port on your firewall, its request will be directed to a special program that keeps it waiting forever for a connection confirmation; then, just when the spammer's MTA thinks it will be able to send mail it receives an error message and the connection closes. Such delays of several minutes seriously slow spammers and are a good way to make their life harder. OpenBSD 3.3 is supposed to include some interesting tools for fighting spam.

nat

NAT rules perform network address translation for groups of internal hosts, with private addresses hidden behind a firewall, which access the outside world through a single interface with one public IP. (The external interface could have more IP addresses assigned to it, but let's focus on the most severe case.) This not only solves the problem of connecting more than one host through a single interface, but it also hides details of your internal network's layout, the number of hosts, and other information that an intruder may find useful.

The magic is possible because the firewall keeps a record of who sent what and where, so it can send replies to the right host. To do that it must keep a table of sorts and mark packets it sends to the Internet. This marking allows attackers to deduct how many hosts are hidden behind the firewall and gives them an idea of what might be hiding behind your firewall, provided they can capture that traffic. It is also used by companies selling DSL access to the Internet to find out who's breaching their contracts. (Some DSL access providers forbid their customers to use NAT, and impose penalties on those who use it. If your provider does this, consider switching to another.) The latest versions of pf(4) can fool these detection systems, but you need to be running OpenBSD-current, which is still experimental. We'll look at what -current has to offer in part 4.

How do you connect your private network to the outside world? It's quite simple, actually:

#################################################################
# macro definitions

ext_if = "ne1" 
ext_ad = "f.f.f.f/32"
prv_ads = "p.p.p.p/24" 
nat_p = "{tcp, udp, icmp}"

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

nat on $ext_if proto $nat_p from $prv_ads to any -> $ext_ad

When it is time to add a new network segment, modify the macros:

#################################################################
# macro definitions

ext_if = "ne1" 
ext_ad = "f.f.f.f/32"
prv_ads = "{ p.p.p.p/24, d.d.d.d/24 }" 
nat_p = "{tcp, udp, icmp}"

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

nat on $ext_if proto $nat_p from $prv_ads to any -> $ext_ad

Shouldn't we use the names of the interfaces that connect our private networks to the firewall? No, address translation is done on the external interface.

Just like rdr rules, nat rules allow us to use the no and ! modifiers before interface names and private host addresses. It is also possible to limit their scope to IPv4 or IPv6 packets (inet and inet6, respectively). The pf.conf(5) man page has more detailed information about using intricate modifiers like binary or unary operators.

binat

The last of the three NAT rules are binat rules, which bind an external public address to an internal private address. VPN setups use this bidirectional translation, and it can provide additional security for hosts exposing public services. These rules are similar to rdr rule, but they do not allow such fine degrees of control. While the following rule set works with rdr rules, it is not possible with binat rules.

rdr on $ext_if proto tcp from any to $ext_ad port 22 -> 192.168.1.1 port 1022 
rdr on $ext_if proto tcp from any to $ext_ad port 25 -> 192.168.1.2 port 1025 
rdr on $ext_if proto tcp from any to $ext_ad port 53 -> 192.168.1.3 port 1053
rdr on $ext_if proto tcp from any to $ext_ad port 80 -> 192.168.1.4 port 8080

Compare it with binat rules:

binat on $ext_if proto tcp from 192.168.1.37 to any -> $ext_ad_1
binat on $ext_if proto tcp from 192.168.1.38 to any -> $ext_ad_2
binat on $ext_if proto tcp from 192.168.1.54 to any -> $ext_ad_3

As you can see, every internal address must have its own equivalent external address. They can all be bound to the same external interface, though. If you want to know more, consult the ifconfig(8) man page (look for information about aliases).

Again, no and ! modifiers are allowed, as are address class modifiers (inet and inet6).

Always remember that NAT rules do not filter traffic. They redirect it. There must be another rule that filters out traffic redirected to another interface or port. The sender of the original packet does not know anything about what goes on behind the firewall. All it knows is that the packet has reached its destination at $ext_ad. The next installment of this series will cover filtering.

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.


Have a question about NAT? Ask Jacek here.
You must be logged in to the O'Reilly Network to post a talkback.
Post Comment
Full Threads Oldest First

Showing messages 1 through 19 of 19.

  • nat syntax
    2004-03-09 11:39:51  gwung [Reply | View]

    a for dummies', more detailed and defined explaination please:

    nat on $ext_if proto $nat_p from $prv_ads to any -> $ext_ad

    Thanks!
  • nic setup
    2004-03-09 11:11:17  gwung [Reply | View]

    how would the NICs be configured using ifconfig? how about routing?
  • dmz
    2004-01-10 00:10:41  anonymous2 [Reply | View]

    Hello.
    The question is simple...if i want to make a DMZ with a 3rd netcard and i dont want any kind of packet filtering (like no firewall at all) for that DMZ net...with only the NAT rule line is enough?
    Thx in advance
  • Incorrect Information in this Article
    2003-12-14 22:38:36  anonymous2 [Reply | View]

    The information posted about NAT rdr is incorrect. The author says that, in order to allow connections from internal interfaces, all that is needed is to do a rdr from the internal interface as well as from the external interface.

    However, this is incorrect, and only results in the packet going one way. The return packet is discarded, however, because it comes from the wrong ip:

    http://www.openbsd.org/faq/pf/rdr.html

    On this page, it explains how to deal with this, and it is not trivial, although the correct lines are included here. The relevant paragraph that describes the issue is here, as well:

    **************
    Adding a second redirection rule for the internal interface does not have the desired effect either. When the local client connects to the external address of the firewall, the initial packet of the TCP handshake reaches the firewall through the internal interface. The redirection rule does apply and the destination address gets replaced with that of the internal server. The packet gets forwarded back through the internal interface and reaches the internal server. But the source address has not been translated, and still contains the local client's address, so the server sends its replies directly to the client. The firewall never sees the reply and has no chance to properly reverse the translation. The client receives a reply from a source it never expected and drops it. The TCP handshake then fails and no connection can be established.
    ************

    This is an issue that is a pain to deal with with ipfw (FreeBSD), as well, although I have fixed the issue there, too. I was ecstatic when I read that simply doing a rdr from the internal interface would work on pf without additional tweaking. However, it doesn't, and this article is misleading (unintentionally, I'm sure). I hope you will correct this error to save someone else the trouble of finding this out the hard way.

    -Dan
    • Jacek Artymiak photo Incorrect Information in this Article
      2003-12-15 12:06:55  Jacek Artymiak | [Reply | View]

      It would help, if you described your LAN configuration and what exactly you are trying to achieve in a little more detail. What works in one case, may not work in another. If you do that, I'll try to help.

      --Jacek
      • Thank you, Jacek
        2003-12-15 15:18:19  anonymous2 [Reply | View]

        Thanks for the offer, although I already have it working. I will explain it for other people's benefit, though.

        I want to rdr certain ports to certain internal hosts using pf, at the same time as using NAT on the external interface. The rdr rules work fine for this, but as you noted, internal hosts cannot connect to the external ip address with the single rdr rule. So you add a rdr rule on the internal NIC, right? Wrong. That's what that quote from the pf documentation is about--such a method doesn't work.

        3 Hosts, let's give them real IPs just to be clear:

        Gateway ($int) 10.0.0.1 ($ext) 1.2.3.4
        Host2 10.0.0.2
        Host3 10.0.0.3

        We do NAT on ext, and redirect port 5555 to Host2:

        nat on $ext proto $tcp from 10.0.0.0/24 to any -> $ext

        rdr on $ext proto tcp from any to $ext port 5555 -> Host2 port 5555

        Any external host connects to 1.2.3.4, the port is forwarded using the rdr rule. Host2 sees the forward, responds to the external host, and everything works.

        But what if Host3 connects to 1.2.3.4? It doesn't work, so we add another rdr rule (as you suggested):

        rdr on $int proto tcp from any to $ext port 5555 -> Host2 port 5555

        Host3 sends a packet to 1.2.3.4, which comes in on $int. Packet looks like this:

        Src: 10.0.0.3; Dest=1.2.3.4

        Then, pf redirects the packet to 10.0.0.2. So far, so good. Host2 responds, with a packet like this:

        Src: 10.0.0.2; Dest 10.0.0.3

        Host3 sees this packet and throws it away. Host3 says, essentially "I never sent anything to 10.0.0.3--why is it responding?" The rdr rule will never work because the wrong host responds to Host3's request to connect to 1.2.3.4.

        The solution from OpenBSD's pf page is to add a NAT rule that changes the source ip:

        nat on $int proto tcp from 10.0.0.0/24 to Host2 port 5555 -> $int

        What this does is make the initial packet appear to come from $int. Host2 then responds to $int, where pf's NAT will translate the source ip back to 1.2.3.4, so that Host3 thinks it's talking to 1.2.3.4. As the page notes, this is not exactly an elegant solution, but it works (they add another rule to tighten it a bit).

        I don't believe simply adding an rdr as you did in the article works, although I may have misunderstood your configuration. The internal rdr does forward the port, but the responding packet will be dropped as it comes from the wrong host. NAT similar to the external interfaces is needed in order to make the internal host believe it is talking to the external ip.

        -Dan
        • That "$tcp" should be "tcp"
          2003-12-15 15:19:29  anonymous2 [Reply | View]

          • That "$tcp" should be "tcp"
            2005-03-27 22:54:02  2MuchRiceMakesMeSick [Reply | View]

            What would be the pass in pass out statements to finish up that redirection?
  • sysctl.conf, not rc.conf
    2003-08-17 01:10:14  anonymous2 [Reply | View]

    Shouldn't the example for activating ip.forwarding refer to sysctl.conf rather than rc.conf?
    • Jacek Artymiak photo sysctl.conf, not rc.conf
      2003-12-15 12:12:07  Jacek Artymiak | [Reply | View]

      Yes, it should. Mea culpa.

      --Jacek
  • I don't understand
    2003-08-14 03:36:33  postleb [Reply | View]

    Hi,
    great articel.
    But when it comes to the interesting part on page 4, redirections you are loosing me.
    Just right away in the first example:
    rdr on $ext_if proto tcp from any to $ext_ad port 80 ->
    $dmz_ad port 8080

    Why are we going on $ext_ad and what does this macro stand for?

    I don't understand macro definitions:
    xt_ad = "f.f.f.f/32"
    prv_ad = "f.f.f.f/24"
    dmz_ad = "w.w.w.w/32"

    What do they stand for in "real" IP adresses?
    Which article did I miss to catch up?
    I thought start reading the first articel can't be wrong :-)
    Pls be patient with me as I'm not a native english speaker. Learning new stuff in a foreign language is double though.

    Greetings
    Frank
    • Jacek Artymiak photo I don't understand
      2003-12-15 12:19:22  Jacek Artymiak | [Reply | View]

      ext_ad is the external address of your firewall's external interface, i.e. the address that other host on the Internet can see and connect to;

      prv_ad is the address of your firewall's interface to which you connect private hosts; that address may or may not be accessible from the outside;

      dmz_ad is the address of your firewalls's interface to which you conect hosts in the DeMilitarized Zone (a separate segment of your private network that is accessible to the outside world.

      --Jacek
  • Nuff Said
    2003-08-07 13:14:39  anonymous2 [Reply | View]

    I used your four part article for basic introduction to opebsd packet filtering. Since we are using 3.2 this article just brought it home... Good job.
  • On the article
    2003-08-06 10:04:14  anonymous2 [Reply | View]

    This is a lovely article. Looking forward to your further writings. Pedja
  • mistype
    2003-06-05 06:07:32  anonymous2 [Reply | View]

    Just wanted to mention there's a typo "logininterface" should be "loginterface" page 2

    Great site by the way
  • Great article!
    2003-04-09 04:19:58  anonymous2 [Reply | View]

    Great article! I'd love to see one on altq... *hint hint* ;)
    • Jacek Artymiak photo Great article!
      2003-04-09 07:43:52  Jacek Artymiak | [Reply | View]

      Thanks! Working on altq already!
  • Typo in article - logininterface
    2003-03-10 03:32:29  anonymous2 [Reply | View]

    On page 2 (of the otherwise excellent article) there is a typo:
    # ex. 1: collect statistics on ne1
    set logininterface $ext_if

    It should ofcourse say "loginterface" instead of "logininterface". Do you think you could correct it?

    /Johan Torin
    jtorin@myrealbox.com
    • chromatic  photo Typo in article - logininterface
      2003-06-05 09:08:17  chromatic | O'Reilly AuthorO'Reilly Blogger [Reply | View]

      Oops. Thanks, it's fixed now.


Sponsored Resources

  • Inside Lightroom
Advertisement

Sponsored by:

O'Reilly Media

©2009, O'Reilly Media, Inc.
(707) 827-7000 / (800) 998-9938
All trademarks and registered trademarks appearing on oreilly.com are the property of their respective owners.
About O'Reilly
Academic Solutions
Authors
Contacts
Customer Service
Jobs
Newsletters
O'Reilly Labs
Press Room
Privacy Policy
RSS Feeds
Terms of Service
User Groups
Writing for O'Reilly
Content Archive
Business Technology
Computer Technology
Google
Microsoft
Mobile
Network
Operating System
Digital Photography
Programming
Software
Web
Web Design
More O'Reilly Sites
O'Reilly Radar
Ignite
Tools of Change for Publishing
Digital Media
Inside iPhone
O'Reilly FYI
makezine.com
craftzine.com
hackszine.com
perl.com
xml.com

Partner Sites
InsideRIA
java.net
O'Reilly Insights on Forbes.com