Best practice for resetting (iptables) firewall state
philipp_subx at redfish-solutions.com
Tue Jul 11 22:25:55 PDT 2023
I'm working on a wrapper for using xt_asn and xt_geoip in xtables-addons, and was wondering about how best to solve certain problems.
Since, for now, I'll be using shell and UCI, I'm not going to maintain state (old/current vs. new/desired state) of the firewall so I won't be able to easily optimize firewall changes. The best I can do is detect the previous state, unload it, and then apply the new state.
(Yes, in an ideal world, I'd apply any new rules, then unapply any old rules that aren't being used any more...)
An example: I'm blocking the countries AA BB CC, but decide instead to block BB CC DD.
The optimal path would be:
1. Add block DD
2. Remove block AA
But that's more state than can be easily represented in a shell script that doesn't maintain any state beyond the current desired config which would be /etc/config/xyzzy.
So, the easiest/most straightforward solution I can think of is:
1. Run "iptables-save | grep '^-A input_wan_rule .* -j myprefix_.*' to find where my rules are hooked in (ditto for "forwarding_wan_rule");
2. Change '-A' to '-D' and delete the hooks;
3. Extract the rule name (target of -j above) and do "iptables -F rule_name" then "iptables -X rule_name" to remove the rule from the firewall
4. Generate the new rules based on the current contents of /etc/config/xyzzy
And yes, obviously the firewall is most vulnerable while it's being reconfigured, since rules are being non-atomically torn down and built back up again, and depending on the number of rules and the speed of the processor, this can be a non-negligible amount of time.
One observation: for the most part, the rules themselves while numerous are trivial. I don't have to worry about replacing an old version of a rule with a new but different version of that rule. If I'm blocking country AA, then the rule is present in a trivial and invariant form like:
-A forwarding_wan_rule -m geoip --src-cc AA -j fwr_AA
So the granularity of optimization would be:
1. If country X was in the old rules but not the new ones, delete it;
2. If country X is in both the old and new rules, don't do anything;
3. If country X wasn't in the old rules but is in the new ones, add it;
With the only complexity being in preserving the ordering (although since countries don't overlap, ordering shouldn't make any difference).
And another caveat: if the country database changes, and the CIDR list for country AA gets updated, then the only way to squirt the update into the kernel again by deletion/reinsertion, which invalidates (2) above because the country X isn't really 'invariant' in that case. Well, it could be if we were using ipset's and we could just apply the relevant deltas to the ipset, but we're not using ipsets.
So deleting all the rules and adding them back might be unavoidable in any case, without doing a lot more work (which would involve keeping "last" and "next" versions of the country databases, and detecting if they've changed, etc).
For atomicity, I suppose I could use a generational version of the rules, build generation N+1, insert the hook saying "use N+1", then delete the hook for N, then delete the chain for N... and "iptables -E ..." would help with renaming the new chain to the old chain after the old chain got deleted.
# construct new rules for countries BB, CC, DD...
iptables -N cc_BB_
iptables -A cc_BB_ -m limit --limit 1/sec --limit 5 -j LOG --log-level 4 --log-prefix "DROP cc BB: "
iptables -A cc_BB_ -j DROP
# create new container for new country rules
iptables -N country_rules_
# hook in the individual new country rules
iptables -A country_rules_ -m geoip --src-cc BB -g cc_BB_
iptables -A country_rules_ -m geoip --src-cc CC -g cc_CC_
# add the hook into the new country rules
iptables -A forwarding_wan_rule -j country_rules_
# clean up the old country rules
iptables -D country_rules -m geoip --src-cc AA -g cc_AA
iptables -D country_rules -m geoip --src-cc BB -g cc_BB
# remove the hook into the old country rules
iptables -D forwarding_wan_rule -j country_rules
# remove the old country rules
iptables -F cc_AA
iptables -F cc_BB
# rename the new rules into the old names
iptables -E country_rules_ country_rules
iptables -E cc_BB_ cc_BB_
iptables -E cc_CC_ cc_CC_
Anyone have any suggestions, tips & tricks, etc. to give on how best manage firewall state without too much extra complexity?
Also, how do you write an init.d wrapper that doesn't have a persistent foreground process, but handles "start", "stop", "reload", etc.
More information about the openwrt-devel