Thank you for your suggestion, Jon. I have revised the code to include curly braces around the variable name, but I opted not to enclose the variable in double quotes. Your point about the potential risks associated with having spaces within variable contents is valid, but in this particular context, using quotes can introduce problems.
The iptables -C command expects its arguments in a specific sequence: the chain name followed by the rule specification. If the variable is quoted, the entirety of its content is interpreted as a single argument. Consequently, iptables would misconstrue the complete rule specification as the chain’s name. This can trigger an error stating that the chain name is too long (exceeding the limit of 28 characters): iptables v1.8.9 (legacy): chain name ' IPTVFORWARD -i red0 -d 224.0.0.0/4 -j ACCEPT' too long (must be under 29 chars).
Bash scripting presents so many nuanced aspects that makes my head spinning.
Can you include the entire bash script? With the single quotes, iptables thinks the chain name is really long. So something else seems wrong in the script.
#!/bin/sh
# Used for private firewall rules
RULE_INPUT="-I IPTVINPUT -i red0 -d 224.0.0.0/4 -j ACCEPT"
RULE_FORWARD="-I IPTVFORWARD -i red0 -d 224.0.0.0/4 -j ACCEPT"
# See how we were called.
case "$1" in
start)
# add your 'start' rules here
# start igmpproxy
if ! pgrep -x "igmpproxy" > /dev/null
then
/usr/sbin/igmpproxy /etc/igmpproxy.conf &
else
echo "igmpproxy is already running"
fi
# Ensure the rules do not already exist
if ! /sbin/iptables -C ${RULE_INPUT} 2> /dev/null; then
/sbin/iptables ${RULE_INPUT}
fi
if ! /sbin/iptables -C ${RULE_FORWARD} 2> /dev/null; then
/sbin/iptables ${RULE_FORWARD}
fi
;;
stop)
# add your 'stop' rules here
# Only delete rules if they exist
if /sbin/iptables -C ${RULE_INPUT} 2> /dev/null; then
/sbin/iptables -D ${RULE_INPUT}
fi
if /sbin/iptables -C ${RULE_FORWARD} 2> /dev/null; then
/sbin/iptables -D ${RULE_FORWARD}
fi
# stop igmpproxy
if pgrep -x "igmpproxy" > /dev/null
then
killall igmpproxy || echo "Failed to kill igmpproxy"
else
echo "No igmpproxy process found to kill"
fi
;;
reload)
$0 stop
sleep 1
$0 start
# add your 'reload' rules here
;;
*)
echo "Usage: $0 {start|stop|reload}"
;;
esac
Your variables RULE_xxx contain the option -I, which is good for the rule generation.
But for check and delete operation the line expands to iptables -C -I IPTV_xxx ... and iptables -D -I IPTV_xxx ...
which is wrong.
Defining the variables as
if ! /sbin/iptables -C ${RULE_INPUT} 2> /dev/null; then
/sbin/iptables -I ${RULE_INPUT}
fi
if ! /sbin/iptables -C ${RULE_FORWARD} 2> /dev/null; then
/sbin/iptables -I ${RULE_FORWARD}
fi
( similiar for the delete/stop operation ) should do the job.
BTW: for test purposes it helps not pipe the output to /dev/null.
A final note, If the stderr is not redirected to /dev/null, the iptables -C test will also generate the error message “Bad rule…”. Below I start with the rule not present in the chain, the test generates the error message, then I create the rule, test again and the error message is not shown (as expected).
This behavior of iptables -C should have been expected, but it surprised me. I was expecting it would just return either 0 if successful or non 0 if unsuccessful, but it first gives the error and then exit with its return value.
[root@ipfire cfusco]# iptables -C IPTVINPUT -i red0 -d 224.0.0.0/4 -j ACCEPT && echo "Rule exists" || echo "Rule does not exist"
iptables: Bad rule (does a matching rule exist in that chain?).
Rule does not exist
[root@ipfire cfusco]# iptables -I IPTVINPUT -i red0 -d 224.0.0.0/4 -j ACCEPT
[root@ipfire cfusco]# iptables -C IPTVINPUT -i red0 -d 224.0.0.0/4 -j ACCEPT && echo "Rule exists" || echo "Rule does not exist"
Rule exists
This behaviour is wanted!
iptables is a CLI command. Entered from a terminal, errors have to be reported on the terminal. Any other behaviour is an error, IMO.
Using the IPTables::IPv4 module in Perl, for example, implements the interface ( return code 0/1, error message ). But IPFire uses the good old CLI interface.
@jon , do you think using an array can help manage variables with white spaces more effectively, while avoiding issues caused by the combination of double quotes and iptables -C?
#!/bin/bash
# Used for private firewall rules
RULE_INPUT=(IPTVINPUT -i red0 -d 224.0.0.0/4 -j ACCEPT)
RULE_FORWARD=(IPTVFORWARD -i red0 -d 224.0.0.0/4 -j ACCEPT)
# See how we were called.
case "$1" in
start)
# add your 'start' rules here
# begin igmpproxy
/usr/sbin/igmpproxy /etc/igmpproxy.conf &
# Ensure the rules when they do not already exist
if ! /sbin/iptables -C "${RULE_INPUT[@]}" 2>/dev/null; then
/sbin/iptables -I "${RULE_INPUT[@]}"
fi
if ! /sbin/iptables -C "${RULE_FORWARD[@]}" 2>/dev/null; then
/sbin/iptables -I "${RULE_FORWARD[@]}"
fi
# end igmpproxy
;;
stop)
# add your 'stop' rules here
# begin igmpproxy
# Only delete rules if they exist
if /sbin/iptables -C "${RULE_INPUT[@]}" 2>/dev/null; then
/sbin/iptables -D "${RULE_INPUT[@]}"
fi
if /sbin/iptables -C "${RULE_FORWARD[@]}" 2>/dev/null; then
/sbin/iptables -D "${RULE_FORWARD[@]}"
fi
killall igmpproxy || echo "No igmpproxy process found to kill"
# end igmpproxy
;;
reload)
$0 stop
sleep 1
$0 start
;;
*)
echo "Usage: $0 {start|stop|reload}"
;;
esac
EDIT: maybe we should split this part of the thread and close the Original one.
To avoid the error for the first call of restart ( stop / start sequence ) one could insert a check for existence of the chain. But this is bit too much effort, IMO.
I see your point, Bashism means no portability. Do you see any other reason?
Yes, see for yourself, shown below inline so it is self contained and it can’t be attributed to other parts of the script.
[root@ipfire ~]# RULE_INPUT="IPTVINPUT -i red0 -d 224.0.0.0/4 -j ACCEPT";/sbin/iptables -C ${RULE_INPUT} && echo "Yes" || echo "NO"
Yes
[root@ipfire ~]# RULE_INPUT="IPTVINPUT -i red0 -d 224.0.0.0/4 -j ACCEPT";/sbin/iptables -C "${RULE_INPUT}"
iptables v1.8.9 (legacy): chain name `IPTVINPUT -i red0 -d 224.0.0.0/4 -j ACCEPT' too long (must be under 29 chars
Try `iptables -h' or 'iptables --help' for more information.
NO
It is pretty clear jon, if you put double quote, the whole thing is seen as the name of the chain (spelled out by the error message), because iptables -C expects the first parameters to be the name, if you put a double quote in the variable, the entire string IS the first parameter. This is a limitation of the double quote, you have to check what the calling program expects to receive. It is nuanced.