/etc/npf.conf
# Associate a dynamic list of IPs, both IPv4 and IPv6, to default WLAN interface[1]
$wifi_if = ifaddrs(athn0)
# Introduce 2 container lists for blacklisted IPs[2]
table <blacklist> type hash file "/etc/npf_blacklist"
table <suspicious> type tree dynamic
# Introduce 2 variables to list opened TCP and UDP ports[3]
$services_tcp = { http, https, smtp, smtps, domain, 587, 6000 }
$services_udp = { domain, ntp, 6000, 51413 }
# Variable $LAN represents range of IPs for the local network
$LAN = { 192.168.1.0/24 }
# Load ICMP application-level gateway[4]
alg "icmp"
# Introduce a pseudo-device for logging events[5]
procedure "log" {
log: npflog0
}
# Introduce a set of 'normalization' options[6]
procedure "norm" {
normalize: "random-id", "min-ttl" 512, "max-mss" 1432, "no-df"
}
group default {
#Pass everything on loop interface
pass final on lo0 all
#Block blacklisted IPs
block in final from <blacklist>
#Block IPs marked as 'suspicious'
block in final from <suspicious>
#Allow all outgoing traffic
pass stateful out final all
#Only allow selected ICMP types
pass in final proto icmp icmp-type timxceed all
pass in final proto icmp icmp-type unreach all
pass in final proto icmp icmp-type echoreply all
pass in final proto icmp icmp-type sourcequench all
pass in final proto icmp icmp-type paramprob all
# Allow SSH/(T)FTP/MPD/TigerVNC \
# connections on LAN and log them [8]
pass stateful in final proto tcp from \
$LAN to $wifi_if port ftp apply "log"
pass stateful in final proto tcp from \
$LAN to $wifi_if port ssh apply "log"
pass stateful in final proto udp from \
$LAN to $wifi_if port tftp apply "log"
pass stateful in final proto tcp from \
$LAN to $wifi_if port 6600 apply "log"
pass stateful in final proto tcp from \
$LAN to $wifi_if port 6600 apply "log"
pass stateful in final proto tcp from \
$LAN to $wifi_if port 5901 apply "log"
# Allow DHCP requests
pass out final proto udp from any port \
bootpc to any port bootps
pass in final proto udp from any port \
bootps to any port bootpc
pass in final proto udp from any port \
bootps to 255.255.255.0 port bootpc
#Allow incoming TCP/UDP packets \
# on selected ports applying "norm" procedure
pass stateful in final proto tcp to $wifi_if \
port $services_tcp apply "norm"
pass stateful in final proto udp to $wifi_if \
port $services_udp apply "norm"
# Allow Traceroute
pass stateful in final proto udp to $wifi_if \
port 33434-33600 apply"norm"
# Reject everything else [9]
block return-rst in final proto tcp all apply "log"
block return-icmp in final proto udp all apply "log"
block return in final all apply "log"
}
Notes
a) Rule are processed from top to bottom, meaning a packet is kept waiting until a matching rule is found; if no pass criteria matches a packet, final rules shall block it (see above)
b) group default has to be set mandatory; we could have defined a second "home" group on wifi_if
containing most rules, while letting group default only pass on lo0, but this just sounded redundant, as we only need a single group for this config
[1] ifaddrs() is needed for DHCP; NPF will capture the runtime list of addresses, reflecting
any changes to the interface, including attachig/detaching. inet4/6 functions are meant for static IP use. family keyword can be used in combination of a filtering rule to explicitly select an IP address type.
[2]
- table
<blacklist>
type hash
provides amortised O(1) lookup time; a good option for sets which do not change significantly (e.g. a listed inside a file
, kept across sessions )
echo any potentially harmful IP address to /etc/npf_blacklist
and notify NPF of changes by running
$ npfctl table "blacklist" flush
- table
<suspicious>
tree
is a good option when the set changes often and requires prefix matching.
dynamic
indicates table is emptied every time configuration is reloaded. ideal to temporary block suspicious addresses, which you can do by
$ npfctl table "suspicious" add 10.0.1.0/24
[3] 587 is SMTP over TLS, 6000 is X server, 51413 is Transmission torrent. Consult services(5)
man page and /etc/services
database
[4] Allows to find an active connection by looking at the ICMP payload, and to perform NAT translation of the ICMP payload. Good to keep loaded for when in need of dynamic mapping as certain application layer protocols are not compatible with NAT and require translation outside layers 3 and 4. no mapping is implied in the current config
[5] The npflog0 interface will be auto-created once the configuration is loaded. The log packets will be written to /var/log/npflog0.pcap
file by npfd(8) daemon.
[6] Modify packets according to the specified normalization options. What those options actually do respectively is
Randomize the IPv4 ID parameter.
Enforce a minimum value for the IPv4 Time To
Live (TTL) parameter.
Enforce a maximum value for the MSS on TCP
packets. Typically, for "MSS clamping".
Remove the Don't Fragment (DF) flag from IPv4
packets.
[8] Better to disable it on a public network, especially Open WEP
[9] returning TCP RESET or ICMP proto/port UNREACHABLE messages on TCP connection and UDP streams respectively, while logging rejected connections to npflog0*
Enabling services
touch /etc/npf_blacklist
cat <<EOT>> /etc/rc.conf
npf=YES
npfd=YES
npfd_flags="-d 60"
EOT
service npf start
service npfd start
testing config
npfctl show
table <blacklist> type hash
table <suspicious> type tree
procedure "norm"
procedure "log"
group # id="1"
pass final on lo0 all # id="2"
block in final from <blacklist> # id="3"
block in final from <suspicious> # id="4"
pass stateful out final all # id="5"
pass in final proto icmp icmp-type 11 # id="6"
pass in final proto icmp icmp-type 3 # id="7"
pass in final proto icmp icmp-type 0 # id="8"
pass in final proto icmp icmp-type 4 # id="9"
pass in final proto icmp icmp-type 12 # id="a"
pass stateful in final family inet4 proto tcp flags S/FSRA from 192.168.1.0/24 to <.ifnet-athn0> port 21 apply "log" # id="b"
pass stateful in final family inet4 proto tcp flags S/FSRA from 192.168.1.0/24 to <.ifnet-athn0> port 22 apply "log" # id="c"
pass stateful in final family inet4 proto udp from 192.168.1.0/24 to <.ifnet-athn0> port 69 apply "log" # id="d"
pass stateful in final family inet4 proto tcp flags S/FSRA from 192.168.1.0/24 to <.ifnet-athn0> port 6600 apply "log" # id="e"
pass stateful in final family inet4 proto tcp flags S/FSRA from 192.168.1.0/24 to <.ifnet-athn0> port 6600 apply "log" # id="f"
pass stateful in final family inet4 proto tcp flags S/FSRA from 192.168.1.0/24 to <.ifnet-athn0> port 5901 apply "log" # id="10"
pass out final proto udp from any port 68 to any port 67 # id="11"
pass in final proto udp from any port 67 to any port 68 # id="12"
pass in final family inet4 proto udp from any port 67 to 255.255.255.0 port 68 # id="13"
pass stateful in final proto tcp flags S/FSRA to <.ifnet-athn0> { port 80, port 443, port 25, port 465, port 53, port 587, port 6000 } apply "norm" # id="14"
pass stateful in final proto udp to <.ifnet-athn0> { port 53, port 123, port 6000, port 51413 } apply "norm" # id="15"
pass stateful in final proto udp to <.ifnet-athn0> port 33434:33600 apply "norm" # id="16"
block return-rst in final all apply "log" # id="17"
block return-icmp in final all apply "log" # id="18"
block return in final all apply "log" # id="19"
Display real time logs of inbound packets that were blocked/passed on $wifi_if
tcpdump -enr /var/log/npflog0.pcap
keeping in mind the rule telling 'allow FTP connections from LAN' was 11th on the list...
02:05:06.493690 rule 11.rules.0/0(match): pass in on ???: 192.168.1.13.21 > 192.168.1.2.36542: Flags [P.], seq 1785:1835, ack 67, win 4197, options [nop,nop,TS val 1 ecr 8800950], length 50: FTP: [!ftp]
This is me just connecting on port 21 from Android with AndFTP (smartphone connected to home router)
further reading