[OpenBSD]

[Previous: Packet Queueing and Prioritization] [Contents] [Next: Packet Tagging]

PF: Address Pools and Load Balancing


Table of Contents


Introduction

An address pool is a supply of two or more addresses whose use is shared among a group of users. An address pool can be specified as the redirection address in rdr rules, as the translation address in nat rules, and as the target address in route-to, reply-to, and dup-to filter options.

There are four methods for using an address pool:

Except for the round-robin method, the address pool must be expressed as a CIDR (Classless Inter-Domain Routing) network block. The round-robin method will accept multiple individual addresses using a list or table.

NAT Address Pool

An address pool can be used as the translation address in nat rules. Connections will have their source address translated to an address from the pool based on the method chosen. This can be useful in situations where PF is performing NAT for a very large network. Since the number of NATed connections per translation address is limited, adding additional translation addresses will allow the NAT gateway to scale to serve a larger number of users.

In this example a pool of two addresses is being used to translate outgoing packets. For each outgoing connection PF will rotate through the addresses in a round-robin manner.

nat on $ext_if inet from any to any -> { 192.0.2.5, 192.0.2.10 }

One drawback with this method is that successive connections from the same internal address will not always be translated to the same translation address. This can cause interference, for example, when browsing websites that track user logins based on IP address. An alternate approach is to use the source-hash method so that each internal address is always translated to the same translation address. To do this, the address pool must be a CIDR network block.

nat on $ext_if inet from any to any -> 192.0.2.4/31 source-hash

This nat rule uses the address pool 192.0.2.4/31 (192.0.2.4 - 192.0.2.5) as the translation address for outgoing packets. Each internal address will always be translated to the same translation address because of the source-hash keyword.

Load Balance Incoming Connections

Address pools can also be used to load balance incoming connections. For example, incoming web server connections can be distributed across a web server farm:
web_servers = "{ 10.0.0.10, 10.0.0.11, 10.0.0.13 }"

rdr on $ext_if proto tcp from any to any port 80 -> $web_servers

Successive connections will be redirected to the web servers in a round-robin manner.

As with the NAT example, if the web servers are all placed within a CIDR network block, the source-hash keyword can be used so that connections from a given IP address are always redirected to the same physical web server. Again, this is sometimes necessary to maintain session information while browsing a website.

Load Balance Outgoing Traffic

Address pools can be used in combination with the route-to filter option to load balance two or more Internet connections when a proper multi-path routing protocol (like BGP4) is unavailable. By using route-to with a round-robin address pool, outbound connections can be evenly distributed among multiple outbound paths.

One additional piece of information that's needed to do this is the IP address of the adjacent router on each Internet connection. This is fed to the route-to option to control the destination of outgoing packets.

The following example balances outgoing traffic across two Internet connections:

lan_net = "192.168.0.0/24"
int_if  = "dc0"
ext_if1 = "fxp0"
ext_if2 = "fxp1"
ext_gw1 = "68.146.224.1"
ext_gw2 = "142.59.76.1"

pass in on $int_if route-to \
   { ($ext_if1 $ext_gw1), ($ext_if2 $ext_gw2) } round-robin \
   from $lan_net to any keep state

The route-to option is used on traffic coming in on the internal interface to specify the outgoing network interfaces that traffic will be balanced across along with their respective gateways. Note that the route-to option must be present on each filter rule that traffic is to be balanced for. Return packets will be routed back to the same external interface that they exited (this is done by the ISPs) and will be routed back to the internal network normally.

To ensure that packets with a source address belonging to $ext_if1 are always routed to $ext_gw1 (and similarly for $ext_if2 and $ext_gw2), the following two lines should be included in the ruleset:

pass out on $ext_if1 route-to ($ext_if2 $ext_gw2) from $ext_if2 \
   to any
pass out on $ext_if2 route-to ($ext_if1 $ext_gw1) from $ext_if1 \
   to any

Finally, NAT can also be used on each outgoing interface:

nat on $ext_if1 from $lan_net to any -> ($ext_if1)
nat on $ext_if2 from $lan_net to any -> ($ext_if2)

A complete example that load balances outgoing traffic might look something like this:

lan_net = "192.168.0.0/24"
int_if  = "dc0"
ext_if1 = "fxp0"
ext_if2 = "fxp1"
ext_gw1 = "68.146.224.1"
ext_gw2 = "142.59.76.1"

#  nat outgoing connections on each internet interface
nat on $ext_if1 from $lan_net to any -> ($ext_if1)
nat on $ext_if2 from $lan_net to any -> ($ext_if2)

#  default deny
block in  from any to any
block out from any to any

#  pass all outgoing packets on internal interface
pass out on $int_if from any to $lan_net
#  pass in quick any packets destined for the gateway itself
pass in quick on $int_if from $lan_net to $int_if
#  load balance outgoing tcp traffic from internal network. 
pass in on $int_if route-to \
    { ($ext_if1 $ext_gw1), ($ext_if2 $ext_gw2) } round-robin \
    proto tcp from $lan_net to any flags S/SA modulate state
#  load balance outgoing udp and icmp traffic from internal network
pass in on $int_if route-to \
    { ($ext_if1 $ext_gw1), ($ext_if2 $ext_gw2) } round-robin \
    proto { udp, icmp } from $lan_net to any keep state

#  general "pass out" rules for external interfaces
pass out on $ext_if1 proto tcp from any to any flags S/SA modulate state
pass out on $ext_if1 proto { udp, icmp } from any to any keep state
pass out on $ext_if2 proto tcp from any to any flags S/SA modulate state
pass out on $ext_if2 proto { udp, icmp } from any to any keep state

#  route packets from any IPs on $ext_if1 to $ext_gw1 and the same for
#  $ext_if2 and $ext_gw2
pass out on $ext_if1 route-to ($ext_if2 $ext_gw2) from $ext_if2 to any 
pass out on $ext_if2 route-to ($ext_if1 $ext_gw1) from $ext_if1 to any 

[Previous: Packet Queueing and Prioritization] [Contents] [Next: Packet Tagging]


[back] www@openbsd.org
$OpenBSD: pools.html,v 1.10 2004/01/01 04:16:17 nick Exp $