OpenBSD Handbook

    Theme
    • Part I. Install & Configure
      • Introduction to OpenBSD
      • Installing OpenBSD
      • The X Window System
      • Networking on OpenBSD
      • System Configuration
      • OpenBSD Basics
      • Managing Software: Packages and Ports
    • Part II. Daily Operations
      • OpenBSD Desktop Environments
      • Multimedia
      • Printing
      • Linux Compatibility
      • Windows Compatibility
      • Games
    • Part III. System Administration
      • Virtualization
      • Storage and File Systems
      • Updating and Upgrading
      • Localization
      • The OpenBSD Boot Process
      • Security
    • Part IV. Networking & Daemons
      • Services
        • Database
          • MariaDB
          • PostgreSQL
          • Redis
          • memcached
        • Directory
          • YP (NIS)
          • LDAP
        • File
          • NFS
          • Samba
        • FTP Services
          • ftpd
          • ProFTPD
          • vsftpd
          • Pure-FTPd
          • TFTP
        • Mail
          • OpenSMTPD Mail Server
          • Dovecot IMAP and POP3 Server
          • Postfix
          • Exim
          • Rspamd
        • Name
          • Unbound DNS Resolver
          • NSD Authoritative DNS Server
          • BIND DNS Server
        • Networking
          • OpenBGPD
          • rad
          • DHCP on OpenBSD
          • slaacd
        • Web
          • Apache HTTP Server
          • nginx Web Server
          • OpenBSD httpd Web Server
          • relayd Reverse Proxy
        • Logging
          • syslogd
        • Monitoring
          • SNMP
        • Remote Access
          • Audit OpenSSH
          • OpenSSH Server
        • File Synchronization
          • rsync
        • Messaging
          • RabbitMQ Message Broker
        • Time
          • NTP Time Synchronization
      • PF
        • pfctl cheat sheet
        • PF Anchors
        • PF Filter Rules
        • PF Forwarding
        • PF Lists and Macros
        • PF Load Balancing
        • PF Logging
        • PF NAT
        • PF Options
        • PF Policies
        • PF Shortcuts
        • PF Tables
      • Advanced Networking
        • High Availability and State Replication
        • Multi-WAN and Policy-Based Routing
        • VPN and Cryptographic Tunneling
        • Classic and Lightweight Tunnels
        • IPv6 at Scale
        • QoS and Traffic Shaping
        • MPLS and Label Distribution
        • Network Services at Scale
        • Virtualization and Host Networking
        • Large-Scale L2 and L3 Design
        • Telemetry, Logging, and Flow Export
        • Hardening and Operational Safety
        • Reference Architectures
        • Troubleshooting Playbooks
      • Serial Communication
    • Part V. Miscellaneous
      • Virtualization Cheat Sheet
      • OpenBSD Cheatsheet
      • How-to Guides
        • Installation and First Boot
          • Complete the First 30 Minutes After Installation
          • Check Hardware Compatibility Before Installing
          • Bootstrap Wi-Fi Firmware Without Ethernet
          • Run OpenBSD as a Virtual Machine Guest
          • Install OpenBSD on a VPS or Cloud Server
          • Install OpenBSD with Full-Disk Encryption
          • Dual-Boot OpenBSD on a UEFI System
          • Automate OpenBSD Installation with Autoinstall and PXE
        • Desktops and Laptops
          • Set Up OpenBSD as a Laptop or Workstation
          • Configure Laptop Power Management
          • Troubleshoot Web Browsers on OpenBSD
          • Use Plan 9 Tools on OpenBSD
          • Build a Minimal Desktop with suckless Tools
          • Install Z shell (zsh)
        • Networking and Remote Access
          • Create an Administrator Account with doas and SSH Keys
          • Configure Secure Remote Access with OpenSSH
          • Use FIDO Security Keys with OpenSSH
          • Configure Wi-Fi Roaming and Wired Failover
          • Troubleshoot PF, NAT, and Routing
          • Route Multiple VLANs with PF
          • Configure a Road-Warrior WireGuard VPN
          • Route IPv6 Networks without NAT
          • Test OpenBGPD with Two Providers
          • Unlock an Internal Service with an SSH Key
          • Build a Simple Router and Firewall
        • Servers and Services
          • Set Up WordPress on OpenBSD
          • Build a Mail Server with OpenSMTPD, Dovecot, and Rspamd
        • Security, Maintenance and Recovery
          • Establish a Conservative Security Baseline
          • Recover Access and Reset the Root Password
          • Collect OpenBSD Diagnostic Evidence
          • Troubleshoot Package Installation and Update Failures
          • Schedule Recurring Maintenance
          • Monitor an OpenBSD System
          • Back Up and Restore an OpenBSD System
          • Configure softraid RAID1
          • Recover an OpenBSD System That Does Not Boot
        • Building, Automation and Virtual Machines
          • Manage OpenBSD with Ansible
          • Build Third-Party Software from Ports
          • Build OpenBSD from Source
          • Plan a Docker Workload Migration to OpenBSD
      • OpenBSD for Linux Users
      • OpenBSD for FreeBSD Users
      • OpenBSD for macOS Users
    • OpenBSD FAQ
    • OpenBSD Architectures
      • OpenBSD Package Search
        • Introduction
        • NAT Address Pool
        • Load Balance Incoming Connections
        • Load Balance Outgoing Traffic

        PF Load Balancing

        Introduction #

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

        There are five methods for using an address pool:

        • ‘bitmask’ - grafts the network portion of the pool address over top of the address that is being modified (source address for ’nat-to’ rules, destination address for ‘rdr-to’ rules). Example: if the address pool is 192.0.2.1/24 and the address being modified is 10.0.0.50, then the resulting address will be 192.0.2.50. If the address pool is 192.0.2.1/25 and the address being modified is 10.0.0.130, then the resulting address will be 192.0.2.2.
        • ’least-states’ - selects the address with the fewest active states, taking configured weights into account.
        • ‘random’ - randomly selects an address from the pool.
        • ‘source-hash’ - uses a hash of the source address to determine which address to use from the pool. This method ensures that a given source address is always mapped to the same pool address. The key that is fed to the hashing algorithm can optionally be specified after the ‘source-hash’ keyword in hex format or as a string. By default, pfctl will generate a random key every time the ruleset is loaded.
        • ‘round-robin’ - loops through the address pool in sequence. This method also accepts address lists and tables .

        A single CIDR block can supply a pool of addresses. Multiple addresses and tables are also supported, but cannot use bitmask. The sticky-address option is available with least-states, random, round-robin, and source-hash; it retains a source-to-pool association while states refer to it. See pf.conf(5) for pool weights and source tracking timeouts.

        NAT Address Pool #

        An address pool can be used as the translation address in ’nat-to’ 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.

        match out on egress inet nat-to { 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. This example uses a CIDR pool.

        match out on egress inet nat-to 192.0.2.4/31 source-hash
        

        This 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 }"
        
        match in on egress proto tcp to port 80 rdr-to $web_servers \
            round-robin sticky-address
        

        Successive connections will be redirected to the web servers in a round-robin manner with connections from the same source being sent to the same web server. This “sticky connection” will exist as long as there are states that refer to this connection. Once the states expire, so will the sticky connection. Further connections from that host will be redirected to the next web server in the round robin.

        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 = "198.51.100.100"
        ext_gw2 = "203.0.113.200"
        
        pass in on $int_if from $lan_net route-to \
           { $ext_gw1, $ext_gw2 } round-robin
        

        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 (it cannot be used with ‘match’ rules).

        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 from $ext_if2 route-to $ext_gw2
        pass out on $ext_if2 from $ext_if1 route-to $ext_gw1
        

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

        match out on $ext_if1 from $lan_net nat-to ($ext_if1)
        match out on $ext_if2 from $lan_net nat-to ($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 = "198.51.100.100"
        ext_gw2 = "203.0.113.200"
        
        #  nat outgoing connections on each internet interface
        match out on $ext_if1 from $lan_net nat-to ($ext_if1)
        match out on $ext_if2 from $lan_net nat-to ($ext_if2)
        
        #  default deny
        block in
        block out
        
        #  pass all outgoing packets on internal interface
        pass out on $int_if 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 traffic from internal network.
        pass in on $int_if from $lan_net \
            route-to { $ext_gw1, $ext_gw2 } \
            round-robin
        #  keep https traffic on a single connection; some web applications,
        #  especially "secure" ones, don't allow it to change mid-session
        pass in on $int_if proto tcp from $lan_net to port https \
            route-to $ext_gw1
        
        #  general "pass out" rules for external interfaces
        pass out on $ext_if1
        pass out on $ext_if2
        
        #  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 from $ext_if2 route-to $ext_gw2
        pass out on $ext_if2 from $ext_if1 route-to $ext_gw1
        
        Report a bug
        • Introduction
        • NAT Address Pool
        • Load Balance Incoming Connections
        • Load Balance Outgoing Traffic