Well, if nftables is the way of the future, maybe I'm better off with BSD?

Enable pf in /etc/rc.conf:

pf_enable="YES"
pf_rules="/etc/pf.conf"
pflog_enable="YES"
pflog_logfile="/var/log/pflog"

Make a variable for use in rules:

myports = "{ 80 22 25 110 123 }"

Make a table:

table <localnet> { 192.168.1.0/24, 192.168.2.0/24 }
table <spammers> persist file "/path/china" file "/path/ukraine"

Basic ruleset:

block in all
pass out all keep state
pass in quick from <localnet> to any keep state
block in quick from <spammers> to any keep state
pass in on fxp0 proto tcp to any port $myports keep state

Basic syntax: Each rule line will start with action (either pass or block) followed [in this order] by:

[direction] in or out
[log] whether to log
[quick] don't process subsequent rules
[on interface] specific interface
[af] inet for IPv4 or inet6 for IPv6
[proto protocol] tcp, udp, icmp, icmp6, a name from /etc/protocols, proto# 0 to 255, a list
[from src_addr [port src_port]] source addr and port
[to dst_addr [port dst_port]] dest addr and port
[flags tcp_flags] SYN, ACK, etc
[state] probably "no" or "keep"

Check a table:

pfctl -t localnet -T show