60

I need to add a rule to iptables to block connections to a tcp port from the Internet.

Since my script may be called multiple times and there is not a script to delete the rule, I want to check if an iptables rule already exists before inserting it - otherwise there will be a lot of dup rules in the INPUT chain.

How can I check if an iptables rule already exists?

Gaff
  • 18,569
  • 15
  • 57
  • 68
sevenever
  • 704
  • 1
  • 6
  • 8
  • Alternatively, use this wrapper, which provides idempotent iptables interaction : https://xyne.archlinux.ca/projects/idemptables/ –  Jan 16 '18 at 03:19

7 Answers7

67

There is a new -C --check option in recent iptables versions.

# iptables -C INPUT -p tcp --dport 8080 --jump ACCEPT
iptables: Bad rule (does a matching rule exist in that chain?).
# echo $?
1

# iptables -A INPUT -p tcp --dport 8080 --jump ACCEPT

# iptables -C INPUT -p tcp --dport 8080 --jump ACCEPT
# echo $?
0

For older iptables versions, I would use Garrett suggestion :

# iptables-save | grep -- "-A INPUT -p tcp -m tcp --dport 8080 -j ACCEPT"
Marc MAURICE
  • 904
  • 8
  • 5
13

The new -C option is not satisfactory, because it is open to a time-of-check-to-time-of-use (TOCTTOU) race condition. If two processes try to add the same rule at around the same time, -C will not protect them from adding it twice.

So, it is really no better than the grep solution. An accurate text processing job over the output of iptables-save can work as reliably as -C, since that output is a reliable snapshot of the state of the tables.

What is needed is an --ensure option which atomically checks and adds a rule only if it doesn't already exist. Moreover, it would be nice if the rule is moved to the correct position where a new rule would be inserted if it did not exist already (--ensure-move). For instance if iptables -I 1 is used to create a rule at the head of a chain, but that rule exists already in the seventh position, then the existing rule should move to the first position.

Without these features, I think a feasible workaround is to write a shell script loop based on this pseudo code:

while true ; do
  # delete all copies of the rule first

  while copies_of_rule_exist ; do
    iptables -D $RULE
  done

  # now try to add the rule

  iptables -A $RULE # or -I 

  # At this point there may be duplicates due to races.
  # Bail out of loop if there is exactly one, otherwise
  # start again.
  if exactly_one_copy_of_rule_exists ; then
    break;
  fi
done

This code could spin around; it does not guarantee that two or more racers will be out within a fixed number of iterations. Some randomized exponential backoff sleeps could be added to help with that.

Kaz
  • 2,631
  • 1
  • 18
  • 23
  • 2
    May I add that this command is rather not satisfactory for the simple reason that you have to pinpoint the exact way your rule was added. I made the test with my setup, and it couldn't find a rule because I didn't specify the exact keywords and values... – Fabien Haddadi Sep 28 '18 at 14:12
  • If you temporarily remove a rule then you might break something... – user541686 Apr 27 '19 at 09:54
9

This may seem a bit backwards, but it works for me - Try deleting the rule first.

iptables -D INPUT -s xxx.xxx.xxx.xxx -j DROP;

You should get a message similiar to:

iptables: Bad rule (does a matching rule exist in that chain?)

Then simply add your rule as normal:

iptables -A INPUT -s xxx.xxx.xxx.xxx -j DROP;

recurse
  • 256
  • 2
  • 5
  • 5
    Opening an unwanted hole in your firewall, just to simulate an idempotent command, is probably not a great idea. Sure, the hole is intended to be temporary, but it still improves an attacker's chances. And if something interrupts the addition of the replacement rule, then the hole could persist indefinitely. –  Jan 16 '18 at 03:23
  • 2
    Good point @sampablokuper: this strategy is probably only suitable for ACCEPT rules and not DROP ones, for security reasons – lucaferrario Feb 07 '18 at 18:04
  • 1
    I ended up doing the same thing! – warhansen Aug 06 '18 at 11:50
4

To avoid duplicate rules from your script, add below line.

iptables -C -INPUT -p tcp --dport 8080 --jump ACCEPT || iptables -A -INPUT -p tcp --dport 8080 --jump ACCEPT

First time when above command is run, we would observe below message

iptables: Bad rule (does a matching rule exist in that chain?).

This is just for information. But second half of the command would ensure to add the rule.

MarianD
  • 2,666
  • 1
  • 17
  • 26
4

Just list and search for it?

iptables --list | grep $ip

... or however you have the rule specified. If you use grep -q it won't output anything, and you can just check the return value with $?

Jonathon Reinhart
  • 3,384
  • 12
  • 35
  • 54
  • 3
    I would suggest `iptables-save|grep $ip` instead as it is a more easily parseable format, especially in a script. You can check the exact syntax of the command too if you want. – Garrett Nov 22 '11 at 04:21
  • 1
    Neither of these actually answers the question, because `iptables-save|grep $ip` could very well match *multiple* rules. Possibly `iptables-save` could be used to check for the *complete* rule specification, but that's still a bit of a hack: the format returned by `iptables-save` may not match exactly the rule in the script. `iptables-save` may generate the options in a different order, add things (like `-m tcp`), and so forth. – larsks Jun 24 '12 at 01:45
  • 1
    Bear in mind that `iptables --list` will try to resolve well-known ports. So make sure of that before you grep for a port. – Fabien Haddadi Sep 28 '18 at 14:13
0

How about first adding and then removing duplicates as described in https://serverfault.com/questions/628590/duplicate-iptable-rules. Easiest (for adjacent lines) seems to be

iptables-save | uniq | iptables-restore
serv-inc
  • 518
  • 1
  • 4
  • 18
0

I have to say for adding in a systemd exception 'ExecPreStart' for iptables for KVM and DOCKER to live happily with Linux bridging networks, this return -1 makes the docker systemd daemon FAIL. UGH....

https://bbs.archlinux.org/viewtopic.php?id=233727

Trying to delete an old table will make it fail and docker service wont start. Using the check method will fail. In the end YOU have to write

  1. a bash script that will check okayishly if iptables rule is present
  2. you have to hadd the execprestart to run that script instead of just trying to add the rule or check using only iptables command (god i wish it was implemmented better)
  3. Now you have to ansbile the god damn thing! Its the same with fockin cron jobs. You have to create so many files!

OK nvm the Check command works as intended.

Ansible loops over all the bridges I have and adds them which is neat. Crisis averted.

[Service]
ExecStartPre=/bin/bash -c '/usr/sbin/iptables -C FORWARD -p all -i vlanbr1 -o vlanbr1 -j ACCEPT || /usr/sbin/iptables -A FORWARD -p all -i vlanbr1 -o vlanbr1 -j ACCEPT'
ExecStartPre=/bin/bash -c '/usr/sbin/iptables -C FORWARD -p all -i vlanbr2 -o vlanbr2 -j ACCEPT || /usr/sbin/iptables -A FORWARD -p all -i vlanbr2 -o vlanbr2 -j ACCEPT'