ldx / python-iptables

Python bindings for iptables

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

'python-iptables: match "state" already registered' when creating rule with match state in ip4 and ip6

opened this issue · comments

I'm applying the rules in the python console as root:

>>> rule_d = {'conntrack': {'ctstate': 'ESTABLISHED,RELATED'}, 'target': 'ACCEPT'}
>>> iptc.easy.add_rule('filter', 'INPUT', rule_d, ipv6=False)

to ipv4 and after that to ipv6 with

>>> iptc.easy.add_rule('filter', 'INPUT', rule_d, ipv6=True)

what gives me the error:
python-iptables: match "state" already registered
and throws me out of the python console.

I traced the error down manually as far as I could with the most recent call last:

encode_iptc_rule(rule_d, ipv6)
  in easy.py -> add_rule
_iptc_setmatch(iptc_rule, name, value)
  in easy.py -> encode_iptc_rule
iptc_match = iptc_rule.create_match(name)
  in easy.py -> _iptc_set_match
match = Match(self, name=name, revision=revision)
  in ip4tc.py -> Rule -> create_match
module = self._xt.find_match(name)
  in ip4tc.py -> Match -> __init__

My iptables version is: iptables v1.8.2 (legacy)

#112 and #135 seem to be related though they are closed

I can't reproduce the problem in my installation

$ sudo ipython3
Python 3.5.3 (default, Sep 27 2018, 17:25:39)
Type "copyright", "credits" or "license" for more information.

IPython 5.1.0 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: import iptc

In [2]: rule_d = {'conntrack': {'ctstate': 'ESTABLISHED,RELATED'}, 'target': 'ACCEPT'}

In [3]: iptc.easy.add_rule('filter', 'INPUT', rule_d, ipv6=False)

In [4]: iptc.easy.add_rule('filter', 'INPUT', rule_d, ipv6=True)

Do you really want to exit ([y]/n)? y

$ sudo iptables -S INPUT
-P INPUT ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

$ sudo ip6tables -S INPUT
-P INPUT ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

$ uname -ar
Linux hypervisor 4.19.0-0.bpo.4-amd64 #1 SMP Debian 4.19.28-2~bpo9+1 (2019-03-27) x86_64 GNU/Linux

$ lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description:    Debian GNU/Linux 9.9 (stretch)
Release:        9.9
Codename:       stretch

$ sudo iptables -V
iptables v1.6.0

My environment:

uname -ar
Linux cronos 5.1.9-arch1-1-ARCH #1 SMP PREEMPT Tue Jun 11 16:18:09 UTC 2019 x86_64 GNU/Linux

iptables -V
iptables v1.8.2 (legacy)

python -V
Python 3.7.3
or
python-V
Python 3.5.7

I tried with both python versions in clean venvs. Seems to more an iptables problem.

UPDATE: When I first apply the rule to ipv4 and leave the interpreter, then start the interpreter again and add the same rule to ipv6 seem to work. Problem is I'm using a script to apply my rules and I would like to apply the rule during the same session to both ipv4 and ipv6.

I've ran the python interpreter with strace. If it helps here's the output from the moment I typed
iptc.easy.add_rule('filter', 'INPUT', rule_d, ipv6=True)

python.strace.since_add_rule.txt

There's a lot of junk of the python interpreter in it but the error is written on line 763 and the call to libxtables is on line 548

Same happens here on Alpine 3.10 but not Alpine 3.9

import iptc

if not iptc.Chain(iptc.Table(iptc.Table.FILTER), "MAILCOW") in iptc.Table(iptc.Table.FILTER).chains:
  iptc.Table(iptc.Table.FILTER).create_chain("MAILCOW")

for c in ['FORWARD', 'INPUT']:
  chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), c)
  rule = iptc.Rule()
  rule.src = '0.0.0.0/0'
  rule.dst = '0.0.0.0/0'
  target = iptc.Target(rule, "MAILCOW")
  rule.target = target
  if rule not in chain.rules:
    chain.insert_rule(rule)

if not iptc.Chain(iptc.Table6(iptc.Table6.FILTER), "MAILCOW") in iptc.Table6(iptc.Table6.FILTER).chains:
  iptc.Table6(iptc.Table6.FILTER).create_chain("MAILCOW")

for c in ['FORWARD', 'INPUT']:
  chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), c)
  rule = iptc.Rule6()
  rule.src = '::/0'
  rule.dst = '::/0'
  target = iptc.Target(rule, "MAILCOW")
  rule.target = target
  if rule not in chain.rules:
    chain.insert_rule(rule)

Results in python-iptables: match "state" already registered. :(

Oh, it works when you downgrade iptables and ip6tables to the Alpine 3.9 versions:

iptables (1.6.2-r1 instead of 1.8.3-r0)
ip6tables (1.6.2-r1 instead of 1.8.3-r0)

Looks like this is the commit that introduces the check that makes the program exit:

http://git.netfilter.org/iptables/commit/libxtables/xtables.c?id=3b2530ce7a0d6aa3bee687bf0167bb4908c7b798

I tracked this down to commit f210f14. Setting xtables_matches to NULL is not enough to reset the find_matches logic anymore.

I feel like the above commit is a bit of a hack. Can we instead cache protocol-independent extensions using a protocol-independent key?