NPF.CONF(5)

NAME
npf.conf - NPF packet filter configuration file

DESCRIPTION
npf.conf is the default configuration file for the NPF packet filter.

This manual page serves as a reference for editing npf.conf. Please refer to the official NPF documentation website for comprehensive and in-depth information.

There are multiple structural elements that npf.conf may contain, such as:

•   variables
•   table definitions (with or without content)
•   abstraction groups
•   packet filtering rules
•   map rules for address translation
•   application level gateways
•   procedure definitions to call on filtered packets
•   parameter settings.

SYNTAX

  Variables
Variables are specified using the dollar (‘$’) sign, which is used for both definition and referencing of a variable. Variables are defined by assigning a value to them as follows:

    $var1 = 10.0.0.1

A variable may also be defined as a set:

    $var2 = {
        10.0.0.1,   # First host
        10.0.0.2,   # Second host
    }

Newlines within curly braces are ignored, and trailing commas are optional. Common variable definitions are for IP addresses, networks, ports, and interfaces.

  Tables
Tables are specified using a name between angle brackets ‘<’ and ‘>’. The following is an example of table definition:

    table <blocklist> type ipset

Currently, tables support three data storage types: ipset, lpm, or const. The contents of the table may be pre-loaded from the specified file. The const tables are immutable (no insertions or deletions after loading) and therefore must always be loaded from a file.

The specified file should contain a list of IP addresses and/or networks in the form of 10.1.1.1 or 10.0.0.0/24.

Tables of type ipset and const can only contain IP addresses (without masks). The lpm tables can contain networks and they will perform the longest prefix match on lookup.

  Interfaces
In NPF, an interface can be referenced directly by using its name, or can be passed to an extraction function which will return a list of IP addresses configured on the actual associated interface.

It is legal to pass an extracted list from an interface in keywords where NPF would expect instead a direct reference to said interface. In this case, NPF infers a direct reference to the interface, and does not consider the list.

There are two types of IP address lists. With a static list, NPF will capture the interface addresses on configuration load, whereas with a dynamic list NPF will capture the runtime list of addresses, reflecting any changes to the interface, including the attach and detach. Note that with a dynamic list, bringing the interface down has no effect, all addresses will remain present.

Three functions exist, to extract addresses from an interface with a chosen list type and IP address type:

inet4(interface)
    Static list. IPv4 addresses.

inet6(interface)
    Static list. IPv6 addresses.

ifaddrs(interface)
    Dynamic list. Both IPv4 and IPv6. The family keyword of a filtering rule can be used in combination to explicitly select an IP address type. This function can also be used with map to specify the translation address, see below.

Example of configuration:

    $var1 = inet4(wm0)
    $var2 = ifaddrs(wm0)
    group default {
    	block in on wm0 all               # rule 1
    	block in on $var1 all             # rule 2
    	block in on inet4(wm0) all        # rule 3
    	pass in on inet6(wm0) from $var2  # rule 4
    	pass in on wm0 from ifaddrs(wm0)  # rule 5
    }

In the above example, $var1 is the static list of IPv4 addresses configured on wm0, and $var2 is the dynamic list of all the IPv4 and IPv6 addresses configured on wm0. The first three rules are equivalent, because with the block ... on <interface> syntax, NPF expects a direct reference to an interface, and therefore does not consider the extraction functions. The fourth and fifth rules are equivalent, for the same reason.

  Groups
NPF requires that all rules be defined within groups. Groups can be thought of as higher level rules which can contain subrules. Groups may have the following options: name, interface, and direction. Packets matching group criteria are passed to the ruleset of that group. If a packet does not match any group, it is passed to the default group. The default group must always be defined.

Example of configuration:

    group "my-name" in on wm0 {
    	# List of rules, for packets received on wm0
    }
    group default {
    	# List of rules, for the other packets
    }

  Rules
With a rule statement NPF is instructed to pass or block a packet depending on packet header information, transit direction and the interface it arrived on, either immediately upon match or using the last match.

If a packet matches a rule which has the final option set, this rule is considered the last matching rule, and evaluation of subsequent rules is skipped. Otherwise, the last matching rule is used.

The proto keyword can be used to filter packets by layer 4 protocol (TCP, UDP, ICMP or other). Its parameter should be a protocol number or its symbolic name, as specified in the /etc/protocols file. This keyword can additionally have protocol-specific options, such as flags.

The flags keyword can be used to match the packets against specific TCP flags, according to the following syntax:

    proto tcp flags match[/mask]

Where match is the set of TCP flags to be matched, out of the mask set, both sets being represented as a string combination of: ‘S’ (SYN), ‘A’ (ACK), ‘F’ (FIN), and ‘R’ (RST). The flags that are not present in mask are ignored.

For ICMP, the icmp or ipv6-icmp keyword is used based on the address family of the packet being filtered. Its parameter should be the ICMP type and or the ICMP code. The ICMP type is indicated using the icmp-type keyword followed by the name or number representing the ICMP type. The ICMP code is also indicated using the code keyword followed by the name or number of the code of the ICMP type.

    proto icmp icmp-type icmp_type code icmp_code

To notify the sender of a blocking decision, three return options can be used in conjunction with a block rule:

return
    Behaves as return-rst or return-icmp, depending on whether the packet being blocked is TCP or UDP.

return-rst
    Return a TCP RST message, when the packet being blocked is a TCP packet. Applies to IPv4 and IPv6.

return-icmp
    Return an ICMP UNREACHABLE message, when the packet being blocked is a UDP packet. Applies to IPv4 and IPv6.

The from and to keywords are provided to filter by source or destination IP addresses. They can be used in conjunction with the port keyword. Negation ( the exclamation mark, ‘’!) can be used in front of the address filter criteria.

Further packet specification at present is limited to TCP and UDP understanding source and destination ports, and ICMP and IPv6-ICMP understanding ICMP type.

A rule can also instruct NPF to create an entry in the state table when passing the packet or to apply a procedure to the packet (e.g., "log").

A “fully-featured” rule would for example be:

    pass stateful in final family inet4 proto tcp flags S/SA \
            from $source port $sport to $dest port $dport    \
            apply someproc

Alternatively, NPF supports pcap-filter(7) syntax, for example:

    block out final pcap-filter tcp and dst 10.1.1.252

Fragments are not selectable since NPF always reassembles packets before further processing.

  Layer 2 filtering
NPF allows the filtering of frames at the data link layer. NPF also requires that the inspection rules are defined within groups. Groups containing rules to filter frames should be marked with a layer-2 label. If layer 2 groups are defined in your NPF configuration, then a layer-2 default group becomes mandatory.

Example of configuration:

    group "my-name" in on wm0 layer-2 {
    	# List of rules, for frames received on wm0
    }
    group default layer-2 {
    	# List of rules, for the other frames
    }

Rules for filtering at the data link layer are configured based on the interface name, direction, source and destination MAC addresses, and EtherType. Rules that are defined for the link layer should pass the ether keyword after the pass or block instruction. EtherType is passed on the rule by preceeding the four digit hexadecimal constant EtherType with ‘Ex’. When a frame matches a rule with the final keyword, the rule is considered the last matching rule.

A “fully-featured” rule would for example be:

    pass ether in final from $src_mac to $dest_mac type $ether_type

Example of rule configuration:

    pass ether from 00:00:5E:00:53:00 to 00:00:5E:00:53:01 type Ex0800

This passes frames with source MAC address 00:00:5E:00:53:00 and destination MAC address 00:00:5E:00:53:01 carrying IP packets (0800). Alternatively, layer 2 rules also support variables.

    block ether in final from $source_mac to $dest_mac type $ether_type

Filtering at this layer is stateless and has no access to upper layer protocols. Block returns are not supported. MAC address tables are also not supported yet.

  User/group ID filtering
NPF allows filtering by user or group identity. Packet filtering by user or group controls data packet flows based on the user or group identity of the process that generated the traffic, or is waiting to receive traffic, rather than just traditional parameters like IP address, port number, and protocol.

There are many situations where this is useful:

Finer-grained access control.
    One can allow specific destinations to be accessed only by certain users or groups.

Application level security.
    Two processes are using a specific port, but only one should be allowed to access packets originating from a particular host.

Improves isolation in multi-tenant systems
    Prevent an untrusted user from making any network connections.

Security hardening and containment.
    A user application that has been exploited can be prevented from making network connections to a command-and-control server.

Compliance and policy enforcement.
    Can restrict access to particular networks to network administrators only.

This filtering process can be achieved by passing the user or group ID on the rule.

    pass out from all user jack group < 1000

The above rule only allows sockets of processes owned by user jack and belonging to a group with an ID value of less than 1000.

    block in from all user > 100 group wheel

The above rule prevents all listening sockets bound by processes owned by any user with the ID value greater than 100 and belonging to the wheel group.

A rule can have either a user ID or group ID set. If both are set, both must agree to be a match to the socket involved in communication. Numbers or names can be used for the identification of the user or group as they still resolve to a numeric ID of the user or group.

  Stateful
NPF supports stateful packet inspection which can be used to bypass unnecessary rule processing as well as to complement NAT. The connection state is uniquely identified by an n-tuple: IP version, layer 4 protocol, source and destination IP addresses and port numbers. Each state is represented by two keys: one for the original flow and one for the reverse flow, so that the reverse lookup on the returning packets would succeed. The packets are matched against the connection direction respectively.

Depending on the settings (see the section on state.key in the npf-params(7) manual), the connection identifier (keys) may also include the interface ID, making the states per-interface.

Stateful packet inspection is enabled using the stateful or stateful-all keywords. The former matches the interface after the state lookup, while the latter avoids matching the interface (assuming the state.key.interface parameter is disabled), i.e. making the state global, and must be used with caution. In both cases, a full TCP state tracking is performed for TCP connections and a limited tracking for message-based protocols (UDP and ICMP).

By default, a stateful rule implies SYN-only flag check (‘flags S/SAFR’) for the TCP packets. It is not advisable to change this behavior; however, it can be overridden with the aforementioned flags keyword.

  Map
Network Address Translation (NAT) is expressed in a form of segment mapping. The translation may be dynamic (stateful) or static (stateless). The following mapping types are available:

->  outbound NAT (translation of the source)
<-  inbound NAT (translation of the destination)
<-> bi-directional NAT (combination of inbound and outbound NAT)

The following would translate the source (10.1.1.0/24) to the IP address specified by $pub_ip for the packets on the interface $ext_if.

    map $ext_if dynamic 10.1.1.0/24 -> $pub_ip

Translations are implicitly filtered by limiting the operation to the network segments specified, that is, translation would be performed only on packets originating from the 10.1.1.0/24 network. Explicit filter criteria can be specified using pass criteria ... as an additional option of the mapping.

The dynamic NAT implies network address and port translation (NAPT). The port translation can be controlled explicitly. For example, the following provides “port forwarding”, redirecting the public port 9022 to the port 22 of an internal host:

    map $ext_if dynamic proto tcp 10.1.1.2 port 22 <- $ext_if port 9022

In the regular dynamic NAT case, it is also possible to disable port translation using the no-ports flag.

The translation address can also be dynamic, based on the interface. The following would select the IPv4 address(es) currently assigned to the interface:

    map $ext_if dynamic 10.1.1.0/24 -> ifaddrs($ext_if)

If the dynamic NAT is configured with multiple translation addresses, then a custom selection algorithm can be chosen using the algo keyword. The currently available algorithms for the dynamic translation are:

ip-hash
    The translation address for a new connection is selected based on a hash of the original source and destination addresses. This algorithms attempts to keep all connections of particular client associated with the same translation address. This is the default algorithm.

round-robin
    The translation address for each new connection is selected on a round-robin basis.

netmap
    See the description below.

The static NAT can also have different address translation algorithms, chosen using the algo keyword. The currently available algorithms are:

netmap
    Network address mapping from one segment to another, leaving the host part as-is. The new address is computed as following:

        addr = net-addr | (orig-addr & ~mask)

npt66
    IPv6-to-IPv6 network prefix translation (NPTv6).

If no algorithm is specified, then 1:1 address mapping is assumed. Currently, the static NAT algorithms do not perform port translation.

  Application Level Gateways
Certain application layer protocols are not compatible with NAT and require translation outside layers 3 and 4. Such translation is performed by packet filter extensions called Application Level Gateways (ALGs).

NPF supports the following ALGs:

icmp
    ICMP ALG. Applies to IPv4 and IPv6. Allows to find an active connection by looking at the ICMP payload, and to perform NAT translation of the ICMP payload. Generally, this ALG is necessary to support traceroute(8) behind the NAT, when using the UDP or TCP probes.

The ALGs are built-in. If NPF is used as kernel module, then they come as kernel modules too. In such case, the ALG kernel modules can be autoloaded through the configuration, using the alg keyword.

For example:

    alg icmp

Alternatively, the ALG kernel modules can be loaded manually, using modload(8).

  Procedures
A rule procedure is defined as a collection of extension calls (it may have none). Every extension call has a name and a list of options in the form of key-value pairs. Depending on the call, the key might represent the argument and the value might be optional. Available options:

log: interface
    Log events. This requires the npf_ext_log kernel module, which would normally get auto-loaded by NPF. The specified npflog interface would also be auto-created once the configuration is loaded. The log packets can be written to a file using the npfd(8) daemon.

normalize: option1[, option2 ...]
    Modify packets according to the specified normalization options. This requires the npf_ext_normalize kernel module, which would normally get auto-loaded by NPF.

The available normalization options are:

max-mss value
    Enforce a maximum value for the Maximum Segment Size (MSS) TCP option. Typically, for “MSS clamping”.

min-ttl value
    Enforce a minimum value for the IPv4 Time To Live (TTL) parameter.

no-df
    Remove the Don't Fragment (DF) flag from IPv4 packets.

random-id
    Randomize the IPv4 ID parameter.

routing: interface
    This is used to route packets via the defined interface. Packets that take this route bypass the kernel's routing table.

For example:

    procedure "someproc" {
    	log: npflog0
    	normalize: "random-id", "min-ttl" 64, "max-mss" 1432
    	route: "bge0"
    }

In this case, the procedure calls the logging, normalization and routing modules.

  Parameter settings
NPF supports a set of dynamically tunable configuration-wide parameters. For example:

    set state.tcp.timeout.time_wait 0  # destroy the state immediately

See npf-params(7) for the list of parameters and their details.

  Misc
Text after a hash (‘#’) character is considered a comment. The backslash (‘\’) character at the end of a line marks a continuation line, i.e., the next line is considered an extension of the present line. Additionally, within curly braces of variable definitions, newlines are allowed without continuation characters.

GRAMMAR
The following is a non-formal BNF-like definition of the grammar. The definition is simplified and is intended to be human readable, therefore it does not strictly represent the formal grammar.

    # Syntax of a single line.  Lines can be separated by LF (\n) or
    # a semicolon.  Comments start with a hash (#) character.
    syntax		= var-def | set-param | alg | table-def |
    		  map | group | proc | comment
    # Variable definition.  Names can be alpha-numeric, including "_"
    # character.
    var-name	= "$" . string
    interface	= interface-name | var-name
    var-def		= var "=" ( var-value | "{" value *[ "," value ] [ "," ] "}" )
    # Parameter setting.
    set-param	= "set" param-value
    # Application level gateway.  The name should be in double quotes.
    alg		= "alg" alg-name
    alg-name	= "icmp"
    # Table definition.  Table ID shall be numeric.  Path is in the
    # double quotes.
    table-id	= <table-name>
    table-def	= "table" table-id "type" ( "ipset" | "lpm" | "const" )
    		  [ "file" path ]
    # Mapping for address translation.
    map		= map-common | map-ruleset
    map-common	= "map" interface
    		  ( "static" [ "algo" map-algo ] | "dynamic" )
    		  [ map-flags ] [ proto ]
    		  map-seg ( "->" | "<-" | "<->" ) map-seg
    		  [ "pass" [ proto ] filt-opts ]
    map-ruleset	= "map" "ruleset" group-opts
    map-algo	= "ip-hash" | "round-robin" | "netmap" | "npt66"
    map-flags	= "no-ports"
    map-seg		= ( addr-mask | interface ) [ port-opts ]
    # Rule procedure definition.  The name should be in the double quotes.
    #
    # Each call can have its own options in a form of key-value pairs.
    # Both key and values may be strings (either in double quotes or not)
    # and numbers, depending on the extension.
    proc		= "procedure" proc-name "{" *( proc-call [ new-line ] ) "}"
    proc-opts	= key [ " " val ] [ "," proc-opts ]
    proc-call	= call-name ":" proc-opts new-line
    # Group definition and the rule list.
    group		= "group" ( "default" | group-opts ) "{" rule-list "}"
    group-opts	= name-string [ "in" | "out" ] [ "on" interface ]
    rule-list	= [ rule new-line ] rule-list
    npf-filter	= [ "family" family-opt ] [ proto ] ( "all" | filt-opts )
    static-rule	= ( "block" [ block-opts ] | "pass" )
    		  [ "stateful" | "stateful-all" ]
    		  [ "in" | "out" ] [ "final" ] [ "on" interface ]
    		  ( npf-filter | "pcap-filter" pcap-filter-expr )
    		  [ "apply" proc-name ]
    dynamic-ruleset	= "ruleset" group-opts
    rule		= static-rule | dynamic-ruleset
    tcp-flag-mask	= tcp-flags
    tcp-flags	= [ "S" ] [ "A" ] [ "F" ] [ "R" ]
    block-opts	= "return-rst" | "return-icmp" | "return"
    family-opt	= "inet4" | "inet6"
    proto-opts	= "flags" tcp-flags [ "/" tcp-flag-mask ] |
    		  "icmp-type" type [ "code" icmp-code ]
    proto		= "proto" protocol [ proto-opts ]
    filt-opts	= "from" filt-addr [ port-opts ] "to" filt-addr [ port-opts ] user_id group_id
    filt-addr	= [ "!" ] [ interface | addr-mask | table-id | "any" ]
    port-opts	= "port" ( port-num | port-from "-" port-to | var-name )
    addr-mask	= addr [ "/" mask ]
    user_id		= "user" id_items
    group_id	= "group" id_items
    id_items	= [id] | [op_unary id] | [id op_binary id]
    op_unary	= ["="] | ["!="] | ["<="] | [">="] | [">"] | ["<"]
    op_binary	= ["<>"] | ["><"]

FILES
/dev/npf
    control device
/etc/npf.conf
    default configuration file
/usr/share/examples/npf
    directory containing further examples

EXAMPLES

    $ext_if = { inet4(wm0) }
    $int_if = { inet4(wm1) }
    table <blocklist> type ipset file "/etc/npf_blocklist"
    table <limited> type lpm
    $services_tcp = {
    	http,    # Web traffic
    	https,   # Secure web traffic
    	smtp,    # Email sending
    	domain,  # DNS queries
    	6000,    # Custom service
    	9022,    # SSH forwarding
    }
    $services_udp = { domain, ntp, 6000, }
    $localnet = { 10.1.1.0/24 }
    alg "icmp"
    # These NAT rules will dynamically select the interface address(es).
    map $ext_if dynamic 10.1.1.0/24 -> ifaddrs($ext_if)
    map $ext_if dynamic proto tcp 10.1.1.2 port 22 <- ifaddrs($ext_if) port 9022
    procedure "log" {
    	# The logging facility can be used together with npfd(8).
    	log: npflog0
    }
    group "external" on $ext_if {
    	pass stateful out final all
    	block in final from <blocklist>
    	pass stateful in final family inet4 proto tcp to $ext_if \
    		port ssh apply "log"
    	pass stateful in final proto tcp to $ext_if \
    		port $services_tcp
    	pass stateful in final proto udp to $ext_if \
    		port $services_udp
    	pass stateful in final proto tcp to $ext_if \
    		port 49151-65535  # passive FTP
    	pass stateful in final proto udp to $ext_if \
    		port 33434-33600  # traceroute
    }
    group "internal" on $int_if {
    	block in all
    	block in final from <limited>
    	# Ingress filtering as per BCP 38 / RFC 2827.
    	pass in final from $localnet
    	pass out final all
    }
    group default {
    	pass final on lo0 all
    	block all
    }

SEE ALSO
bpf(4), npf(7), npf-params(7), pcap-filter(7), npfctl(8), npfd(8)

NPF documentation website: http://rmind.github.io/npf/

HISTORY
NPF first appeared in NetBSD 6.0.

AUTHORS
NPF was designed and implemented by Mindaugas Rasiukevicius.