IPF(5)
π NAME
ipf, ipf.conf β the IPFilter firewall rules file format, straight from the IPF(5) page in the File Formats Manual. iconic file-format behavior β¨
π‘οΈ DESCRIPTION
Okay so the ipf.conf file is where you specify the rules for the firewall, packet authentication, and packet accounting components of IPFilter. To actually load the rules you wrote in ipf.conf, you use the ipf(8) program. Periodt.
For use as a firewall, there are two important rule types, bestie: those that block and drop packets (block rules) and those that allow packets through (pass rules). Accompanying the decision to apply is a collection of statements that specify under what conditions the result is to be applied and how. Itβs giving βrules with loreβ π
The simplest rules that can be used in ipf.conf are expressed like this:
block in all
pass out all
Each rule must contain at least the following three components:
- a decision keyword (
pass,block, etc.) - the direction of the packet (
inorout) - address patterns or
allto match any address information
β¨ Long lines
For rules lines that are particularly long, it is possible to split them over multiple lines implicitly like this:
pass in on bgeo proto tcp from 1.1.1.1 port > 1000
to 2.2.2.2 port < 5000 flags S keep state
or explicitly using the backslash (\) character:
pass in on bgeo proto tcp from 1.1.1.1 port > 1000 \
to 2.2.2.2 port < 5000 flags S keep state
π Comments
Comments in the ipf.conf file are indicated by the use of the # character. This can either be at the start of the line, like this:
# Allow all ICMP packets in
pass in proto icmp from any to any
Or at the end of a line, like this:
pass in proto icmp from any to any # Allow all ICMP packets in
π₯ Firewall rules
This section goes into detail on how to construct firewall rules that are placed in the ipf.conf file.
It is beyond the scope of this document to describe what makes a good firewall rule set or which packets should be blocked or allowed in. Some suggestions will be provided but further reading is expected to fully understand what is safe and unsafe to allow in/out. No cap, this part is important.
π Filter rule keywords
The first word found in any filter rule describes what the eventual outcome of a packet that matches it will be. Descriptions of the many and various sections that can be used to match on the contents of packet headers will follow on below.
The complete list of keywords, along with what they do, is as follows:
passβ rules that match a packet indicate to ipfilter that it should be allowed to continue on in the direction it is flowing.blockβ rules are used when it is desirable to prevent a packet from going any further. Packets that are blocked on theinside are never seen by TCP/IP and those that are blocked goingoutare never seen on the wire.logβ when IPFilter successfully matches a packet against a log rule a log record is generated and made available foripmon(8)to read. These rules have no impact on whether or not a packet is allowed through or not. So if a packet first matched a block rule and then matched a log rule, the status of the packet after the log rule is that it will still be blocked.countβ rules provide the administrator with the ability to count packets and bytes that match the criteria laid out in the configuration file. The count rules are applied after NAT and filter rules on the inbound path. For outbound packets, count rules are applied before NAT and before the packet is dropped. Thus the count rule cannot be used as a true indicator of link layer (and yes, thatβs exactly where the original sentence stops β iconic cliffhanger, not me editing it π).authβ rules cause the matching packet to be queued up for processing by a user space program. The user space program is responsible for making anioctlsystem call to collect the information about the queued packet and anotherioctlsystem call to return the verdict (block, pass, etc) on what to do with the packet. In the event that the queue becomes full, the packets will end up being dropped.callβ provides access to functions built into IPFilter that allow for more complex actions to be taken as part of the decision making that goes with the rule.decapsulateβ rules instruct ipfilter to remove any other headers (IP, UDP, AH) and then process what is inside as a new packet. For non-UDP packets, there are builtin checks that are applied in addition to whatever is specified in the rule, to only allow decapsulation of recognised protocols. After decapsulating the inner packet, any filtering result that is applied to the inner packet is also applied to the other packet.
The default way in which filter rules are applied is for the last matching rule to be used as the decision maker. So even if the first rule to match a packet is a pass, if there is a later matching rule that is a block and no further rules match the packet, then it will be blocked. Vibe check: last matching rule wins, bestie.
π§ Matching Network Interfaces
On systems with more than one network interface, it is necessary to be able to specify different filter rules for each of them. In the first instance, this is because different networks will send us packets via each network interface but it is also because of the hosts, the role and the resulting security policy that we need to be able to distinguish which network interface a packet is on.
To accommodate systems where the presence of a network interface is dynamic, it is not necessary for the network interface named in a filter rule to be present in the system when the rule is loaded. This can lead to silent errors being introduced and unexpected behaviour with the simplest of keyboard mistakes β for example, typing in hem0 instead of hme0 or hme2 instead of hme3. Not the typo jumpscare π
On Solaris systems prior to Solaris 10 Update 4, it is not possible to filter packets on the loopback interface (lo0) so filter rules that specify it will have no impact on the corresponding flow of packets. See below for Solaris specific tips on how to enable this.
Some examples of including the network interface in filter rules are:
block in on bge0 all
pass out on bge0 all
π Address matching (basic)
The first and most basic part of matching for filtering rules is to specify IP addresses and TCP/UDP port numbers. The source address information is matched by the from information in a filter rule and the destination address information is matched with the to information in a filter rule.
The typical format used for IP addresses is CIDR notation, where an IP address (or network) is followed by a / and a number representing the size of the netmask in bits. This notation is used for specifying address matching in both IPv4 and IPv6. If the / and bitmask size are excluded from the matching string, it is assumed that the address specified is a host address and that the netmask applied should be all 1βs.
Some examples of this are:
pass in from 10.1.0.0/24 to any
block out from any to 10.1.1.1
It is not possible to specify a range of addresses that does not have a boundary that can be defined by a standard subnet mask.
π¦ Names instead of addresses
Hostnames, resolved either via DNS or /etc/hosts, or network names, resolved via /etc/networks, may be used in place of actual addresses in the filter rules. WARNING: if a hostname expands to more than one address, only the first is used in building the filter rule.
Caution should be exercised when relying on DNS for filter rules in case the sending and receiving of DNS packets is blocked when ipf(8) is processing that part of the configuration file, leading to long delays, if not errors, in loading the filter rules. DNS drama, iykyk.
β Protocol Matching
To match packets based on TCP/UDP port information, it is first necessary to indicate which protocol the packet must be. This is done using the proto keyword, followed by either the protocol number or a name which is mapped to the protocol number, usually through the /etc/protocols file.
pass in proto tcp from 10.1.0.0/24 to any
block out proto udp from any to 10.1.1.1
pass in proto icmp from any to 192.168.0.0/16
π Sending back error packets
When a packet is just discarded using a block rule, there is no feedback given to the host that sent the packet. This is both good and bad. If this is the desired behaviour and it is not desirable to send any feedback about packets that are to be denied. The catch is that often a host trying to connect to a TCP port or with a UDP based application will send more than one packet because it assumes that just one packet may be discarded so a retry is required. The end result being logs can become cluttered with duplicate entries due to the retries.
To address this problem, a block rule can be qualified in two ways. The first of these is specific to TCP and instructs IPFilter to send back a reset (RST) packet. This packet indicates to the remote system that the packet it sent has been rejected and that it shouldnβt make any further attempts to send packets to that port. Telling IPFilter to return a TCP RST packet in response to something that has been received is achieved with the return-rst keyword like this:
block return-rst in proto tcp from 10.0.0.0/8 to any
When sending back a TCP RST packet, IPFilter must construct a new packet that has the source address of the intended target, not the source address of the system it is running on (if they are different.)
For all of the other protocols handled by the IP protocol suite, to send back an error indicating that the received packet was dropped requires sending back an ICMP error packet. Whilst these can also be used for TCP, the sending host may not treat the received ICMP error as a hard error in the same way as it does the TCP RST packet. To return an ICMP error it is necessary to place return-icmp after the block keyword like this:
block return-icmp in proto udp from any to 192.168.0.1/24
Okay so when you elect to return an ICMP error packet, you can also choose which type of ICMP error gets sent back. While the full complement of ICMP unreachable codes can be used by specifying a number instead of the string below, only the following should be used in conjunction with return-icmp. Which return code to use is a choice you make by weighing pros and cons β using some codes may make it more obvious that a firewall is being used rather than just the host not responding. π
filter-prohibβ prohibited by filter: sending packets to the destination given in the received packet is prohibited due to the application of a packet filter.net-prohibβ prohibited network: sending packets to the destination given in the received packet is administratively prohibited.host-unkβ host unknown: the destination host address is not known by the system receiving the packet and therefore cannot be reached.host-unrβ host unreachable: it is not possible to reach the host as given by the destination address in the packet header.net-unkβ network unknown: the destination network address is not known by the system receiving the packet and therefore cannot be reached.net-unrβ network unreachable: it is not possible to forward the packet on to its final destination as given by the destination address.port-unrβ port unreachable: there is no application using the given destination port and therefore it is not possible to reach that port.proto-unrβ protocol unreachable: the IP protocol specified in the packet is not available to receive packets.
An example that shows how to send back a port unreachable packet for UDP packets to 192.168.1.0/24 is as follows:
block return-icmp(port-unr) in proto udp from any to 192.168.1.0/24
In the above examples, when sending the ICMP packet, IPFilter will construct a new ICMP packet with a source address of the network interface used to send the packet back to the original source.
β οΈ This can give away that there is an intermediate system blocking packets.
To have IPFilter send back ICMP packets where the source address is the original destination, regardless of whether or not it is on the local host, return-icmp-as-dest is used like this:
block return-icmp-as-dest(port-unr) in proto udp \
from any to 192.168.1.0/24
π¦ TCP/UDP Port Matching π¦
Having specified which protocol is being matched, it is then possible to indicate which port numbers a packet must have in order to match the rule. Due to port numbers being used differently to addresses, it is therefore possible to match on them in different ways. IPFilter allows you to use the following logical operations:
< xβ the original line here is a little cursed, bestie: it says βis true if the port number is greater than or equal toxand less than or equal toy... is true if the port number in the packet is less thanx.β The< xbit at the end is the actual< xcheck: the port number in the packet is less thanx. π«<= xβ true if the port number in the packet is less than or equal tox.> xβ true if the port number in the packet is greater thanx.>= xβ true if the port number in the packet is greater or equal tox.= xβ true if the port number in the packet is equal tox.!= xβ true if the port number in the packet is not equal tox.
Additionally, there are a number of ways to specify a range of ports:
x <> yβ true if the port number is less thanaand greater thany(the page really saysa, notxβ Iβm just reporting the lore π).x >< yβ true if the port number is greater thanxand less thany.x:yβ true if the port number is greater than or equal toxand less than or equal toy.
Some examples of this are:
block in proto tcp from any port >= 1024 to any port < 1024
pass in proto tcp from 10.1.0.0/24 to any port = 22
block out proto udp from any to 10.1.1.1 port = 135
pass in proto udp from 1.1.1.1 port = 123 to 10.1.1.1 port = 123
pass in proto tcp from 127.0.0.0/8 to any port 6000:6009
If there is no desire to mention any specific source or destination information in a filter rule, then the word all can be used to indicate that all addresses are considered to match the rule. β¨
π IPv4 or IPv6 π
If a filter rule is constructed without any addresses, then IPFilter will attempt to match both IPv4 and IPv6 packets with it. In the next list of rules, each one can be applied to either network protocol because there is no address specified from which IPFilter can derive which network protocol to expect.
pass in proto udp from any to any port = 53
block in proto tcp from any port < 1024 to any
To explicitly match a particular network address family with a specific rule, the family must be added to the rule. For IPv4 it is necessary to add family inet and for IPv6, family inet6. Thus the next rule will block all packets (both IPv4 and IPv6):
block in all
but in the following example, we block all IPv4 packets and only allow in IPv6 packets:
block in family inet all
pass in family inet6 all
To continue on from the example where we allowed either IPv4 or IPv6 packets to port 53 in: to change that so only IPv6 packets to port 53 need to be allowed/blocked, it is possible to add in a protocol family qualifier:
pass in family inet6 proto udp from any to any port = 53
π First match vs last match π
To change the default behaviour from being the last matched rule decides the outcome to being the first matched rule, the word quick is inserted to the rule. β‘
β¨ Extended Packet Matching β¨
π Beyond using plain addresses π
okay so bestie, if your firewall is working with large numbers of hosts and networks β or you're just trying to filter super discretely against various hosts β it can be way easier admin-wise to define a pool of addresses and have a filter rule reference that address pool instead of having a rule for every single address. π
In addition to being able to use address pools, you can use the interface name(s) in the from/to address fields of a rule. If the name being used in the address section can be matched to any of the interface names mentioned in the rule's on or via fields, then it can be used with one of these keywords for extended effect:
broadcastβ use the primary broadcast address of the network interface for matching packets with this filter rule. Like this:
pass in on fxp0 proto udp from any to fxp0/broadcast port = 123
peerβ use the peer address on point to point network interfaces for matching packets with this filter rule. This option typically only has meaningful use with link protocols such as SLIP and PPP. For example, this rule allows ICMP packets from the remote peer ofppp0to be received if they're destined for the address assigned to the link at the firewall end:
pass in on ppp0 proto icmp from ppp0/peer to ppp0/32
netmaskedβ use the primary network address, with its netmask, of the network interface for matching packets with this filter rule. If a network interface had an IP address of192.168.1.1and its netmask was255.255.255.0(/24), then using the wordnetmaskedafter the interface name would match any addresses that would match192.168.1.0/24. If we assume thatbge0has this IP address and netmask then the following two rules both serve to produce the same effect:
pass in on bge0 proto icmp from any to 192.168.1.0/24
pass in on bge0 proto icmp from any to bge0/netmasked
networkβ using the primary network address, and its netmask, of the network interface, construct an address for exact matching. If a network interface has an address of192.168.1.1and its netmask is255.255.255.0, using this option would only match packets to192.168.1.0. So:
pass in on bge0 proto icmp from any to bge0/network
Another way to use the name of a network interface to get the address is to wrap the name in ()'s. In the above method, IPFilter looks at the interface names in use to decide whether or not the name given is a hostname or network interface name. With the use of ()'s, it is possible to tell IPFilter that the name should be treated as a network interface name even though it doesn't appear in the list of network interfaces that the rule is associated with. π«Ά
pass in proto icmp from any to (bge0)/32
πΈ Using address pools πΈ
Rather than list out multiple rules that either allow or deny specific addresses, it is possible to create a single object, called an address pool, that contains all of those addresses and reference that in the filter rule. For documentation on how to write the configuration file for those pools and load them, see ippool.conf(5) and ippool(8). There are two types of address pools that can be defined in ippool.conf(5): trees and hash tables. To refer to a tree defined in ippool.conf(5), use this syntax:
pass in from pool/trusted to any
Either a name or number can be used after the /, just so long as it matches up with something that has already been defined in ipool.conf(5) and loaded in with ippool(8). Loading a filter rule that references a pool that does not exist will result in an error. π
If hash tables have been used in ippool.conf(5) to store the addresses in instead of a tree, then replace the word pool with hash:
pass in from any to hash/webservers
There are different operational characteristics with each, so there may be some situations where a pool works better than hash and vice versa.
π₯ Matching TCP flags π₯
The TCP header contains a field of flags that is used to decide if the packet is a connection request, connection termination, data, etc. By matching on the flags in conjunction with port numbers, it is possible to restrict the way in which IPFilter allows connections to be created. A quick overview of the TCP flags is below. Each is listed with the letter used in IPFilter rules, followed by its three or four letter pneumonic.
SβSYNβ this bit is set when a host is setting up a connection. The initiator typically sends a packet with the SYN bit and the responder sends back SYN plus ACK.AβACKβ this bit is set when the sender wishes to acknowledge the receipt of a packet from another host.PβPUSHβ this bit is set when a sending host has sent some data that is yet to be acknowledged and a reply is sought.FβFINβ this bit is set when one end of a connection starts to close the connection down.UβURGβ this bit is set to indicate that the packet contains urgent data.RβRSTβ this bit is set only in packets that are a reply to another that has been received but is not targeted at any open port.CβCWNEβECN
When matching TCP flags, it is normal to just list the flag that you wish to be set. By default the set of flags it is compared against is FSRPAU. Rules that say flags S will be displayed by ipfstat(8) as having flags S/FSRPAU. This is normal. Periodt. The last three flags, E, W and e, are optional β they may or may not be used by an end host and have no bearing on either the acceptance of data nor control of the connection. Masking them out with flags S/FSRPAUEWe may cause problems for remote hosts making a successful connection. π
pass in quick proto tcp from any to any port = 22 flags S/SAFR
pass out quick proto tcp from any port = 22 to any flags SA
By itself, filtering based on the TCP flags becomes more work but when combined with stateful filtering (see below), the situation changes.
π¦ Matching on ICMP header information π¦
TCP and UDP are not the only protocols for which filtering beyond just the IP header is possible β extended matching on ICMP packets is also available. The list of valid ICMP types is different for IPv4 vs IPv6.
As a practical example, to allow the ping command to work against a specific target requires allowing two different types of ICMP packets, like this:
pass in proto icmp from any to webserver icmp-type echo
pass out proto icmp from webserver to any icmp-type echorep
The ICMP header has two fields that are of interest for filtering: the ICMP type and code. Filter rules can accept either a name or number for both the type and code. The list of names supported for ICMP types is listed below, however only ICMP unreachable errors have named codes (see above.)
The list of ICMP types that are available for matching an IPv4 packet are as follows:
echo(echo request)echorep(echo reply)inforeq(information request)inforep(information reply)maskreq(mask request)maskrep(mask reply)paramprob(parameter problem)redir(redirect)routerad(router advertisement)routersol(router solicit)squence(source quence)timest(timestamp)timestreq(timestamp reply)timex(time exceeded)unreach(unreachable)
The list of ICMP types that are available for matching an IPv6 packet are as follows:
echo(echo request)echorep(echo reply)fqdnquery(FQDN query)fqdnreply(FQDN reply)inforeq(information request)inforep(information reply)listendone(MLD listener done)listendqry(MLD listener query)listendrep(MLD listener reply)neighadvert(neighbour advert)neighborsol(neighbour solicit)paramprob(parameter problem)redir(redirect)renumber(router renumbering)routerad(router advertisement)routersol(router solicit)timex(time exceeded)toobig(packet too big)unreach(unreachable,whoreq(WRU request),whorep(WRU reply).
π Stateful Packet Filtering π
Okay so stateful packet filtering is where IPFilter remembers some info from one or more packets itβs already seen, and then applies that info to future packets coming in from the network. Itβs giving βI remember you, bestieβ energy. π§ β¨
What that actually means is different for each transport-layer protocol:
- For TCP, it means if IPFilter sees the very first packet of an attempt to make a connection, it has enough info to allow all the other subsequent packets without needing any explicit rules to match them. IPFilter uses the TCP port numbers, TCP flags, window size and sequence numbers to figure out which packets should match. π
- For UDP, only the UDP port numbers are available.
- For ICMP, the ICMP types can be combined with the ICMP id field to determine which reply packets match a request/query thatβs already been seen.
- For all other protocols, only matching on IP address and protocol number is available for determining if a received packet is a mate to one thatβs already been let through.
The difference this makes is a reduction in the number of rules from 2 or 4 down to 1. No cap, thatβs iconic. For example, these 4 rules:
pass in on bge0 proto tcp from any to any port = 22
pass out on bge1 proto tcp from any to any port = 22
pass in on bge1 proto tcp from any port = 22 to any
pass out on bge0 proto tcp from any port = 22 to any
can be replaced with this single rule:
pass in on bge0 proto tcp from any to any port = 22 flags S keep state
Similar rules for UDP and ICMP might be:
pass in on bge0 proto udp from any to any port = 53 keep state
pass in on bge0 proto icmp all icmp-type echo keep state
When using stateful filtering with TCP, it is best to add flags S to the rule so state is only created when a packet is seen thatβs an indication of a new connection. IPFilter can gather some info from packets in the middle of a TCP connection to do stateful filtering, but there are some options that are only sent at the start of the connection which alter the valid window of what TCP accepts. The end result of trying to pick up TCP state in mid-connection is that some later packets that are part of the connection may not match the known state info and get dropped or blocked, causing problems. π
If a TCP packet matches IP addresses and port numbers but does not fit into the recognised window, it will not be automatically allowed and will be flagged inside IPFilter as βout of windowβ (oow). See below, βExtra packet attributesβ, for how to match on this attribute. π
Once a TCP connection has reached the established state, the default timeout allows it to be idle for 5 days before itβs removed from the state table. The timeouts for the other TCP connection states vary from 240 seconds to 30 seconds. Both UDP and ICMP state entries have asymmetric timeouts where the timeout set upon seeing packets in the forward direction is much larger than for the reverse direction. For UDP the default timeouts are 120 and 12 seconds; for ICMP 60 and 6 seconds. This reflects those protocols being used more for query-response than for ongoing connections. For all other protocols the timeout is 60 seconds in both directions. β³
β¨ Stateful filtering options β¨
The following options can be used with stateful filtering:
limitβ limits the number of state table entries that this rule can create to the number given afterlimit. A rule that has alimitspecified is always permitted that many state table entries, even if creating an additional entry would cause the table to have more entries than the otherwise global limit. π«Ά
pass ... keep state(limit 100)
ageβ sets the timeout for the state entry when it sees packets going through it. Additionally, itβs possible to set the timeout for the reply packets that come back through the firewall to a different value than for the forward path, allowing a short timeout to be set after the reply has been seen and the state is no longer required. β±οΈ
pass in quick proto icmp all icmp-type echo \
keep state (age 3)
pass in quick proto udp from any \
to any port = 53 keep state (age 30/1)
strictβ only has an impact when used with TCP. It forces all packets that are allowed through the firewall to be sequential: no out-of-order delivery of packets is allowed. This can cause significant slowdown for some connections and may stall others. Use with caution. π
pass in proto tcp ... keep state(strict)
noicmperrβ prevents ICMP error packets from being able to match state table entries created with this flag using the contents of the original packet included. π
pass ... keep state(noicmperr)
syncβ indicates to IPFilter that it needs to provide information to the userland daemons responsible for syncing other machinesβ state tables up with this one. π
pass ... keep state(sync)
nologβ do not generate any log records for the creation or deletion of state table entries. π€«
pass ... keep state(nolog)
icmp-headβ rather than just preventing ICMP error packets from being able to match state table entries, allow an ACL to be processed that can filter in or out ICMP error packets based as you would with normal firewall rules. Theicmp-headoption requires a filter rule group number or name to be present, just as you would use withhead. π
pass in quick proto tcp ... keep state(icmp-head 101)
block in proto icmp from 10.0.0.0/8 to any group 101
max-srcsβ allows the number of distinct hosts that can create a state entry to be defined. πΈ
pass ... keep state(max-srcs 100)
pass ... keep state(limit 1000, max-srcs 100)
max-per-srcβ whilstmax-srcslimits the number of individual hosts that may cause the creation of a state table entry, each one of those hosts is still able to fill up the state table with new entries until the global maximum is reached. This option allows the number of state table entries per address to be limited. π
pass ... keep state(max-srcs 100, max-per-src 1)
pass ... keep state(limit 100, max-srcs 100, max-per-src 1)
Whilst these two rules might seem identical, in that they both ultimately limit the number of hosts and state table entries created from the rule to 100, there is a subtle difference: the second will always allow up to 100 state table entries to be created, whereas the first may not if the state table fills up from other rules. π
Further, itβs possible to specify a netmask size after the per-host limit that enables the per-host limit to become a per-subnet or per-network limit. π¦
pass ... keep state(max-srcs 100, max-per-src 1/24)
If there is no IP protocol implied by addresses or other features of the rule, IPFilter will assume that no netmask is an all-ones netmask for both IPv4 and IPv6.
π Tieing down a connection π
For any connection that transits a firewall, each packet will be seen twice: once going in and once going out. So a connection has 4 flows of packets:
- forward inbound packets
- forward outbound packets
- reverse inbound packets
- reverse outbound packets
IPFilter lets you define the network interface to be used at all four points in the flow of packets. For rules that match inbound packets, out-via is used to specify which interfaces the packets go out. For rules that match outbound packets, in-via is used to match the inbound packets. In each case, the syntax generalises to this:
pass ... in on forward-in,reverse-in \
out-via forward-out,reverse-out ...
pass ... out on forward-out,reverse-out \
in-via forward-in,reverse-in ...
An example that pins down all 4 network interfaces used by an ssh connection might look something like this:
pass in on bge0,bge1 out-via bge1,bge0 proto tcp \
from any to any port = 22 flags S keep state
π¦ Working with packet fragments π¦
Fragmented packets result in 1 packet containing all of the layer 3 and 4 header information whilst the data is split across a number of other packets.
To enforce access control on fragmented packets, one of two approaches can be taken. The first is to allow through all of the data fragments (those that made up the body of the original packet) and rely on matching the header information in the βfirstβ fragment, when it is seen. The reception of body fragments without the first will result in the receiving host being unable to completely reassemble the packet and discarding all of the fragments. π΅βπ«
The following three rules deny all fragmented packets from being received except those that are UDP, and even then only allow those destined for port 2049 to be completed:
block in all with frags
pass in proto udp from any to any with frag-body
pass in proto udp from any to any port = 2049 with frags
Another mechanism thatβs available is to track βfragment stateβ. This relies on the first fragment of a packet that arrives being the fragment that contains all of the layer 3 and layer 4 header information. With the receipt of that fragment before any other, itβs possible to determine which other fragments are to be allowed through without needing to explicitly allow all fragment body packets. An example of how this is done is as follows:
pass in proto udp from any port = 2049 to any with frags keep frags
π Building a tree of rules π
Writing your filter rules as one long list can be both inefficient in terms of processing the rules and difficult to understand. To make the construction of filter rules easier, itβs possible to place them in groups. A rule can be both a member of a group and the head of a new group. π³
Using filter groups requires at least two rules: one to be in the group, and one to send matching packets to the group. If a packet matches a filter rule that is a group head but does not match any of the rules in that group, then the packet is considered to have matched the head rule.
Rules that are a member of a group contain the word group followed by either a name or number that defines which group theyβre in. Rules that form the branch point or starting point for the group must use the word head, followed by either a group name or number. If rules are loaded that define a group but there is no matching head, then they will effectively be orphaned rules. Itβs possible to have more than one head rule point to the same group, allowing groups to be used like subroutines to implement specific common policies. π«Άβ¨
okay so a super common glow-up for filter groups: you drop head rules right into the filter "main line" for each direction, one per interface you're actually running. like this:
block in quick on bge0 all head 100
block out quick on bge0 all head 101
block in quick on fxp0 all head internal-in
block out quick on fxp0 all head internal-out
pass in quick proto icmp all icmp-type echo group 100
so looking at that set π there are four groups defined but only one of them even has a member rule. which means the only packets getting through that entire ruleset are ICMP echo packets received on bge0. everything else? not invited. π
and here's the iconic part: a rule can be a member of a group and the head of a whole new group at the same time β that's literally how groups specialise and get all niche:
block in quick on bge0 all head 100
block in quick proto tcp all head 1006 group 100
another use of filter rule groups β and honestly kind of underrated β is giving rules a place to be added dynamically without you having to stress about their specific ordering among the entire ruleset. like, say you're running this simple ruleset:
block in quick all with bad
block in proto tcp from any to any port = smtp head spammers
pass in quick proto tcp from any to any port = smtp flags S keep state
and you're getting a ton of connections to your email server from 10.1.1.1 trying to deliver spam π you can just load this one rule to complement everything above:
block in quick from 10.1.1.1 to any group spammers
πͺ Decapsulation
rule groups also do a different but absolutely vital job for decapsulation rules. take this simple rule:
decapsulate in proto ah all head 1001
if IPFilter receives an IP packet that has an AH header as its layer 4 payload, IPFilter adjusts its view of the packet internally, then jumps to group 1001 using the data beyond the AH header as the new transport header. it's giving "peek behind the curtain" energy β¨
for protocols that are recognised as being used with tunnelling, or that otherwise encapsulate IP protocols, IPFilter can figure out what it's got on the inside with zero assistance. but some tunnelling protocols use UDP as the transport mechanism β and those times you gotta tell IPFilter what protocol is hiding inside UDP:
decapsulate l5-as(ip) in proto udp from any \
to any port = 1520 head 1001
currently IPFilter only supports finding IPv4 and IPv6 headers directly after the UDP header. that's the limit, bestie.
now, if a packet matches a decapsulate rule but then fails to match any of the rules inside the specified group, processing of the packet continues on to the next rule after the decapsulate β and IPFilter's internal view of the packet gets returned to exactly what it was before the decapsulate rule. like nothing ever happened π
also: you can construct a decapsulate rule with no group head at the end, and ipf(8) will happily accept it β but such rules will not result in anything happening. literally zero. a decorative rule. π
π£οΈ Policy Based Routing
firewalls live at the boundary of different networks connecting together, with multiple connections that have different properties β so like, very often you want packets flowing in a direction that's different from what the routing table is telling the kernel. and these decisions can often go even further: changing the route based on both source and destination address, or even port numbers. main character routing behavior, honestly.
to support this kind of setup, IPFilter lets you specify the next hop destination right there in a filter rule. the next hop is given with the interface name to use for output, and the syntax is interface:ip.address. and the expectation is that the address you give as the next hop is directly connected to the network the interface is on.
pass in on bge0 to bge1:1.1.1.1 proto tcp \
from 1.1.2.3 to any port = 80 flags S keep state
combine this with stateful filtering and you can influence which network interface transmits packets in both directions, because now we have a sense of what the reverse flow of packets looks like. obsessed. π«Ά
pass in on bge0 to bge1:1.1.1.1 reply-to hme1:2.1.1.2 \
proto tcp from 1.1.2.3 to any port = 80 flags S keep state
and if the routing table's actions are perfectly fine but you'd like to mask the firewall's presence by not changing the TTL in IP packets as they transit it, you can tell IPFilter to do a "fastroute" action like this:
pass in on bge0 fastroute proto icmp all
β οΈ use this with caution β it can lead to endless packet loops. also worth knowing: policy based routing does not change the IP header's TTL value.
a variation on this kind of rule supports creating a duplicate of the original packet and sending it out a different network. super useful for monitoring traffic and other purposes:
pass in on bge0 to bge1:1.1.1.1 reply-to hme1:2.1.1.2 \
dup-to fxp0:10.0.0.1 proto tcp from 1.1.2.3 \
to any port = 80 flags S keep state
π Matching IPv4 options
the design for IPv4 allows the header to be up to 64 bytes long, but most traffic only uses the basic header, which is 20 bytes. the other 44 bytes can store IP options. and ngl these options are generally not necessary for proper interaction and function on the Internet today. for most people it's fully enough to block and drop every packet that has any options set, which you do like this:
block in quick all with ipopts
this rule usually goes towards the top of the configuration so all incoming packets get blocked. π
but if you wanted to allow in one specific IP option type, the syntax shifts slightly:
pass in quick proto igmp all with opt rtralrt
here's the list of IP options most people encounter, plus what their use/threat is π
lsrr(loose source route) β the sender of the packet includes a list of addresses they want the packet routed through on the way to the destination. because replies to such packets are expected to use that list of addresses in reverse, hackers can very effectively use this header option in address spoofing attacks. yeah. π¬rr(record route) β the sender allocates some buffer space for recording the IP address of each router the packet goes through. most often used with ping, where the ping response contains a copy of all the addresses from the original packet, telling the sender what route the packet took to get there. due to performance and security issues with IP header options, this is almost no longer used.rtralrt(router alert) β this option is often used in IGMP messages as a flag to routers that the packet needs to be handled differently. unlikely to ever be received from an unknown sender. you may find it on LANs or otherwise controlled networks where the RSVP protocol and multicast traffic are in heavy use.ssrr(strict source route) β the sender includes a list of addresses they want the packet routed through on the way to the destination. where thelsrroption lets the sender specify only some of the nodes the packet must go through, withssrrevery next hop router must be specified.
the complete list of IPv4 options you can match on is: addext (Address Extension), cipso (Classical IP Security Option), dps (Dynamic Packet State), e-sec (Extended Security), eip (Extended Internet Protocol), encode (ENCODE), finn (Experimental Flow Control), imitd (IMI Traffic Descriptor), lsrr (Loose Source Route), mtup (MTU Probe - obsolete), mtur (MTU response - obsolete), nop (No Operation), nsapa (NSAP Address), rr (Record Route), rtralrt (Router Alert), satid (Stream Identifier), sdb (Selective Directed Broadcast), sec (Security), ssrr (Strict Source Route), tr (Tracerote), ts (Timestamp), ump (Upstream Multicast Packet), visa (Experimental Access Control) and zsu (Experimental Measurement). the whole alphabet of chaos, genuinely π
π Security with CIPSO and IPSO
IPFilter supports filtering on IPv4 packets using security attributes embedded in the IP options part of the packet. these options are usually only used on networks and systems running labelled security. unless you know you're using labelled security and your networking is also labelled, it's highly unlikely this section applies to you. (if it does β hi bestie, you're in a very specific club π)
with the traditional IP Security Options (IPSO), packets can be tagged with a security level. the following keywords are recognised, and they match the relevant RFC with respect to the bit patterns matched: confid (confidential), rserve-1 (1st reserved value), rserve-2 (2nd reserved value), rserve-3 (3rd reserved value), rserve-4 (4th reserved value), secret (secret), topsecret (top secret), unclass (unclassified).
block in quick all with opt sec-class unclass
pass in all with opt sec-class secret
𧬠Matching IPv6 extension headers
just like you can filter on the various IPv4 header options, you can also filter on the IPv6 extension headers sitting between the IPv6 header and the transport protocol header. the full set is:
dstopts(destination options)esp(encrypted, secure, payload)frag(fragment)hopopts(hop-by-hop options)ipv6(IPv6 header)mobility(IP mobility)nonerouting
π Logging
there are two ways packets get logged with IPFilter. the first is a rule that specifically says "log these types of packets" β the second is a qualifier tacked onto one of the other keywords. which means you can log and allow, or log and deny, a packet all with a single rule. multitasking icon. π
pass in log quick proto tcp from any to any port = 22
when you're using stateful filtering, the log action becomes part of the result that gets remembered about a packet. so if the rule above was qualified with keep state, every packet in the connection would be logged. to only log the first packet from every packet flow tracked with keep state, you gotta tell IPFilter you only want that first one:
pass in log first quick proto tcp from any to any port = 22 \
flags S keep state
now β if it's a requirement that your logging gives an accurate representation of which connections are allowed, you can qualify the log action with the option or-block. this lets the administrator tell IPFilter to block the packet if the attempt to record it in IPFilter's kernel log records failed (those records have an upper bound on size, so it's a real possibility). and unless the system shuts down or reboots, once a log record is written into the kernel buffer it's there until ipmon(8) reads it.
block in log proto tcp from any to any port = smtp
pass in log or-block first quick proto tcp from any \
to any port = 22 flags S keep state
by default, IPFilter only logs the header portion of a packet received on the network. but up to 128 bytes of a packet's body can also be logged with the body keyword β and ipmon(8) will display the contents of that logged portion of the body in hex. cute little hex dump souvenir π
block in log body proto icmp all
okay so when packets get logged from ipmon(8) to syslog, the default is that ipmon(8) gets to pick which syslog facility and priority the packet is logged with π but bestie you can tune that per-rule, like this:
block in quick log level err all with bad
pass in log level local1.info proto tcp \
from any to any port = 22 flags S keep state
and ipfstat(8) will happily tell you how many packets got successfully logged and how many log attempts failed. receipts, always. π
π¬ Filter rule comments
so sometimes you want to attach a lil text string to an IPFilter rule β could be an admin comment, could be anything honestly β and that's what a rule comment is for π
The comment gets loaded with the rule into the kernel and you'll see it when you list the rules with ipfstat. it's giving sticky notes on your firewall. π
pass in quick proto tcp from any \
to port = 80 comment "all web server traffic is ok"
pass out quick proto tcp from any port = 80 \
to any comment "all web server traffic is ok"
π·οΈ Tags
so for filtering and NAT to correctly match up packets with rules, you can add tags β with NAT (for inbound packets) and with filtering (for outbound packets). This lets a filter be correctly mated with its NAT rule in the event that the NAT rule changed the packet in a way that would mean it's not obvious what it was π (iconic teamwork honestly).
For inbound packets, IPFilter can match the tag used in the filter rules with the one set by NAT. For outbound rules</outbound>, it's the reverse β the filter sets the tag and the NAT rule matches up with it. π
pass in ... match-tag(nat=proxy)
pass out ... set-tag(nat=proxy)
another use of tags? supplying a number that's only used with logging πͺ΅ When packets match these rules, the log tag is carried over into the log file records generated by ipmon(8). With the correct use of tools such as grep, extracting log records of interest is simplified β literally so slay, filtering your logs by tag is chef's kiss. π₯
block in quick log ... set-tag(log=33)
β³ Filter Rule Expiration
IPFilter lets rules be added into the kernel that it will remove after a specific period of time, and you do that by specifying rule-ttl at the end of a rule π°οΈ When listing rules in the kernel using ipfstat(8), rules that are going to expire will NOT display rule-ttl with the timeout β instead, what you'll see is a comment with how many ipfilter ticks left the rule has to live. so don't panic when it looks different, that's just how it vibes. π
The time to live is specified in seconds.
pass in on fxp0 proto tcp from any \
to port = 22 flags S keep state rule-ttl 30
𧬠Internal packet attributes
bestie this is the good part β besides filtering on super specific network and transport header fields, you can also filter on other attributes that IPFilter attaches to a packet β¨ These attributes go in a rule after the keyword "with", just like you saw with frags and frag-body above. Here's the whole list of what else is available:
oowβ the packet's IP addresses and TCP ports match an existing entry in the state table, but the sequence numbers indicate that it's outside of the accepted window π out-of-window behavior, we see you.
block return-rst in quick proto tcp from any to any with not oow
bcastβ this one gets set by IPFilter when it receives notification that the link layer packet was a broadcast packet. Important: no checking of the IP addresses is performed to determine if it is a broadcast destination or not π«Ά so it's the OS's word we're going on.
block in quick proto udp all with bcast
mcastβ same deal but for multicast: set by IPFilter when it receives notification that the link layer packet was a multicast packet. And again, no checking of the IP addresses is performed to determine if it is a multicast destination or not. π
pass in quick proto udp from any to any port = dns with mcast
mbcastβ this one matches a packet that is either a multicast or a broadcast packet at the link layer, as indicated by the operating system. two vibes, one flag π
pass in quick proto udp from any to any port = ntp with mbcast
natβ the packet positively matched a NAT table entry. slay. βbadβ sanity checking of the packet failed π This could indicate that the layer 3/4 headers are not properly formed. red flag fr fr.bad-srcβ when reverse path verification is enabled, this flag gets set when the interface the packet is received on does not match the one that would be used to send a packet out of to the source address in the received packet π© that's a spoofy vibe and not the cute kind.bad-natβ an attempt to perform NAT on the packet failed π it tried, it really did.notβ okay so every single one of the attributes you match with the"with"keyword can also be looked for in itsnotform, i.e. checking that it's not present. For example, to only allow in good packets, you can do this:
block in all
pass in all with not bad
like β block everything, then let through the good ones. main character packet energy only π
β¨ Tuning IPFilter β¨
Okay bestie, plot twist: ipf.conf isn't just for rules β it can also tune how IPFilter itself behaves π
. That means things like timeouts for the NAT/state table(s), plus their sizes, can be set right in the config. The tunables' presence and names may change from one IPFilter release to the next β so don't get too attached, okay? π
The tunables you can change via ipf.conf are the same set you can see and modify with the -T command line option to ipf(8). Slay.
β οΈ NOTE: whenipf(8)parsesipf.conf, it applies the settings before loading any rules. So if your settings are at the top, they may get applied while the rules may not get applied if there's an error further down in the config file. Messy? Yes. Facts? Also yes π.
To set one of the values below, the syntax is simple: "set", followed by the name of the tuneable, then the value to set it to.
set state_max 9999;
set state_size 10101;
A list of the currently available variables inside IPFilter that may be tuned from ipf.conf is as follows:
activeβ set through the-scommand line switch ofipf(8). Seeipf(8)for details.chksrcβ when set, enables reverse path verification on source addresses and for filters to match packets with thebad-srcattribute.control_forwardingβ when set, turns off kernel forwarding when IPFilter is disabled or unloaded.default_passβ the default policy β whether packets are blocked or passed, etc β is represented by the value of this variable. It's a bit field and the bits that can be set are found in<netinet/ip_fil.h>. It is not recommended to tune this value directly.ftp_debugβ set the debugging level of the in-kernel FTP proxy. Debug messages will be printed to the system console.ftp_forcepasvβ when set, the FTP proxy must see aPASV/EPSVcommand before creating the state/NAT entries for the227response.ftp_insecureβ when set, the FTP proxy will not wait for a user to login before allowing data connections to be created.ftp_pasvonlyβ when set, the proxy will not create state/NAT entries for when it sees either thePORTorEPRTcommand.ftp_pasvrdrβ when enabled, causes the FTP proxy to create very insecure NAT/state entries that will allow any connection between the client and server hosts when a227reply is seen. Use with extreme caution π¨.ftp_single_xferβ when set, the FTP proxy will only allow one data connection at a time.hostmap_sizeβ sets the size of the hostmap table used by NAT to store address mappings for use with sticky rules.icmp_ack_timeoutβ default timeout used for ICMP NAT/state when a reply packet is seen for an ICMP state that already exists.icmp_minfragmtuβ sets the minimum MTU that is considered acceptable in an ICMP error before deciding it is a bad packet.icmp_timeoutβ default timeout used for ICMP NAT/state when the packet matches the rule.ip_timeoutβ default timeout used for NAT/state entries that are not TCP/UDP/ICMP.ipf_flagsβ the original gives no description for this one, so I'm not inventing lore, bestie π«Ά.ips_proxy_debugβ this sets the debugging level for the proxy support code. When enabled, debugging messages will be printed to the system console.log_allβ when set, it changes the behaviour of"log body"to log the entire packet rather than just the first128bytes.log_sizeβ sets the size of the in-kernel log buffer in bytes.log_suppressβ when set, IPFilter will check to see if the packet it is logging is similar to the one it previously logged, and if so, increases the occurrence count for that packet. The previously logged packet must not have yet been read byipmon(8).min_ttlβ is used to set the TTL value that packets below will be marked with thelow-ttlattribute.nat_doflushβ if set, it will cause the NAT code to do a more aggressive flush of the NAT table at the next opportunity. Once the flush has been done, the value is reset to0.nat_lockβ this should only be changed usingipfs(8).nat_loggingβ when set, NAT will create log records that can be read from/dev/ipnat.nat_maxbucketβ maximum number of entries allowed to exist in each NAT hash bucket. This prevents an attacker trying to load up the hash table with entries in a single bucket, reducing performance.nat_rules_sizeβ size of the hash table to store map rules.nat_table_maxβ maximum number of entries allowed into the NAT table.nat_table_sizeβ size of the hash table used for NAT.nat_table_wm_highβ when the fill percentage of the NAT table exceeds this mark, more aggressive flushing is enabled.nat_table_wm_lowβ this sets the percentage at which the NAT table's aggressive flushing will turn itself off.rdr_rules_sizeβ size of the hash table to store rdr rules.state_lockβ this should only be changed usingipfs(8).state_loggingβ when set, the stateful filtering will create log records that can be read from/dev/ipstate.state_maxβ maximum number of entries allowed into the state table.state_maxbucketβ maximum number of entries allowed to exist in each state hash bucket. This prevents an attacker trying to load up the hash table with entries in a single bucket, reducing performance.state_sizeβ size of the hash table used for stateful filtering.state_wm_freqβ this controls how often the aggressive flushing should be run once the state table exceedsstate_wm_highin percentage full.state_wm_highβ when the fill percentage of the state table exceeds this mark, more aggressive flushing is enabled.state_wm_lowβ this sets the percentage at which the state table's aggressive flushing will turn itself off at.tcp_close_waitβ timeout used when a TCP state entry reaches theFIN_WAIT_2state.tcp_closedβ timeout used when a TCP state entry is ready to be removed after either aRSTpacket is seen.tcp_half_closedβ timeout used when a TCP state entry reaches theCLOSE_WAITstate.tcp_idle_timeoutβ timeout used when a TCP state entry reaches theESTABLISHEDstate.tcp_last_ackβ timeout used when a TCP NAT/state entry reaches theLAST_ACKstate.tcp_syn_receivedβ timeout applied to a TCP NAT/state entry afterSYN-ACKpacket has been seen.tcp_syn_sentβ timeout applied to a TCP NAT/state entry afterSYNpacket has been seen.tcp_time_waitβ timeout used when a TCP NAT/state entry reaches theTIME_WAITstate.tcp_timeoutβ timeout used when a TCP NAT/state entry reaches either the half established state (one ack is seen after aSYN-ACK) or one side is inFIN_WAIT_1.udp_ack_timeoutβ default timeout used for UDP NAT/state when a reply packet is seen for a UDP state that already exists.udp_timeoutβ default timeout used for UDP NAT/state when the packet matches the rule.update_ipidβ when set, turns on changing the IP id field inNAT'dpackets to a random number.
πΈ Table of visible variables πΈ
A list of all of the tunables, their minimum, maximum and current values is as follows. The columns are Name, Min, Max, Current β every single row, no skips, no cap π:
activeβ min0, max0, current0chksrcβ min0, max1, current0control_forwardingβ min0, max1, current0default_passβ min0, maxMAXUINT, current134217730ftp_debugβ min0, max10, current0ftp_forcepasvβ min0, max1, current1ftp_insecureβ min0, max1, current0ftp_pasvonlyβ min0, max1, current0ftp_pasvrdrβ min0, max1, current0ftp_single_xferβ min0, max1, current0hostmap_sizeβ min1, maxMAXINT, current2047icmp_ack_timeoutβ min1, maxMAXINT, current12icmp_minfragmtuβ min0, max1, current68icmp_timeoutβ min1, maxMAXINT, current120ip_timeoutβ min1, maxMAXINT, current120ipf_flagsβ min0, maxMAXUINT, current0ips_proxy_debugβ min0, max10, current0log_allβ min0, max1, current0log_sizeβ min0, max524288, current32768log_suppressβ min0, max1, current1min_ttlβ min0, max1, current4nat_doflushβ min0, max1, current0nat_lockβ min0, max1, current0nat_loggingβ min0, max1, current1nat_maxbucketβ min1, maxMAXINT, current22nat_rules_sizeβ min1, maxMAXINT, current127nat_table_maxβ min1, maxMAXINT, current30000nat_table_sizeβ min1, maxMAXINT, current2047nat_table_wm_highβ min2, max100, current99nat_table_wm_lowβ min1, max99, current90rdr_rules_sizeβ min1, maxMAXINT, current127state_lockβ min0, max1, current0state_loggingβ min0, max1, current1state_maxβ min1, maxMAXINT, current4013state_maxbucketβ min1, maxMAXINT, current26state_sizeβ min1, maxMAXINT, current5737state_wm_freqβ min2, max999999, current20state_wm_highβ min2, max100, current99state_wm_lowβ min1, max99, current90tcp_close_waitβ min1, maxMAXINT, current480tcp_closedβ min1, maxMAXINT, current60tcp_half_closedβ min1, maxMAXINT, current14400tcp_idle_timeoutβ min1, maxMAXINT, current864000tcp_last_ackβ min1, maxMAXINT, current60tcp_syn_receivedβ min1, maxMAXINT, current480tcp_syn_sentβ min1, maxMAXINT, current480tcp_time_waitβ min1, maxMAXINT, current480tcp_timeoutβ min1, maxMAXINT, current480udp_ack_timeoutβ min1, maxMAXINT, current24udp_timeoutβ min1, maxMAXINT, current240update_ipidβ min0, max1, current0
π CALLING OUT TO INTERNAL FUNCTIONS π
okay so IPFilter gives you a pair of functions that you can call straight from a rule β and they let a single rule jump out to a whole group instead of walking through a list of rules just to find that group. If youβve got multiple networks, each with its own group of rules, this feature may help provide better filtering performance. highkey useful π«
The lookup to find which rule group to jump to is done on either the source address or the destination address but not both. pick a lane, bestie π
In the example below, all packets are blocked by default, but then we do a lookup on the source address from group 1010. The two rules in the ipf.conf section are lone members of their group. For an incoming packet from 1.1.1.1, it goes through three rules: (1) the block rule, (2) the call rule, and (3) the pass rule for group 1020. For a packet from 3.3.2.2, it also goes through three rules: (1) the block rule, (2) the call rule, and (3) the pass rule for group 1030. If a packet from 3.1.1.1 shows up, it gets blocked because it doesnβt match any of the entries in group 1010, so it only matches the first rule. no cap β¨
from ipf.conf
-------------
block in all
call now srcgrpmap/1010 in all
pass in proto tcp from any to any port = 80 group 1020
pass in proto icmp all icmp-type echo group 1030
from ippool.conf
----------------
group-map in role=ipf number=1010
{ 1.1.1.1 group = 1020, 3.3.0.0/16 group = 1030; };
β¨ IPFILTER MATCHING EXPRESSIONS β¨
Thereβs an experimental feature added to filter rules: you can use the same expression matching thatβs available with various commands to flush and list state/NAT table entries. Using one of these expressions precludes the filter rule from using the normal IP header matching. itβs giving special-case energy π¦
pass in exp { "tcp.sport 23 or tcp.sport 50" } keep state
πΈ FILTER RULES WITH BPF πΈ
On platforms that have the BPF built into the kernel, IPFilter can be built to allow BPF expressions in filter rules. This allows for packet matching to be on arbitrary data in the packt. Using a BPF expression replaces all of the other protocol header matching done by IPFilter. okay periodt π
pass in bpf-v4 { "tcp and (src port 23 or src port 50)" } \
keep state
These rules tend to be write-only because the act of compiling the filter expression into the BPF instructions loaded into the kernel can make it difficut to accurately reconstruct the original text filter. The end result is that whileipf.conf()can be easy to read, understanding the output fromipfstatmight not be. π
π VARIABLES π
This configuration file, like all others used with IPFilter, supports the use of variable substitution throughout the text. bestie loves a reusable value π
nif="ppp0";
pass in on $nif from any to any
would become
pass in on ppp0 from any to any
Variables can be used recursively, such as 'foo="$bar baz";', so long as $bar exists when the parser reaches the assignment for foo. and yes, thatβs the whole vibe check π«Ά
See ipf(8) for instructions on how to define variables to be used from a shell environment.
π¦ FILES π¦
/dev/ipf/etc/ipf.conf/usr/share/examples/ipfilterβ Directory with examples. π
π SEE ALSO π
ipf(8)ipfstat(8)ippool.conf(5)ippool(8)
and thatβs the IPF(5) page, bestie π footer source/version says IPF(5) β date slot not shown here β so go slay your firewall and stay iconic π
β¨πΊ
π¦ where this page comes from
- freebsd 15.1: base 15.1
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms