[Previous: Network Address Translation] [Contents] [Next: Shortcuts For Creating Rulesets]
Let's look at an example:
pass in on tl0 proto tcp from any to any port 80 rdr-to 192.168.1.20
This line redirects TCP port 80 (web server) traffic to a machine inside the network at 192.168.1.20. So, even though 192.168.1.20 is behind your gateway and inside your network, the outside world can access it.
The from any to any part of the above rdr line can be quite useful. If you know what addresses or subnets are supposed to have access to the web server at port 80, you can restrict them here:
pass in on tl0 proto tcp from 27.146.49.0/24 to any port 80 \
rdr-to 192.168.1.20
This will redirect only the specified subnet. Note this implies you can redirect different incoming hosts to different machines behind the gateway. This can be quite useful. For example, you could have users at remote sites access their own desktop computers using the same port and IP address on the gateway as long as you know the IP address they will be connecting from:
pass in on tl0 proto tcp from 27.146.49.14 to any port 80 \
rdr-to 192.168.1.20
pass in on tl0 proto tcp from 16.114.4.89 to any port 80 \
rdr-to 192.168.1.22
pass in on tl0 proto tcp from 24.2.74.178 to any port 80 \
rdr-to 192.168.1.23
A range of ports can also be redirected within the same rule:
pass in on tl0 proto tcp from any to any port 5000:5500 \
rdr-to 192.168.1.20
pass in on tl0 proto tcp from any to any port 5000:5500 \
rdr-to 192.168.1.20 port 6000
pass in on tl0 proto tcp from any to any port 5000:5500 \
rdr-to 192.168.1.20 port 7000:*
These examples show ports 5000 to 5500 inclusive being redirected to 192.168.1.20. In rule #1, port 5000 is redirected to 5000, 5001 to 5001, etc. In rule #2, the entire port range is redirected to port 6000. And in rule #3, port 5000 is redirected to 7000, 5001 to 7001, etc.
These risks can be minimized by keeping the externally accessed system tightly confined on a separate network. This network is often referred to as a Demilitarized Zone (DMZ) or a Private Service Network (PSN). This way, if the web server is compromised, the effects can be limited to the DMZ/PSN network by careful filtering of the traffic permitted to and from the DMZ/PSN.
server = 192.168.1.40
pass in on $ext_if proto tcp from any to $ext_if port 80 \
rdr-to $server port 80
But when the redirection rule is tested from a client on the LAN, it doesn't work. The reason is that redirection rules apply only to packets that pass through the specified interface ($ext_if, the external interface, in the example). Connecting to the external address of the firewall from a host on the LAN, however, does not mean the packets will actually pass through its external interface. The TCP/IP stack on the firewall compares the destination address of incoming packets with its own addresses and aliases and detects connections to itself as soon as they have passed the internal interface. Such packets do not physically pass through the external interface, and the stack does not simulate such a passage in any way. Thus, PF never sees these packets on the external interface, and the redirection rule, specifying the external interface, does not apply.
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.
Still, it's often desirable for clients on the LAN to connect to the same internal server as external clients and to do so transparently. There are several solutions for this problem:
It's possible to configure DNS servers to answer queries from local hosts differently than external queries so that local clients will receive the internal server's address during name resolution. They will then connect directly to the local server, and the firewall isn't involved at all. This reduces local traffic since packets don't have to be sent through the firewall.
Adding an additional network interface to the firewall and moving the local server from the client's network into a dedicated network (DMZ) allows redirecting of connections from local clients in the same way as the redirection of external connections. Use of separate networks has several advantages, including improving security by isolating the server from the remaining local hosts. Should the server (which in our case is reachable from the Internet) ever become compromised, it can't access other local hosts directly as all connections have to pass through the firewall.
A generic TCP proxy can be setup on the firewall, either listening on the port to be forwarded or getting connections on the internal interface redirected to the port it's listening on. When a local client connects to the firewall, the proxy accepts the connection, establishes a second connection to the internal server, and forwards data between those two connections.
Simple proxies can be created using inetd(8) and nc(1). The following /etc/inetd.conf entry creates a listening socket bound to the loopback address (127.0.0.1) and port 5000. Connections are forwarded to port 80 on server 192.168.1.10. The forwarding is done by user "proxy".
127.0.0.1:5000 stream tcp nowait proxy /usr/bin/nc nc -w \
20 192.168.1.10 80
The following redirection rule forwards port 80 on the internal interface to the proxy:
pass in on $int_if proto tcp from $int_net to $ext_if port 80 \High-performance proxies may also be created with relayd(8).
rdr-to 127.0.0.1 port 5000
With an additional NAT rule on the internal interface, the lacking source address translation described above can be achieved.
pass in on $int_if proto tcp from $int_net to $ext_if port 80 \
rdr-to $server
pass out on $int_if proto tcp to $server port 80 \
received-on $int_if nat-to $int_if
This will cause the initial packet from the client to be translated again when it's forwarded back through the internal interface, replacing the client's source address with the firewall's internal address. The internal server will reply back to the firewall, which can reverse both NAT and RDR translations when forwarding to the local client. This construct is rather complex as it creates two separate states for each reflected connection. Care must be taken to prevent the NAT rule from applying to other traffic, for instance connections originating from external hosts (through other redirections) or the firewall itself. Note that the rdr-to rule above will cause the TCP/IP stack to see packets arriving on the internal interface with a destination address inside the internal network.
[Previous: Network Address Translation] [Contents] [Next: Shortcuts For Creating Rulesets]