Multiple set matches with Linux's iptables 'ipset' extension
Recently, Luke Atchew approached me on Twitter to ask if I had any ideas for solving an ipset challenge. Suppose that you have a set of origin hosts, a set of destination servers, and you want to allow traffic from the origin hosts to the destination servers while blocking all other traffic. As Luke noted, most ipset examples, including mine, are about blocking access to or from a set of hosts; they don't have a matrix setup like Luke's.
One obvious option is a complex multi-rule setup, where you have a
series of rules that try to block and accept more and more of the
traffic that you want; for example, you can start out by blocking
all traffic to '
! -match-set Destinations dst'. This gets complex
if you have other rules involved, though. Another option is one of
ipset's more complicated set types, such as
then you get into hassles populating and maintaining the set (since
it's basically the cross product of your allowed source and destination
As Luke Atchew discovered while working on this, all of this complexity is unnecessary because you can match against multiple ipset sets in the same iptables rule. It is perfectly legitimate to write rules like:
iptables -A FORWARD -m set --match-set Locals src -m set --match-set Remotes dst -j ACCEPT iptables -A FORWARD -m set ! --match-set Locals src -m set --match-set Remotes dst -j DROP
(These rules use different keys, here the source and destination
IPs, but it's probably legitimate to have several
that reuse the same key. You might need a source IP to be in an
'allowed' set and not in a 'temporarily blocked' set, for example.)
An important note here is that you absolutely have to use two '
set' arguments, one before each
--match-set. If you leave the
second one out you will get the somewhat obscure error message
iptables v1.4.21: --match-set can be specified only once', which
may mislead you into believing that this isn't supported at all.
This is a really easy mistake to make because it sure smells like
-m set is surplus; after all, you already told
you wanted to use the ipset extension here.
(I assume that the second '
-m set' is causing the parser to start
setting up a new internal ipset matching operation instead of
accumulating more options for the first one.)