67

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 duplicate rules in the INPUT chain.

How can I check if an iptables rule already exists?

AJM
  • 500
sevenever
  • 774

9 Answers9

75

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
  • 1,004
14

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.

AJM
  • 500
Kaz
  • 2,800
  • 1
  • 20
  • 24
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;
Giacomo1968
  • 58,727
recurse
  • 256
7

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.

Giacomo1968
  • 58,727
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 $?

1

I had this same problem, so I created a small function for bash shell script to handle adding rules only if they do not exists.

Note: This is a preventative measure to avoid duplicates rather than a reactive cleanup solution.

The benefit is that my script still looks like standard iptables commands and you can easily run the fw commands manually by just stripping off the prefixed underscore after copy/paste.

Obviously this code could be improved greatly with error checking and/or better general robustness, but it's succinct and works well enough as-is. YMMV

You can remove the echo statements to make the script silent. I put them in for code clarity sake here.

#!/bin/sh

_iptables() { #split the passed iptables command into the main component parts ARG="$@" #grab the entire rule definition passed to function ACTION=$1 #our action is the first parameter [IAD] RULE=${@:2} #grab all remaining members of the argarray starting at position 2.

#use the "check" functionality of recent iptables to see if rule is a dupe

iptables returns 1 if rule does not exist, 0 if it already exists

iptables -C $RULE 2>/dev/null if [ $? -eq 1 ];then #add rule since it doesn't already exist iptables $ARG echo "Added iptables rule: $RULE" else echo "FW rule not added (already exists?)" fi }

####################################################

Usage examples

####################################################

Syntax:

_iptables -[ACD] chain rule-specification [options]

#################################################### _iptables -I FORWARD -i br1 -o br0 -m state --state RELATED,ESTABLISHED -j ACCEPT _iptables -I FORWARD -i br0 -o br1 -j ACCEPT

_iptables -A POSTROUTING -t nat -o tun21 -j MASQUERADE

_iptables -I banned_ips -s 12.34.56.78 -j DROP _iptables -I banned_ips -s 23.45.67.89 -j DROP

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
  • 538
  • 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'
0

Checks if rule exists, and if not, adds it (specifying rule only once, almost without duplication):

rule="PREROUTING -p tcp --dport 22 -j REDIRECT --to-port 8022"; eval "iptables -t nat -C $rule >/dev/null 2>&1" || eval "iptables -t nat -A $rule"

>/dev/null 2>&1 suppresses message about non existing rule (iptables: No chain/target/match by that name.) for C (check) command.


For tests

To delete rule:

rule="PREROUTING -p tcp --dport 22 -j REDIRECT --to-port 8022"; `eval "iptables -t nat -D $rule"

To list rules for nat table:

iptables -t nat -L --line-number

Related question: