otonat: fast in-kernel 1:1 NAT for FreeBSD

Current version: 0.33 (2015-11-10)


otonat is a 1:1 NAT implementation for FreeBSD. Unlike existing solutions (ipfw, pf, ipnat) that keep state for individual user connections even for 1:1 NAT, otonat is completely stateless as far as user connections are concerned. This allows it to scale much better and provide protection against misbehaving clients that may consume lots of server memory by opening too many connections.


otonat consists of a loadable kernel module (otonat.ko) and a user-space control program (otonatctl) that communicate through a pseudo-device (/dev/otonat).


Download otonat-0.33.tar.gz
PGP signature


A simple "make install" in the root source directory is enough to install both the kernel module and the user-space control program in the appropriate locations.

The kernel module can be loaded manually after installation using:

# kldload otonat

Since otonat uses the pfil(9) framework, if other packet filters are used at the same time, then the order in which the modules are loaded becomes important (the first module to be loaded is the first to see outbound packets, and vice versa for inbound). In most cases, otonat should be loaded first.

A sample rc.d file is available to ensure that otonat is loaded before pf, and unloaded when called with a 'stop' parameter.


otonat operates using simple rules that are composed of:

The addresses must obviously be unique for a given interface, otherwise an error is generated. Only packets that pass through the given interface will be processed by the rule.

For outbound packets, otonat acts on the packet's source field and replaces the internal address with the external address. For inbound packets, otonat acts on the packet's destination field and replaces the external address with the internal address.

It is possible to circumvent otonat processing for certain destination IP addresses on outbound packets, or source IP addresses on inbound packets, respectively. Such exclusions may be added using separate commands (single IP addresses only) and are valid on all interfaces.

Adding a mapping:

# otonatctl -am -n em0 -i -e

Removing the same mapping:

# otonatctl -dm -n em0 -i

Listing mappings:

# otonatctl -lm
1 mappings: -> (em0)

Flushing all mappings:

# otonatctl -fm

Adding an exclusion:

# otonatctl -ax -e

Removing an exclusion:

# otonatctl -dx -e

Listing exclusions:

# otonatctl -lx

Flushing all exclusions:

# otonatctl -fx

Implementation details

otonat internally uses two hash tables that map internal to external and external to internal addresses, respectively. The default hash table size is 1024 entries, and the least significant bits of the address are used as the hash key as it is assumed that most deployments will use multiples of /24 blocks for the internal and external addresses.

Note that the hash table size does not limit the number of active rules; it should rarely be necessary to increase the size unless thousands of active rules are used.


otonat currently operates on IPv4 packets only.

otonat does not contain any ALGs and as such will not attempt to rewrite IP addresses in FTP control commands; therefore, active mode FTP clients behind otonat will not work (unless they have a feature to discover their public IP address). Passive FTP, as is the default in most clients, works fine.

Addresses in IP headers that are embedded in ICMP error messages are not translated.


otonat should work with any FreeBSD version >= 6.0, and has been tested under FreeBSD 6.2, 8.2 and 10.2.

Change log


otonat is placed under a BSD-style license. See the source code files for the full license text.


otonat was written by Manuel Kasper <mk@neon1.net> for Monzoon Networks AG.