NAT with pf
Subject:   Incorrect Information in this Article
Date:   2003-12-14 22:38:36
From:   anonymous2
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:

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.


Full Threads Oldest First

Showing messages 1 through 4 of 4.

  • Jacek Artymiak photo Incorrect Information in this Article
    2003-12-15 12:06:55  Jacek Artymiak | [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.

    • Thank you, Jacek
      2003-12-15 15:18:19  anonymous2 [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) ($ext)

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

      nat on $ext proto $tcp from to any -> $ext

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

      Any external host connects to, 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 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, which comes in on $int. Packet looks like this:

      Src:; Dest=

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

      Src:; Dest

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

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

      nat on $int proto tcp from 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, so that Host3 thinks it's talking to 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.

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

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

          What would be the pass in pass out statements to finish up that redirection?