You could use the PSD feature of firewall filter rules.
According to the Wiki entry:
PSD - Attempts to detect TCP and UDP scans. Parameters are in following format
- WeightThreshold - total weight of the latest TCP/UDP packets with different destination ports coming from the same host to be treated as port scan sequence
- DelayThreshold - delay for the packets with different destination ports coming from the same host to be treated as possible port scan subsequence
- LowPortWeight - weight of the packets with privileged (<=1024) destination port
- HighPortWeight - weight of the packet with non-priviliged destination port
What this means is that for every packet that arrives at a new port, it will add some value to a "score" for each IP source. This score is increased by the "LowPortWeight" if the port is a priveleged port (like 25, 22, 80, 443, etc) and by the HighPortWeight for all other ports. DelayThreshold states how long this "score" is kept, so if a request comes in for a privileged port, and the LowPortWeight is 3 (default) then the score goes up by three, for 'threshold' seconds. (defaults to 3 seconds). If the IP source's total score is below "WeightThreshold" then the match returns true, else returns false.
You have to also set the rule to match TCP or UDP in order for this option to be available on the rule.
So to implement this, your forward chain should have some rules near the end like this:
protocol=tcp psd=21,3s,3,1 action=add-src-to-address-list address-list=blacklist address-list-timeout=1d log=yes log-prefix="tcp port scan detected"
protocol=udp psd=21,3s,3,1 action=add-src-to-address-list address-list=blacklist address-list-timeout=1d log=yes log-prefix="udp port scan detected"
And of course somewhere early in the filter chain, there should be a rule which drops packets with src-address-list=blacklist
EDIT: Note that you may want to have a different action for the "outgoing" connections than to blacklist them - or maybe you do want to blacklist them.... but the main thing to note is that the given rules apply to ALL forwarded traffic, regardless of direction, so if you want to narrow the scope of this port scan detection (that's what PSD stands for) then you can also specify in-interface, out-interface, src-address-list=!whitelist , etc.