NAT allows an administrator to take advantage of the reserved address blocks described in RFC 1918. Typically, the internal network will be set up to use one or more of these network blocks:
10.0.0.0/8 (10.0.0.0 - 10.255.255.255) 172.16.0.0/12 (172.16.0.0 - 172.31.255.255) 192.168.0.0/16 (192.168.0.0 - 192.168.255.255)An OpenBSD system doing NAT will have at least two network interfaces: one to the internet and the other for the internal network. NAT will be translating requests from the internal network so they appear to all be coming from the OpenBSD NAT system.
When the packets pass through the NAT gateway, they will be modified so that they appear to be coming from the NAT gateway itself. The NAT gateway will record the changes it makes in its state table so that it can reverse the changes on return packets and ensure that return packets are passed through the firewall and are not blocked. For example, the following changes might be made:
When the internet host replies to the internal machine's packets, they will be addressed to the NAT gateway's external IP at the translation port. The NAT gateway will then search the state table to determine if the reply packets match an already established connection. A unique match will be found based on the IP/port combination which tells PF the packets belong to a connection initiated by the internal machine. PF will then make the opposite changes it made to the outgoing packets and forward the reply packets on to the internal machine.
Translation of ICMP packets happens in a similar fashion but without the source port modification.
# sysctl net.inet.ip.forwarding=1 # echo 'net.inet.ip.forwarding=1' >> /etc/sysctl.confOr, for IPv6:
# sysctl net.inet6.ip6.forwarding=1 # echo 'net.inet6.ip6.forwarding=1' >> /etc/sysctl.conf
nat-to parameter to an outbound
pass rule.
Often, rather than being set directly on the pass rule, a
match rule is used.
When a packet is selected by a match rule, parameters (e.g.
nat-to) in that rule are remembered and are applied to the packet
when a pass rule matching the packet is reached.
This permits a whole class of packets to be handled by a single
match rule and then specific decisions on whether to allow the
traffic can be made with block and pass rules.
The general format in pf.conf looks something like this:
match out on interface [af] \ from src_addr to dst_addr \ nat-to ext_addr [pool_type] [static-port] [...] pass out [log] on interface [af] [proto protocol] \ from ext_addr [port src_port] \ to dst_addr [port dst_port]
match
match
rule, any optional parameters specified in that rule are remembered
for future use (made "sticky").
match rule applies a network address translation
(nat-to, etc), any subsequent rules will compare
against the new translated address, not the original address.
pass
match rule where
parameters were specified, they will be applied to this packet.
pass rules may have their own parameters; these take
priority over parameters specified in a match rule.
out
nat-to may only be specified for outbound packets.
log
log (all).
interface
af
inet for IPv4 or inet6
for IPv6.
PF is usually able to determine this parameter based on the
source/destination address(es).
protocol
src_addr
/netmask (e.g. /24).
Each IP address on the interface is combined with the netmask to
form a CIDR network block which is substituted into the rule.
:network - substitutes the CIDR network block (e.g.,
192.168.0.0/24)
:broadcast - substitutes the network broadcast
address (e.g., 192.168.0.255)
:peer - substitutes the peer's IP address on a
point-to-point link
In addition, the :0 modifier can be appended to either
an interface name/group or to any of the above modifiers to
indicate that PF should not include aliased IP addresses in the
substitution.
These modifiers can also be used when the interface is contained in
parentheses.
Example: fxp0:network:0
! ("not")
modifier.
pass from !192.0.2.1 and pass from !192.0.2.2
is equivalent to just pass.
any meaning all addresses
src_port
!= (not equal)
< (less than)
> (greater than)
<= (less than or equal)
>= (greater than or equal)
>< (range)
<> (inverse range)
The last two are binary operators (they take two arguments) and do not include the arguments in the range.
: (inclusive range)
The inclusive range operator is also a binary operator and does include the arguments in the range.
port option is not usually used in nat rules
because the goal is usually to NAT all traffic regardless of the port(s) being
used.
dst_addr
dst_port
ext_addr
( ).
This tells PF to update the rule if the IP address(es) on the named
interface changes.
This is highly useful when the external interface gets its IP address
via DHCP or dial-up, as the ruleset doesn't have to be reloaded each
time the address changes.
:network - substitutes the CIDR network block (e.g.,
192.168.0.0/24)
:peer - substitutes the peer's IP address on a
point-to-point link
In addition, the :0 modifier can be appended to either
an interface name/group or to any of the above modifiers to
indicate that PF should not include aliased IP addresses in the
substitution.
These modifiers can also be used when the interface is contained in
parentheses.
Example: fxp0:network:0
pool_type
static-port
This would lead to a most basic form of these lines similar to this:
pass out on tl0 from 192.168.1.0/24 to any nat-to 198.51.100.1This rule says to perform NAT on the
tl0 interface for any
packets coming from 192.168.1.0/24 and to replace the source IP address
with 198.51.100.1.
A more advanced form might take care of address translation first (possibly much earlier in the ruleset), and then pass permitted traffic:
# using "tag" match out inet from 192.168.1.0/24 to any nat-to 198.51.100.1 tag natted pass out on tl0 to any tagged natted # or using "received-on" match out inet from dc0:network to any nat-to 195.51.100.1 pass out on tl0 received-on dc0Rather than hard-coding addresses, it is also possible to specify interface names (and optional modifiers) instead:
match out inet from dc0:network to any nat-to tl0 pass out on tl0 received-on dc0The advantage should be fairly clear: the IP addresses of either interface can be changed without changing this rule. For
nat-to, you will usually want to avoid translating IPv6
traffic: inet will restrict to only matching IPv4 traffic,
avoiding unexpected surprises.
When specifying an interface name for the translation address as above, the IP address is determined at pf.conf load time, not on the fly. If DHCP or PPP is being used to configure the external interface, this can be a problem: if the assigned IP address changes, NAT will continue translating outgoing packets using the old IP address. This will cause outgoing connections to stop functioning. To get around this, PF can automatically update the translation address by putting parentheses around the interface name:
match out inet from dc0:network to any nat-to (tl0) pass out on tl0 received-on dc0The downside is that it incurs a slight overhead of looking up the current address for each new connection.
binat-to parameter.
A binat-to rule establishes a one-to-one mapping between an
internal IP address and an external address.
This can be useful, for example, to provide a web server on the internal
network with its own external IP address.
Connections from the internet to the external address will be translated to
the internal address and connections from the web server (such as DNS requests)
will be translated to the external address.
TCP and UDP ports are never modified with binat-to rules as they
are with nat rules.
Example:
web_serv_int = "192.168.1.100" web_serv_ext = "198.51.100.6" pass on tl0 from $web_serv_int to any binat-to $web_serv_ext
-s state option.
This option will list all the current NAT sessions:
# pfctl -s state all tcp 192.168.1.35:2132 (198.51.100.1:53136) -> 198.51.100.10:22 TIME_WAIT:TIME_WAIT all udp 192.168.1.35:2491 (198.51.100.1:60527) -> 198.51.100.33:53 MULTIPLE:SINGLEExplanations (first line only):
if-bound, otherwise the word all will
appear if the state is floating (the default).