From 26c8ae106dc417971e723950d50fb45e603db757 Mon Sep 17 00:00:00 2001 From: ldx Date: Sat, 7 Sep 2013 12:14:53 +0200 Subject: [PATCH 001/287] Use consistent parameter names. --- iptc/ip4tc.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index bba4ec3..3c1b505 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -455,8 +455,8 @@ def __eq__(self, match): return True return False - def __ne__(self, rule): - return not self.__eq__(rule) + def __ne__(self, match): + return not self.__eq__(match) def _final_check(self): self._xt.final_check_match(self._module) @@ -583,8 +583,8 @@ def __eq__(self, targ): return True return False - def __ne__(self, rule): - return not self.__eq__(rule) + def __ne__(self, target): + return not self.__eq__(target) def _allocate_buffer(self, target): self._target_buf = _malloc(self.size) From 2f1ed442992befc891c1d3e9eb304009fb37b498 Mon Sep 17 00:00:00 2001 From: ldx Date: Sun, 8 Sep 2013 14:05:54 +0200 Subject: [PATCH 002/287] Fix memory leak in Target. Move freeing of Target._target_buf to another class, thus getting rid of __del__() in Target. This enables Python to garbage collect Target objects - before this gcing Target was not possible because of the circular reference between Rule and Target. --- iptc/ip4tc.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 3c1b505..e710882 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -390,6 +390,20 @@ def _set_rule(self, rule): """The rule this target or match belong to.""" +class _Buffer(object): + def __init__(self, size=0): + if size > 0: + self.buffer = _malloc(size) + if self.buffer is None: + raise Exception("Can't allocate buffer") + else: + self.buffer = None + + def __del__(self): + if self.buffer is not None: + _free(self.buffer) + + class Match(IPTCModule): """Matches are extensions which can match for special header fields or other attributes of a packet. @@ -556,15 +570,11 @@ def __init__(self, rule, name=None, target=None, revision=None): else: self._revision = self._module.revision - self._allocate_buffer(target) + self._create_buffer(target) if self._is_standard_target(): self.standard_target = name - def __del__(self): - if getattr(self, "_target_buf", None) and self._target_buf is not None: - _free(self._target_buf) - def __eq__(self, targ): basesz = ct.sizeof(xt_entry_target) if (self.target.u.target_size != targ.target.u.target_size or @@ -586,10 +596,9 @@ def __eq__(self, targ): def __ne__(self, target): return not self.__eq__(target) - def _allocate_buffer(self, target): - self._target_buf = _malloc(self.size) - if self._target_buf is None: - raise Exception("Can't allocate target buffer") + def _create_buffer(self, target): + self._buffer = _Buffer(self.size) + self._target_buf = self._buffer.buffer if target: ct.memmove(self._target_buf, ct.byref(target), self.size) self._update_pointers() @@ -610,6 +619,7 @@ def _parse(self, argv, inv, entry): self._xt.parse_target(argv, inv, self._module, entry, ct.cast(self._ptrptr, ct.POINTER(ct.c_void_p))) self._target_buf = ct.cast(self._module.t, ct.POINTER(ct.c_ubyte)) + self._buffer.buffer = self._target_buf self._update_pointers() def _get_size(self): From 5e332a53b65bbada0f954f6e87e445b965f02122 Mon Sep 17 00:00:00 2001 From: ldx Date: Sun, 8 Sep 2013 14:08:43 +0200 Subject: [PATCH 003/287] Release matches after use. Matches are allocated dynamically for the same type except the first instance. Free them via _Buffer when the python object is gced. --- iptc/ip4tc.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index e710882..b83d209 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -450,6 +450,8 @@ def __init__(self, rule, name=None, match=None, revision=None): self._revision = revision else: self._revision = self._module.revision + if self._module.next is not None: + self._store_buffer(module) self._match_buf = (ct.c_ubyte * self.size)() if match: @@ -472,6 +474,10 @@ def __eq__(self, match): def __ne__(self, match): return not self.__eq__(match) + def _store_buffer(self, module): + self._buffer = _Buffer() + self._buffer.buffer = ct.cast(module, ct.POINTER(ct.c_ubyte)) + def _final_check(self): self._xt.final_check_match(self._module) From 1dd825c410531e1ad0ec6b10f9ec899d40012a2e Mon Sep 17 00:00:00 2001 From: ldx Date: Thu, 19 Sep 2013 21:32:51 +0200 Subject: [PATCH 004/287] Add /usr/lib/iptables as possible XTABLES_LIBDIR. --- iptc/xtables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index 11a96dc..3f11aab 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -683,7 +683,7 @@ class XTablesError(Exception): _xtables_libdir = os.getenv("XTABLES_LIBDIR") if _xtables_libdir is None: import os.path - for xtdir in ["/lib/xtables", "/usr/lib/xtables", + for xtdir in ["/lib/xtables", "/usr/lib/xtables", "/usr/lib/iptables", "/usr/local/lib/xtables"]: if os.path.isdir(xtdir): _xtables_libdir = xtdir From 097579e70c020d79af0e83ae4d59c81f7e7feba5 Mon Sep 17 00:00:00 2001 From: ldx Date: Tue, 29 Oct 2013 23:58:26 +0100 Subject: [PATCH 005/287] Use dict as Table cache. For both IPv4 and IPv6, use a dict instead of a weakref cache. This makes the cache a permanent one, and avoids the overhead of creating/freeing tables too often. --- iptc/ip4tc.py | 2 +- iptc/ip6tc.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index b83d209..6520e86 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -1318,7 +1318,7 @@ class Table(object): ALL = ["filter", "mangle", "raw", "nat"] """This is the constant for all tables.""" - _cache = weakref.WeakValueDictionary() + _cache = dict() def __new__(cls, name, autocommit=None): obj = Table._cache.get(name, None) diff --git a/iptc/ip6tc.py b/iptc/ip6tc.py index 6e16756..dbf034d 100644 --- a/iptc/ip6tc.py +++ b/iptc/ip6tc.py @@ -569,7 +569,7 @@ class Table6(Table): ALL = ["filter", "mangle", "raw", "security"] """This is the constant for all tables.""" - _cache = weakref.WeakValueDictionary() + _cache = dict() def __new__(cls, name, autocommit=None): obj = Table6._cache.get(name, None) From 80b18c57f451242af25dc26df53d270b609654f8 Mon Sep 17 00:00:00 2001 From: saurabh hirani Date: Wed, 6 Nov 2013 20:52:27 +0530 Subject: [PATCH 006/287] update Table._free to use autocommit check --- iptc/ip4tc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 6520e86..df947d1 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -1362,7 +1362,8 @@ def _free(self, ignore_exc=True): if self._handle is None: raise IPTCError("table is not initialized") try: - self.commit() + if self.autocommit: + self.commit() except IPTCError, e: if not ignore_exc: raise e From 714d89dbd459f251d8f207c26f306db6e8905dfe Mon Sep 17 00:00:00 2001 From: ldx Date: Sun, 10 Nov 2013 01:31:20 +0100 Subject: [PATCH 007/287] Reset autocommit flag after test. --- iptc/test/test_iptc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iptc/test/test_iptc.py b/iptc/test/test_iptc.py index 7441fc4..ba27bd5 100755 --- a/iptc/test/test_iptc.py +++ b/iptc/test/test_iptc.py @@ -18,10 +18,10 @@ def _check_chains(testcase, *chains): class TestTable6(unittest.TestCase): def setUp(self): - pass + self.autocommit = iptc.Table(iptc.Table.FILTER).autocommit def tearDown(self): - pass + iptc.Table(iptc.Table.FILTER, self.autocommit) def test_table6(self): filt = None From add95fc523466ddf0bafcc7cfda26b5b85462b2b Mon Sep 17 00:00:00 2001 From: ldx Date: Sun, 10 Nov 2013 02:39:39 +0100 Subject: [PATCH 008/287] Update documentation. --- doc/examples.rst | 141 ++++++++++++++++++++++++++++++++++++----------- doc/intro.rst | 84 +++++----------------------- 2 files changed, 124 insertions(+), 101 deletions(-) diff --git a/doc/examples.rst b/doc/examples.rst index 4e7d302..bf87d72 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -1,12 +1,12 @@ Examples ======== -Introduction ------------- +Rules +----- In ``python-iptables``, you usually first create a rule, and set any source/destination address, in/out interface and protocol specifiers, for -example: +example:: >>> import iptc >>> rule = iptc.Rule() @@ -20,7 +20,7 @@ source IP address of 192.168.1.0/255.255.255.0. A rule may contain matches and a target. A match is like a filter matching certain packet attributes, while a target tells what to do with the packet (drop it, accept it, transform it somehow, etc). One can create a match or -target via a Rule: +target via a Rule:: >>> rule = iptc.Rule() >>> m = rule.create_match("tcp") @@ -29,7 +29,7 @@ target via a Rule: Match and target parameters can be changed after creating them. It is also perfectly valid to create a match or target via instantiating them with their constructor, but you still need a rule and you have to add the matches -and the target to their rule manually: +and the target to their rule manually:: >>> rule = iptc.Rule() >>> match = iptc.Match(rule, "tcp") @@ -38,14 +38,14 @@ and the target to their rule manually: >>> rule.target = target Any parameters a match or target might take can be set via the attributes of -the object. To set the destination port for a TCP match: +the object. To set the destination port for a TCP match:: >>> rule = iptc.Rule() >>> rule.protocol = "tcp" >>> match = rule.create_match("tcp") >>> match.dport = "80" -To set up a rule that matches packets marked with 0xff: +To set up a rule that matches packets marked with 0xff:: >>> rule = iptc.Rule() >>> rule.protocol = "tcp" @@ -55,18 +55,84 @@ To set up a rule that matches packets marked with 0xff: Parameters are always strings. When you are ready constructing your rule, add them to the chain you want it -to show up in: +to show up in:: >>> chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), "INPUT") >>> chain.insert_rule(rule) This will put your rule into the INPUT chain in the filter table. -Simple rule with standard target --------------------------------- +Chains and tables +----------------- -Reject packets with source address ``127.0.0.1/255.0.0.0`` coming in on any of -the eth interfaces: +You can of course also check what a rule's source/destination address, +in/out inteface etc is. To print out all rules in the FILTER table:: + + >>> import iptc + >>> table = iptc.Table(iptc.Table.FILTER) + >>> for chain in table.chains: + >>> print "=======================" + >>> print "Chain ", chain.name + >>> for rule in chain.rules: + >>> print "Rule", "proto:", rule.protocol, "src:", rule.src, "dst:", \ + >>> rule.dst, "in:", rule.in_interface, "out:", rule.out_interface, + >>> print "Matches:", + >>> for match in rule.matches: + >>> print match.name, + >>> print "Target:", + >>> print rule.target.name + >>> print "=======================" + +As you see in the code snippet above, rules are organized into chains, and +chains are in tables. You have a fixed set of tables; for IPv4:: + +* FILTER, +* NAT, +* MANGLE and +* RAW. + +For IPv6 the tables are:: + +* FILTER, +* MANGLE, +* RAW and +* SECURITY. + +To access a table:: + + >>> import iptc + >>> table = iptc.Table(iptc.Table.FILTER) + >>> print table.name + filter + +To create a new chain in the FILTER table:: + + >>> import iptc + >>> table = iptc.Table(iptc.Table.FILTER) + >>> chain = table.create_chain("testchain") + + $ sudo iptables -L -n + [...] + Chain testchain (0 references) + target prot opt source destination + +To access an existing chain:: + + >>> import iptc + >>> table = iptc.Table(iptc.Table.FILTER) + >>> chain = iptc.Chain(table, "INPUT") + >>> chain.name + 'INPUT' + >>> len(chain.rules) + 10 + >>> + +More about matches and targets +------------------------------ + +There are basic targets, such as ``DROP`` and ``ACCEPT``. E.g. to reject +packets with source address ``127.0.0.1/255.0.0.0`` coming in on any of the +``eth`` interfaces:: >>> import iptc >>> chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), "INPUT") @@ -77,25 +143,22 @@ the eth interfaces: >>> rule.target = target >>> chain.insert_rule(rule) -Simple rule not using any match extensions ------------------------------------------- +To instantiate a target or match, we can either create an object like above, +or use the ``rule.create_target(target_name)`` and +``rule.create_match(match_name)`` methods. For example, in the code above +target could have been created as:: -Inserting a rule to NAT TCP packets going out via ``eth0``: + >>> target = rule.create_target("DROP") - >>> import iptc - >>> chain = iptc.Chain(iptc.Table(iptc.Table.NAT), "POSTROUTING") - >>> rule = iptc.Rule() - >>> rule.protocol = "tcp" - >>> rule.out_interface = "eth0" - >>> target = iptc.Target(rule, "MASQUERADE") - >>> target.to_ports = "1234" +instead of:: + + >>> target = iptc.Target(rule, "DROP") >>> rule.target = target - >>> chain.insert_rule(rule) -Rule using the udp match extension ----------------------------------- +The former also adds the match or target to the rule, saving a call. -Mark packets going to ``192.168.1.2`` UDP port ``1234`` with ``0xffff``: +Another example, using a target which takes parameters. Let's mark packets +going to ``192.168.1.2`` UDP port ``1234`` with ``0xffff``:: >>> import iptc >>> chain = iptc.Chain(iptc.Table(iptc.Table.MANGLE), "PREROUTING") @@ -110,13 +173,25 @@ Mark packets going to ``192.168.1.2`` UDP port ``1234`` with ``0xffff``: >>> rule.target = target >>> chain.insert_rule(rule) -Multiple matches with iprange ------------------------------ +Matches are optional (specifying a target is mandatory). E.g. to insert a rule +to NAT TCP packets going out via ``eth0``:: -Now we will add multiple matches to a rule. This one is the -``python-iptables`` equivalent of the following iptables command: + >>> import iptc + >>> chain = iptc.Chain(iptc.Table(iptc.Table.NAT), "POSTROUTING") + >>> rule = iptc.Rule() + >>> rule.protocol = "tcp" + >>> rule.out_interface = "eth0" + >>> target = iptc.Target(rule, "MASQUERADE") + >>> target.to_ports = "1234" + >>> rule.target = target + >>> chain.insert_rule(rule) + +Here only the properties of the rule decide whether the rule will be applied +to a packet. -# iptables -A INPUT -p tcp –destination-port 22 -m iprange –src-range 192.168.1.100-192.168.1.200 –dst-range 172.22.33.106 -j DROP +Matches are optional, but we can add multiple matches to a rule. In the +following example we will do that, using the ``iprange`` and the ``tcp`` +matches:: >>> import iptc >>> rule = iptc.Rule() @@ -131,3 +206,7 @@ Now we will add multiple matches to a rule. This one is the >>> rule.target = iptc.Target(rule, "DROP") >>> chain = iptc.Chain(iptc.Table.(iptc.Table.FILTER), "INPUT") >>> chain.insert_rule(rule) + +This is the ``python-iptables`` equivalent of the following iptables command:: + + # iptables -A INPUT -p tcp –destination-port 22 -m iprange –src-range 192.168.1.100-192.168.1.200 –dst-range 172.22.33.106 -j DROP diff --git a/doc/intro.rst b/doc/intro.rst index 63446f1..33dce84 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -19,10 +19,17 @@ manpage puts it: rule specifies what to do with a packet that matches. This is called a `target`, which may be a jump to a user-defined chain in the same table. -``Python-iptables`` provides python bindings to iptables under Linux. -Interoperability with iptables is achieved via using the iptables C libraries -(``libiptc``, ``libxtables``, and the iptables extensions), not calling the -iptables binary and parsing its output. +``Python-iptables`` provides a pythonesque wrapper via python bindings to +iptables under Linux. Interoperability with iptables is achieved via using +the iptables C libraries (``libiptc``, ``libxtables``, and the iptables +extensions), not calling the iptables binary and parsing its output. It is +meant primarily for dynamic and/or complex routers and firewalls, where rules +are often updated or changed, or Python programs wish to interface with the +Linux iptables framework.. + +|buildstatus| + +.. |buildstatus| image:: https://travis-ci.org/ldx/python-iptables.png?branch=master Compiling and installing ------------------------ @@ -54,67 +61,7 @@ Now you can run the tests:: WARNING: this test will manipulate iptables rules. Don't do this on a production machine. Would you like to continue? y/n y - test_table6 (iptc.test.test_iptc.TestTable6) ... ok - test_refresh (iptc.test.test_iptc.TestTable) ... ok - test_table (iptc.test.test_iptc.TestTable) ... ok - test_builtin_chain (iptc.test.test_iptc.TestChain) ... ok - test_chain (iptc.test.test_iptc.TestChain) ... ok - test_chain_counters (iptc.test.test_iptc.TestChain) ... ok - test_chain_policy (iptc.test.test_iptc.TestChain) ... ok - test_chains (iptc.test.test_iptc.TestChain) ... ok - test_create_chain (iptc.test.test_iptc.TestChain) ... ok - test_is_chain (iptc.test.test_iptc.TestChain) ... ok - test_rule_address (iptc.test.test_iptc.TestRule6) ... ok - test_rule_compare (iptc.test.test_iptc.TestRule6) ... ok - test_rule_interface (iptc.test.test_iptc.TestRule6) ... ok - test_rule_iterate (iptc.test.test_iptc.TestRule6) ... ok - test_rule_protocol (iptc.test.test_iptc.TestRule6) ... ok - test_rule_standard_target (iptc.test.test_iptc.TestRule6) ... ok - test_rule_address (iptc.test.test_iptc.TestRule) ... ok - test_rule_compare (iptc.test.test_iptc.TestRule) ... ok - test_rule_fragment (iptc.test.test_iptc.TestRule) ... ok - test_rule_interface (iptc.test.test_iptc.TestRule) ... ok - test_rule_iterate (iptc.test.test_iptc.TestRule) ... ok - test_rule_protocol (iptc.test.test_iptc.TestRule) ... ok - test_rule_standard_target (iptc.test.test_iptc.TestRule) ... ok - - ---------------------------------------------------------------------- - Ran 23 tests in 0.013s - - OK - test_match_compare (iptc.test.test_matches.TestMatch) ... ok - test_match_create (iptc.test.test_matches.TestMatch) ... ok - test_match_parameters (iptc.test.test_matches.TestMatch) ... ok - test_udp_insert (iptc.test.test_matches.TestXTUdpMatch) ... ok - test_udp_port (iptc.test.test_matches.TestXTUdpMatch) ... ok - test_mark (iptc.test.test_matches.TestXTMarkMatch) ... ok - test_mark_insert (iptc.test.test_matches.TestXTMarkMatch) ... ok - test_limit (iptc.test.test_matches.TestXTLimitMatch) ... ok - test_limit_insert (iptc.test.test_matches.TestXTLimitMatch) ... ok - test_comment (iptc.test.test_matches.TestCommentMatch) ... ok - test_iprange (iptc.test.test_matches.TestIprangeMatch) ... ok - test_iprange_tcpdport (iptc.test.test_matches.TestIprangeMatch) ... ok - - ---------------------------------------------------------------------- - Ran 12 tests in 0.024s - - OK - test_target_compare (iptc.test.test_targets.TestTarget) ... ok - test_target_create (iptc.test.test_targets.TestTarget) ... ok - test_target_parameters (iptc.test.test_targets.TestTarget) ... ok - test_insert (iptc.test.test_targets.TestXTClusteripTarget) ... ok - test_mode (iptc.test.test_targets.TestXTClusteripTarget) ... ok - test_insert (iptc.test.test_targets.TestIPTRedirectTarget) ... ok - test_mode (iptc.test.test_targets.TestIPTRedirectTarget) ... ok - test_insert (iptc.test.test_targets.TestXTTosTarget) ... ok - test_mode (iptc.test.test_targets.TestXTTosTarget) ... ok - test_insert (iptc.test.test_targets.TestIPTMasqueradeTarget) ... ok - test_mode (iptc.test.test_targets.TestIPTMasqueradeTarget) ... ok - - ---------------------------------------------------------------------- - Ran 11 tests in 0.015s - - OK + [...] The ``PATH=$PATH`` part is necessary after ``sudo`` if you have installed into a ``virtualenv``, since ``sudo`` will reset your environment to a system @@ -136,9 +83,6 @@ The basic iptables framework and all the match/target extensions are supported by ``python-iptables``, including IPv4 and IPv6 ones. All IPv4 and IPv6 tables are supported as well. -Contact -------- - -ldx (at) nilvec.com +Full documentation with API reference is available here_. -http://nilvec.com +.. _here: http://ldx.github.com/python-iptables/ From ebe4332fb2a8206a0fd45c7fa75bce319beeac74 Mon Sep 17 00:00:00 2001 From: ldx Date: Sun, 10 Nov 2013 02:58:16 +0100 Subject: [PATCH 009/287] Generate README.md from RST docs. --- README.md | 301 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 298 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5ec49f6..0525482 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,300 @@ -[![Build Status](https://travis-ci.org/ldx/python-iptables.png?branch=master)](https://travis-ci.org/ldx/python-iptables) +Introduction +============ -Python-iptables is a pythonesque wrapper around the Linux iptables/ip6tables facility. It is meant primarily for dynamic and/or complex firewalls, where rules are often updated or changed. Python-iptables makes it possible to use Python to parse or change rules without the need to spawn processes to execute an iptables command. +About python-iptables +--------------------- -See [http://ldx.github.com/python-iptables/](http://ldx.github.com/python-iptables/) for documentation. +**Iptables** is the tool that is used to manage **netfilter**, the +standard packet filtering and manipulation framework under Linux. As the +iptables manpage puts it: + +> Iptables is used to set up, maintain, and inspect the tables of IPv4 +> packet filter rules in the Linux kernel. Several different tables may +> be defined. +> +> Each table contains a number of built-in chains and may also contain +> user- defined chains. +> +> Each chain is a list of rules which can match a set of packets. Each +> rule specifies what to do with a packet that matches. This is called a +> target, which may be a jump to a user-defined chain in the same table. + +`Python-iptables` provides a pythonesque wrapper via python bindings to +iptables under Linux. Interoperability with iptables is achieved via +using the iptables C libraries (`libiptc`, `libxtables`, and the +iptables extensions), not calling the iptables binary and parsing its +output. It is meant primarily for dynamic and/or complex routers and +firewalls, where rules are often updated or changed, or Python programs +wish to interface with the Linux iptables framework.. + +![buildstatus](https://travis-ci.org/ldx/python-iptables.png?branch=master) + +Compiling and installing +------------------------ + +First make sure you have iptables installed (most Linux distributions +install it by default). `Python-iptables` needs the shared libraries +`libiptc.so` and `libxtables.so` coming with iptables, they are +installed in `/lib` on Ubuntu. + +You can compile `python-iptables` in the usual distutils way: + + % cd python-iptables + % python setup.py build + +If you like, `python-iptables` can also be installed into a +`virtualenv`: + + % mkvirtualenv python-iptables + % python setup.py install + +If you install `python-iptables` as a system package, make sure the +directory where `distutils` installs shared libraries is in the dynamic +linker's search path (it's in `/etc/ld.so.conf` or in one of the files +in the folder `/etc/ld.co.conf.d`). Under Ubuntu `distutils` by default +installs into `/usr/local/lib`. + +Now you can run the tests: + + % sudo PATH=$PATH ./test.py + WARNING: this test will manipulate iptables rules. + Don't do this on a production machine. + Would you like to continue? y/n y + [...] + +The `PATH=$PATH` part is necessary after `sudo` if you have installed +into a `virtualenv`, since `sudo` will reset your environment to a +system setting otherwise.. + +Once everything is in place you can fire up python to check whether the +package can be imported: + + % sudo PATH=$PATH python + >>> import iptc + >>> + +Of course you need to be root to be able to use iptables. + +What is supported +----------------- + +The basic iptables framework and all the match/target extensions are +supported by `python-iptables`, including IPv4 and IPv6 ones. All IPv4 +and IPv6 tables are supported as well. + +Full documentation with API reference is available +[here](http://ldx.github.com/python-iptables/). + +Examples +======== + +Rules +----- + +In `python-iptables`, you usually first create a rule, and set any +source/destination address, in/out interface and protocol specifiers, +for example: + + >>> import iptc + >>> rule = iptc.Rule() + >>> rule.in_interface = "eth0" + >>> rule.src = "192.168.1.0/255.255.255.0" + >>> rule.protocol = "tcp" + +This creates a rule that will match TCP packets coming in on eth0, with +a source IP address of 192.168.1.0/255.255.255.0. + +A rule may contain matches and a target. A match is like a filter +matching certain packet attributes, while a target tells what to do with +the packet (drop it, accept it, transform it somehow, etc). One can +create a match or target via a Rule: + + >>> rule = iptc.Rule() + >>> m = rule.create_match("tcp") + >>> t = rule.create_target("DROP") + +Match and target parameters can be changed after creating them. It is +also perfectly valid to create a match or target via instantiating them +with their constructor, but you still need a rule and you have to add +the matches and the target to their rule manually: + + >>> rule = iptc.Rule() + >>> match = iptc.Match(rule, "tcp") + >>> target = iptc.Target(rule, "DROP") + >>> rule.add_match(match) + >>> rule.target = target + +Any parameters a match or target might take can be set via the +attributes of the object. To set the destination port for a TCP match: + + >>> rule = iptc.Rule() + >>> rule.protocol = "tcp" + >>> match = rule.create_match("tcp") + >>> match.dport = "80" + +To set up a rule that matches packets marked with 0xff: + + >>> rule = iptc.Rule() + >>> rule.protocol = "tcp" + >>> match = rule.create_match("mark") + >>> match.mark = "0xff" + +Parameters are always strings. + +When you are ready constructing your rule, add them to the chain you +want it to show up in: + + >>> chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), "INPUT") + >>> chain.insert_rule(rule) + +This will put your rule into the INPUT chain in the filter table. + +Chains and tables +----------------- + +You can of course also check what a rule's source/destination address, +in/out inteface etc is. To print out all rules in the FILTER table: + + >>> import iptc + >>> table = iptc.Table(iptc.Table.FILTER) + >>> for chain in table.chains: + >>> print "=======================" + >>> print "Chain ", chain.name + >>> for rule in chain.rules: + >>> print "Rule", "proto:", rule.protocol, "src:", rule.src, "dst:", \ + >>> rule.dst, "in:", rule.in_interface, "out:", rule.out_interface, + >>> print "Matches:", + >>> for match in rule.matches: + >>> print match.name, + >>> print "Target:", + >>> print rule.target.name + >>> print "=======================" + +As you see in the code snippet above, rules are organized into chains, +and chains are in tables. You have a fixed set of tables; for IPv4: + +- FILTER, +- NAT, +- MANGLE and +- RAW. + +For IPv6 the tables are: + +- FILTER, +- MANGLE, +- RAW and +- SECURITY. + +To access a table: + + >>> import iptc + >>> table = iptc.Table(iptc.Table.FILTER) + >>> print table.name + filter + +To create a new chain in the FILTER table: + + >>> import iptc + >>> table = iptc.Table(iptc.Table.FILTER) + >>> chain = table.create_chain("testchain") + + $ sudo iptables -L -n + [...] + Chain testchain (0 references) + target prot opt source destination + +To access an existing chain: + + >>> import iptc + >>> table = iptc.Table(iptc.Table.FILTER) + >>> chain = iptc.Chain(table, "INPUT") + >>> chain.name + 'INPUT' + >>> len(chain.rules) + 10 + >>> + +More about matches and targets +------------------------------ + +There are basic targets, such as `DROP` and `ACCEPT`. E.g. to reject +packets with source address `127.0.0.1/255.0.0.0` coming in on any of +the `eth` interfaces: + + >>> import iptc + >>> chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), "INPUT") + >>> rule = iptc.Rule() + >>> rule.in_interface = "eth+" + >>> rule.src = "127.0.0.1/255.0.0.0" + >>> target = iptc.Target(rule, "DROP") + >>> rule.target = target + >>> chain.insert_rule(rule) + +To instantiate a target or match, we can either create an object like +above, or use the `rule.create_target(target_name)` and +`rule.create_match(match_name)` methods. For example, in the code above +target could have been created as: + + >>> target = rule.create_target("DROP") + +instead of: + + >>> target = iptc.Target(rule, "DROP") + >>> rule.target = target + +The former also adds the match or target to the rule, saving a call. + +Another example, using a target which takes parameters. Let's mark +packets going to `192.168.1.2` UDP port `1234` with `0xffff`: + + >>> import iptc + >>> chain = iptc.Chain(iptc.Table(iptc.Table.MANGLE), "PREROUTING") + >>> rule = iptc.Rule() + >>> rule.dst = "192.168.1.2" + >>> rule.protocol = "udp" + >>> match = iptc.Match(rule, "udp") + >>> match.dport = "1234" + >>> rule.add_match(match) + >>> target = iptc.Target(rule, "MARK") + >>> target.set_mark = "0xffff" + >>> rule.target = target + >>> chain.insert_rule(rule) + +Matches are optional (specifying a target is mandatory). E.g. to insert +a rule to NAT TCP packets going out via `eth0`: + + >>> import iptc + >>> chain = iptc.Chain(iptc.Table(iptc.Table.NAT), "POSTROUTING") + >>> rule = iptc.Rule() + >>> rule.protocol = "tcp" + >>> rule.out_interface = "eth0" + >>> target = iptc.Target(rule, "MASQUERADE") + >>> target.to_ports = "1234" + >>> rule.target = target + >>> chain.insert_rule(rule) + +Here only the properties of the rule decide whether the rule will be +applied to a packet. + +Matches are optional, but we can add multiple matches to a rule. In the +following example we will do that, using the `iprange` and the `tcp` +matches: + + >>> import iptc + >>> rule = iptc.Rule() + >>> rule.protocol = "tcp" + >>> match = iptc.Match(rule, "tcp") + >>> match.dport = "22" + >>> rule.add_match(match) + >>> match = iptc.Match(rule, "iprange") + >>> match.src_range = "192.168.1.100-192.168.1.200" + >>> match.dst_range = "172.22.33.106" + >>> rule.add_match(match) + >>> rule.target = iptc.Target(rule, "DROP") + >>> chain = iptc.Chain(iptc.Table.(iptc.Table.FILTER), "INPUT") + >>> chain.insert_rule(rule) + +This is the `python-iptables` equivalent of the following iptables +command: + + # iptables -A INPUT -p tcp –destination-port 22 -m iprange –src-range 192.168.1.100-192.168.1.200 –dst-range 172.22.33.106 -j DROP From bbdfdcd637eed6ce76565ef28550df92b244ffdc Mon Sep 17 00:00:00 2001 From: ldx Date: Sun, 10 Nov 2013 23:07:16 +0100 Subject: [PATCH 010/287] Release v0.2.0. --- doc/conf.py | 2 +- iptc/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 29e4d76..ac645b0 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -48,7 +48,7 @@ # The short X.Y version. version = '0.2.0' # The full version, including alpha/beta/rc tags. -release = '0.2.0-dev' +release = '0.2.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/iptc/version.py b/iptc/version.py index 954537d..0b35708 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.2.0-dev" +__version__ = "0.2.0" From b210822d33ee0a39f5d9b23deac63a4f785aefff Mon Sep 17 00:00:00 2001 From: ldx Date: Sun, 10 Nov 2013 23:36:59 +0100 Subject: [PATCH 011/287] Update classifiers in setup.py. --- setup.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index e1caaaa..04c038a 100644 --- a/setup.py +++ b/setup.py @@ -22,11 +22,19 @@ ext_modules=[Extension("libxtwrapper", ["libxtwrapper/wrapper.c"])], classifiers=[ - "Development Status :: 4 - Beta", + "Development Status :: 5 - Production/Stable", + "Environment :: Console", "Intended Audience :: Developers", - "License :: OSI Approved :: Apache License, Version 2.0", + "Intended Audience :: Information Technology", + "Intended Audience :: System Administrators", + "Intended Audience :: Telecommunications Industry", + "License :: OSI Approved :: Apache Software License", "Natural Language :: English", - "Topic :: Networking", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python", + "Topic :: Software Development :: Libraries", + "Topic :: System :: Networking :: Firewalls", + "Topic :: System :: Systems Administration", ], license="Apache License, Version 2.0", ) From 7a2bdb6c09b42c58851a877c474975e83f776f4e Mon Sep 17 00:00:00 2001 From: ldx Date: Sat, 16 Nov 2013 15:32:28 +0100 Subject: [PATCH 012/287] Fix stdout closing/reopening around save(). The save() callback prints rules to stdout in the extension. Make sure we close and then reopen not just the low-level fd, but the Python file as well around it. --- iptc/ip4tc.py | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index df947d1..9c7c8ad 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -6,6 +6,7 @@ import shlex import socket import struct +import sys import weakref from util import find_library @@ -296,23 +297,28 @@ def save(self, name): return self._save(name, self.rule.get_ip()) def _save(self, name, ip): - if self._module and self._module.save: - # redirect C stdout to a pipe and read back the output of m->save - pipes = os.pipe() - saved_out = os.dup(1) - os.dup2(pipes[1], 1) - self._xt.save(self._module, ip, self._ptr) - buf = os.read(pipes[0], 1024) - os.dup2(saved_out, 1) - os.close(pipes[0]) - os.close(pipes[1]) - os.close(saved_out) - if name: - return self._get_value(buf, name) - else: - return self._get_all_values(buf) - else: + if not self._module or not self._module.save: return None + # redirect C stdout to a pipe and read back the output of m->save + fd = sys.stdout.fileno() + with os.fdopen(os.dup(fd), 'w') as old_stdout: + try: + pipes = os.pipe() + sys.stdout.close() + os.dup2(pipes[1], fd) + sys.stdout = os.fdopen(fd, 'w') + self._xt.save(self._module, ip, self._ptr) + buf = os.read(pipes[0], 1024) + os.close(pipes[0]) + os.close(pipes[1]) + if name: + return self._get_value(buf, name) + else: + return self._get_all_values(buf) + finally: + sys.stdout.close() + os.dup2(old_stdout.fileno(), fd) + sys.stdout = os.fdopen(fd, 'w') def _get_all_values(self, buf): table = {} # variable -> (value, inverted) From 53e894c339167064e1ffe3be46da2f86226c5432 Mon Sep 17 00:00:00 2001 From: ldx Date: Sat, 16 Nov 2013 16:22:29 +0100 Subject: [PATCH 013/287] Start work on python3 support. --- iptc/__init__.py | 6 +++--- iptc/ip4tc.py | 18 +++++++++--------- iptc/ip6tc.py | 16 ++++++++-------- iptc/xtables.py | 4 ++-- setup.py | 2 +- test.py | 8 ++++---- 6 files changed, 27 insertions(+), 27 deletions(-) diff --git a/iptc/__init__.py b/iptc/__init__.py index 8544917..2a02d4b 100644 --- a/iptc/__init__.py +++ b/iptc/__init__.py @@ -7,9 +7,9 @@ .. moduleauthor:: Nilvec """ -from ip4tc import (is_table_available, Table, Chain, Rule, Match, Target, +from iptc.ip4tc import (is_table_available, Table, Chain, Rule, Match, Target, Policy, IPTCError) -from ip6tc import is_table6_available, Table6, Rule6 -from xtables import XTablesError +from iptc.ip6tc import is_table6_available, Table6, Rule6 +from iptc.xtables import XTablesError __all__ = [] diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 9c7c8ad..c40a8b0 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -9,8 +9,8 @@ import sys import weakref -from util import find_library -from xtables import (XT_INV_PROTO, NFPROTO_IPV4, XTablesError, xtables, +from iptc.util import find_library +from iptc.xtables import (XT_INV_PROTO, NFPROTO_IPV4, XTablesError, xtables, xt_align, xt_counters, xt_entry_target, xt_entry_match) __all__ = ["Table", "Chain", "Rule", "Match", "Target", "Policy", "IPTCError"] @@ -362,7 +362,7 @@ def get_all_parameters(self): return params def _update_parameters(self): - for k, v in self.get_all_parameters().iteritems(): + for k, v in self.get_all_parameters().items(): self.__setattr__(k, v) def __setattr__(self, name, value): @@ -1313,15 +1313,15 @@ class Table(object): low-level details from the user. """ - FILTER = "filter" + FILTER = b"filter" """This is the constant for the filter table.""" - MANGLE = "mangle" + MANGLE = b"mangle" """This is the constant for the mangle table.""" - RAW = "raw" + RAW = b"raw" """This is the constant for the raw table.""" - NAT = "nat" + NAT = b"nat" """This is the constant for the nat table.""" - ALL = ["filter", "mangle", "raw", "nat"] + ALL = [b"filter", b"mangle", b"raw", b"nat"] """This is the constant for all tables.""" _cache = dict() @@ -1370,7 +1370,7 @@ def _free(self, ignore_exc=True): try: if self.autocommit: self.commit() - except IPTCError, e: + except IPTCError as e: if not ignore_exc: raise e finally: diff --git a/iptc/ip6tc.py b/iptc/ip6tc.py index dbf034d..a310cf7 100644 --- a/iptc/ip6tc.py +++ b/iptc/ip6tc.py @@ -4,9 +4,9 @@ import socket import weakref -from ip4tc import Rule, Table, IPTCError -from util import find_library, load_kernel -from xtables import (XT_INV_PROTO, NFPROTO_IPV6, xt_align, xt_counters) +from iptc.ip4tc import Rule, Table, IPTCError +from iptc.util import find_library, load_kernel +from iptc.xtables import XT_INV_PROTO, NFPROTO_IPV6, xt_align, xt_counters __all__ = ["Table6", "Rule6"] @@ -558,15 +558,15 @@ class Table6(Table): low-level details from the user. """ - FILTER = "filter" + FILTER = b"filter" """This is the constant for the filter table.""" - MANGLE = "mangle" + MANGLE = b"mangle" """This is the constant for the mangle table.""" - RAW = "raw" + RAW = b"raw" """This is the constant for the raw table.""" - SECURITY = "security" + SECURITY = b"security" """This is the constant for the security table.""" - ALL = ["filter", "mangle", "raw", "security"] + ALL = [b"filter", b"mangle", b"raw", b"security"] """This is the constant for all tables.""" _cache = dict() diff --git a/iptc/xtables.py b/iptc/xtables.py index 3f11aab..0be6a39 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -4,9 +4,9 @@ import os import sys import weakref -import version -from util import find_library +from iptc import version +from iptc.util import find_library XT_INV_PROTO = 0x40 # invert the sense of PROTO diff --git a/setup.py b/setup.py index 04c038a..55d2690 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ # make pyflakes happy __pkgname__ = None __version__ = None -execfile("iptc/version.py") +exec(open("iptc/version.py").read()) # build/install python-iptables setup( diff --git a/test.py b/test.py index f426612..9397abe 100755 --- a/test.py +++ b/test.py @@ -3,11 +3,11 @@ import sys -print "WARNING: this test will manipulate iptables rules." -print "Don't do this on a production machine." +print("WARNING: this test will manipulate iptables rules.") +print("Don't do this on a production machine.") while True: - print "Would you like to continue? y/n", - answer = raw_input() + print("Would you like to continue? y/n") + answer = input() if answer in "yYnN" and len(answer) == 1: break if answer in "nN": From 7aedd73ec45988d4cd6e8e4c097439d0a671ff59 Mon Sep 17 00:00:00 2001 From: ldx Date: Mon, 18 Nov 2013 00:08:22 +0100 Subject: [PATCH 014/287] Update README. --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0525482..f1a5f00 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,15 @@ wish to interface with the Linux iptables framework.. ![buildstatus](https://travis-ci.org/ldx/python-iptables.png?branch=master) -Compiling and installing ------------------------- +Installing via pip +------------------ + +The usual way: + + pip install --upgrade python-iptables + +Compiling from source +--------------------- First make sure you have iptables installed (most Linux distributions install it by default). `Python-iptables` needs the shared libraries From 5b7d122e49878e4b6bb420615f3d434a4d13311f Mon Sep 17 00:00:00 2001 From: ldx Date: Mon, 18 Nov 2013 00:11:28 +0100 Subject: [PATCH 015/287] Update URL. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 04c038a..ea75d3a 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ description="Python bindings for iptables", author="Nilvec", author_email="nilvec@nilvec.com", - url="http://nilvec.com/", + url="https://github.com/ldx/python-iptables", packages=["iptc"], package_dir={"iptc": "iptc"}, ext_modules=[Extension("libxtwrapper", From 72ef8ed01f876366958642f706f13ee95c36b7ea Mon Sep 17 00:00:00 2001 From: ldx Date: Sun, 24 Nov 2013 12:05:59 +0100 Subject: [PATCH 016/287] Refactor IPTCModule._save(). --- iptc/ip4tc.py | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 9c7c8ad..accd549 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -293,10 +293,7 @@ def final_check(self): def _final_check(self): raise NotImplementedError() - def save(self, name): - return self._save(name, self.rule.get_ip()) - - def _save(self, name, ip): + def _get_saved_buf(self, ip): if not self._module or not self._module.save: return None # redirect C stdout to a pipe and read back the output of m->save @@ -311,15 +308,26 @@ def _save(self, name, ip): buf = os.read(pipes[0], 1024) os.close(pipes[0]) os.close(pipes[1]) - if name: - return self._get_value(buf, name) - else: - return self._get_all_values(buf) + return buf finally: sys.stdout.close() os.dup2(old_stdout.fileno(), fd) sys.stdout = os.fdopen(fd, 'w') + def save(self, name): + return self._save(name, self.rule.get_ip()) + + def _save(self, name, ip): + buf = self._get_saved_buf(ip) + if buf is None: + return None + if not self._module or not self._module.save: + return None + if name: + return self._get_value(buf, name) + else: + return self._get_all_values(buf) + def _get_all_values(self, buf): table = {} # variable -> (value, inverted) res = re.findall(IPTCModule.pattern, buf) @@ -343,22 +351,11 @@ def _get_value(self, buf, name): def get_all_parameters(self): params = {} ip = self.rule.get_ip() - if self._module and self._module.save: - # redirect C stdout to a pipe and read back the output of m->save - pipes = os.pipe() - saved_out = os.dup(1) - os.dup2(pipes[1], 1) - self._xt.save(self._module, ip, self._ptr) - buf = os.read(pipes[0], 1024) - os.dup2(saved_out, 1) - os.close(pipes[0]) - os.close(pipes[1]) - os.close(saved_out) - + buf = self._get_saved_buf(ip) + if buf is not None: res = re.findall(IPTCModule.pattern, buf) for x in res: params[x[1]] = "%s%s" % ((x[0] or x[2]) and "!" or "", x[3]) - return params def _update_parameters(self): From af51e8a2442577ae8fc386d697a152fd877a5a7b Mon Sep 17 00:00:00 2001 From: ldx Date: Sun, 24 Nov 2013 12:07:58 +0100 Subject: [PATCH 017/287] Fix #45. --- iptc/xtables.py | 1 + 1 file changed, 1 insertion(+) diff --git a/iptc/xtables.py b/iptc/xtables.py index 3f11aab..9f112bc 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -684,6 +684,7 @@ class XTablesError(Exception): if _xtables_libdir is None: import os.path for xtdir in ["/lib/xtables", "/usr/lib/xtables", "/usr/lib/iptables", + "/usr/lib64/xtables", "/usr/lib64/iptables", "/usr/local/lib/xtables"]: if os.path.isdir(xtdir): _xtables_libdir = xtdir From 04eaff082fe968ad8b02b95e029c5d8854995804 Mon Sep 17 00:00:00 2001 From: ldx Date: Sun, 24 Nov 2013 12:15:01 +0100 Subject: [PATCH 018/287] Start 0.3.0-dev. --- doc/conf.py | 4 ++-- iptc/version.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index ac645b0..a216f15 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -46,9 +46,9 @@ # built documents. # # The short X.Y version. -version = '0.2.0' +version = '0.3.0' # The full version, including alpha/beta/rc tags. -release = '0.2.0' +release = '0.3.0-dev' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/iptc/version.py b/iptc/version.py index 0b35708..35615ed 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.2.0" +__version__ = "0.3.0-dev" From 296310c057d88fc12a660a7181d7a29651cdcf03 Mon Sep 17 00:00:00 2001 From: ldx Date: Mon, 25 Nov 2013 20:46:19 +0100 Subject: [PATCH 019/287] Fix the method signature of the alias() callback. --- iptc/xtables.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index 9f112bc..68e706b 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -386,8 +386,7 @@ class _xtables_match_v10(ct.Structure): ("save", ct.CFUNCTYPE(None, ct.c_void_p, ct.POINTER(xt_entry_match))), # Print match name or alias - ("alias", ct.CFUNCTYPE(ct.c_char_p, ct.c_void_p, - ct.POINTER(xt_entry_match))), + ("alias", ct.CFUNCTYPE(ct.c_char_p, ct.POINTER(xt_entry_match))), # pointer to list of extra command-line options ("extra_opts", ct.POINTER(option)), @@ -636,8 +635,7 @@ class _xtables_target_v10(ct.Structure): ("save", ct.CFUNCTYPE(None, ct.c_void_p, ct.POINTER(xt_entry_target))), # Print target name or alias - ("alias", ct.CFUNCTYPE(ct.c_char_p, ct.c_void_p, - ct.POINTER(xt_entry_target))), + ("alias", ct.CFUNCTYPE(ct.c_char_p, ct.POINTER(xt_entry_target))), # pointer to list of extra command-line options ("extra_opts", ct.POINTER(option)), From 2bf0827b65ac3321ad45122d927eed488dc71cdd Mon Sep 17 00:00:00 2001 From: ldx Date: Mon, 25 Nov 2013 20:45:20 +0100 Subject: [PATCH 020/287] Handle extensions aliases. --- iptc/ip4tc.py | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index accd549..d165941 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -456,6 +456,8 @@ def __init__(self, rule, name=None, match=None, revision=None): if self._module.next is not None: self._store_buffer(module) + self._check_alias(module[0], match) + self._match_buf = (ct.c_ubyte * self.size)() if match: ct.memmove(ct.byref(self._match_buf), ct.byref(match), self.size) @@ -477,6 +479,19 @@ def __eq__(self, match): def __ne__(self, match): return not self.__eq__(match) + def _check_alias(self, module, match): + # This is ugly, but there are extensions using an alias name. Check if + # that's the case, and load that extension as well if necessary. It + # will be used to parse parameters, since the 'real' extension + # probably won't understand them. + if getattr(module, "alias", None) is not None and module.alias: + self._alias_name = module.alias(match) + alias = self._xt.find_match(self._alias_name) + if not alias: + raise XTablesError("can't find alias match %s" % + (self._alias_name)) + self._alias = alias[0] + def _store_buffer(self, module): self._buffer = _Buffer() self._buffer.buffer = ct.cast(module, ct.POINTER(ct.c_ubyte)) @@ -485,7 +500,11 @@ def _final_check(self): self._xt.final_check_match(self._module) def _parse(self, argv, inv, entry): - self._xt.parse_match(argv, inv, self._module, entry, + if self._alias is not None: + module = self._alias + else: + module = self._module + self._xt.parse_match(argv, inv, module, entry, ct.cast(self._ptrptr, ct.POINTER(ct.c_void_p))) def _get_size(self): @@ -505,6 +524,8 @@ def _update_pointers(self): self._ptrptr = ct.cast(ct.pointer(self._ptr), ct.POINTER(ct.POINTER(xt_entry_match))) self._module.m = self._ptr + if self._alias is not None: + self._alias.m = self._ptr def reset(self): """Reset the match. @@ -579,6 +600,8 @@ def __init__(self, rule, name=None, target=None, revision=None): else: self._revision = self._module.revision + self._check_alias(module[0], target) + self._create_buffer(target) if self._is_standard_target(): @@ -605,6 +628,19 @@ def __eq__(self, targ): def __ne__(self, target): return not self.__eq__(target) + def _check_alias(self, module, target): + # This is ugly, but there are extensions using an alias name. Check if + # that's the case, and load that extension as well if necessary. It + # will be used to parse parameters, since the 'real' extension + # probably won't understand them. + if getattr(module, "alias", None) is not None and module.alias: + self._alias_name = module.alias(target) + alias = self._xt.find_target(self._alias_name) + if not alias: + raise XTablesError("can't find alias target %s" % + (self._alias_name)) + self._alias = alias[0] + def _create_buffer(self, target): self._buffer = _Buffer(self.size) self._target_buf = self._buffer.buffer @@ -625,7 +661,11 @@ def _final_check(self): self._xt.final_check_target(self._module) def _parse(self, argv, inv, entry): - self._xt.parse_target(argv, inv, self._module, entry, + if self._alias is not None: + module = self._alias + else: + module = self._module + self._xt.parse_target(argv, inv, module, entry, ct.cast(self._ptrptr, ct.POINTER(ct.c_void_p))) self._target_buf = ct.cast(self._module.t, ct.POINTER(ct.c_ubyte)) self._buffer.buffer = self._target_buf @@ -660,6 +700,8 @@ def _update_pointers(self): self._ptrptr = ct.cast(ct.pointer(self._ptr), ct.POINTER(ct.POINTER(xt_entry_target))) self._module.t = self._ptr + if self._alias is not None: + self._alias.t = self._ptr def reset(self): """Reset the target. Parameters are set to their default values, any From 700063c084728be80dca3ab2c4dd2d0d88c26334 Mon Sep 17 00:00:00 2001 From: ldx Date: Sat, 4 Jan 2014 23:20:01 +0100 Subject: [PATCH 021/287] Handle real_name of extensions. --- iptc/ip4tc.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index d165941..7c8176d 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -441,6 +441,8 @@ def __init__(self, rule, name=None, match=None, revision=None): name = match.u.user.name self._name = name self._rule = rule + self._alias = None + self._real_name = None self._xt = xtables(rule.nfproto) @@ -484,6 +486,8 @@ def _check_alias(self, module, match): # that's the case, and load that extension as well if necessary. It # will be used to parse parameters, since the 'real' extension # probably won't understand them. + if getattr(module, "real_name", None) is not None and module.real_name: + self._real_name = module.real_name if getattr(module, "alias", None) is not None and module.alias: self._alias_name = module.alias(match) alias = self._xt.find_match(self._alias_name) @@ -526,6 +530,14 @@ def _update_pointers(self): self._module.m = self._ptr if self._alias is not None: self._alias.m = self._ptr + self._update_name() + + def _update_name(self): + m = self._ptr[0] + if self._real_name is not None: + m.u.user.name = self._real_name + else: + m.u.user.name = self.name def reset(self): """Reset the match. @@ -534,7 +546,6 @@ def reset(self): ct.memset(ct.byref(self._match_buf), 0, self.size) self._update_pointers() m = self._ptr[0] - m.u.user.name = self.name m.u.match_size = self.size m.u.user.revision = self._revision if self._module.init: @@ -633,6 +644,8 @@ def _check_alias(self, module, target): # that's the case, and load that extension as well if necessary. It # will be used to parse parameters, since the 'real' extension # probably won't understand them. + if getattr(module, "real_name", None) is not None and module.real_name: + self._real_name = module.real_name if getattr(module, "alias", None) is not None and module.alias: self._alias_name = module.alias(target) alias = self._xt.find_target(self._alias_name) @@ -702,6 +715,14 @@ def _update_pointers(self): self._module.t = self._ptr if self._alias is not None: self._alias.t = self._ptr + self._update_name() + + def _update_name(self): + m = self._ptr[0] + if self._real_name is not None: + m.u.user.name = self._real_name + else: + m.u.user.name = self.name def reset(self): """Reset the target. Parameters are set to their default values, any @@ -709,7 +730,6 @@ def reset(self): ct.memset(self._target_buf, 0, self.size) self._update_pointers() t = self._ptr[0] - t.u.user.name = self.name t.u.target_size = self.size t.u.user.revision = self._revision if self._module.init: From b0726a80d908c3e9388ffeb893dd3ecf0beec9c7 Mon Sep 17 00:00:00 2001 From: ldx Date: Sat, 4 Jan 2014 23:20:42 +0100 Subject: [PATCH 022/287] Add tests for alias and real_name. --- iptc/test/test_matches.py | 38 ++++++++++++++++++++++++++- iptc/test/test_targets.py | 54 +++++++++++++++++++++++++++++++-------- 2 files changed, 81 insertions(+), 11 deletions(-) diff --git a/iptc/test/test_matches.py b/iptc/test/test_matches.py index f565d56..c54b563 100755 --- a/iptc/test/test_matches.py +++ b/iptc/test/test_matches.py @@ -264,6 +264,40 @@ def test_iprange_tcpdport(self): self.fail("inserted rule does not match original") +class TestXTStateMatch(unittest.TestCase): + def setUp(self): + self.rule = iptc.Rule() + self.rule.src = "127.0.0.1" + self.rule.protocol = "tcp" + self.rule.target = iptc.Target(self.rule, "ACCEPT") + + self.match = iptc.Match(self.rule, "state") + + self.chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), + "iptc_test_state") + self.table = iptc.Table(iptc.Table.FILTER) + try: + self.chain.flush() + self.chain.delete() + except: + pass + self.table.create_chain(self.chain) + + def tearDown(self): + self.chain.flush() + self.chain.delete() + pass + + def test_state(self): + self.match.state = "RELATED,ESTABLISHED" + self.rule.add_match(self.match) + self.chain.insert_rule(self.rule) + rule = self.chain.rules[0] + m = rule.matches[0] + self.assertIn(m.name, ["state", "conntrack"]) + self.assertEquals(m.state, "RELATED,ESTABLISHED") + + def suite(): suite_match = unittest.TestLoader().loadTestsFromTestCase(TestMatch) suite_udp = unittest.TestLoader().loadTestsFromTestCase(TestXTUdpMatch) @@ -273,8 +307,10 @@ def suite(): TestCommentMatch) suite_iprange = unittest.TestLoader().loadTestsFromTestCase( TestIprangeMatch) + suite_state = unittest.TestLoader().loadTestsFromTestCase(TestXTStateMatch) return unittest.TestSuite([suite_match, suite_udp, suite_mark, - suite_limit, suite_comment, suite_iprange]) + suite_limit, suite_comment, suite_iprange, + suite_state]) def run_tests(): diff --git a/iptc/test/test_targets.py b/iptc/test/test_targets.py index 2ecaf61..505a49c 100755 --- a/iptc/test/test_targets.py +++ b/iptc/test/test_targets.py @@ -342,22 +342,56 @@ def test_insert(self): self.fail("inserted rule does not match original") +class TestXTNotrackTarget(unittest.TestCase): + def setUp(self): + self.rule = iptc.Rule() + self.rule.dst = "127.0.0.2" + self.rule.protocol = "tcp" + self.rule.out_interface = "eth0" + + self.target = iptc.Target(self.rule, "NOTRACK") + self.rule.target = self.target + + self.chain = iptc.Chain(iptc.Table(iptc.Table.RAW), + "iptc_test_notrack") + try: + self.chain.flush() + self.chain.delete() + except: + pass + iptc.Table(iptc.Table.RAW).create_chain(self.chain) + + def tearDown(self): + self.chain.flush() + self.chain.delete() + + def test_notrack(self): + self.chain.insert_rule(self.rule) + t = self.chain.rules[0].target + self.assertIn(t.name, ["NOTRACK", "CT"]) + + def suite(): + suites = [] suite_target = unittest.TestLoader().loadTestsFromTestCase(TestTarget) suite_tos = unittest.TestLoader().loadTestsFromTestCase(TestXTTosTarget) suite_cluster = unittest.TestLoader().loadTestsFromTestCase( TestXTClusteripTarget) + suite_redir = unittest.TestLoader().loadTestsFromTestCase( + TestIPTRedirectTarget) + suite_masq = unittest.TestLoader().loadTestsFromTestCase( + TestIPTMasqueradeTarget) + suite_dnat = unittest.TestLoader().loadTestsFromTestCase( + TestDnatTarget) + suite_conntrack = unittest.TestLoader().loadTestsFromTestCase( + TestXTNotrackTarget) + suites.extend([suite_target, suite_cluster, suite_tos]) if is_table_available(iptc.Table.NAT): - suite_redir = unittest.TestLoader().loadTestsFromTestCase( - TestIPTRedirectTarget) - suite_masq = unittest.TestLoader().loadTestsFromTestCase( - TestIPTMasqueradeTarget) - suite_dnat = unittest.TestLoader().loadTestsFromTestCase( - TestDnatTarget) - return unittest.TestSuite([suite_target, suite_cluster, suite_redir, - suite_tos, suite_masq, suite_dnat]) - else: - return unittest.TestSuite([suite_target, suite_cluster, suite_tos]) + suites.extend([suite_target, suite_cluster, suite_redir, suite_tos, + suite_masq, suite_dnat]) + if is_table_available(iptc.Table.RAW): + suites.extend([suite_conntrack]) + return unittest.TestSuite(suites) def run_tests(): From 18aa2f50a2adef3abcd4e15a6a3d58d1aa6f302f Mon Sep 17 00:00:00 2001 From: ldx Date: Sat, 4 Jan 2014 23:36:49 +0100 Subject: [PATCH 023/287] Replace assertIn() with assertTrue(). Unittest in Python 2.6 does not have assertIn(). --- iptc/test/test_matches.py | 2 +- iptc/test/test_targets.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/iptc/test/test_matches.py b/iptc/test/test_matches.py index c54b563..69b0b01 100755 --- a/iptc/test/test_matches.py +++ b/iptc/test/test_matches.py @@ -294,7 +294,7 @@ def test_state(self): self.chain.insert_rule(self.rule) rule = self.chain.rules[0] m = rule.matches[0] - self.assertIn(m.name, ["state", "conntrack"]) + self.assertTrue(m.name, ["state", "conntrack"]) self.assertEquals(m.state, "RELATED,ESTABLISHED") diff --git a/iptc/test/test_targets.py b/iptc/test/test_targets.py index 505a49c..6d83f5f 100755 --- a/iptc/test/test_targets.py +++ b/iptc/test/test_targets.py @@ -368,7 +368,7 @@ def tearDown(self): def test_notrack(self): self.chain.insert_rule(self.rule) t = self.chain.rules[0].target - self.assertIn(t.name, ["NOTRACK", "CT"]) + self.assertTrue(t.name in ["NOTRACK", "CT"]) def suite(): From a92f91e45da6355c39df7f348e135f565455a498 Mon Sep 17 00:00:00 2001 From: ldx Date: Sun, 5 Jan 2014 00:19:47 +0100 Subject: [PATCH 024/287] Add /lib64/xtables to the extension search path list. --- iptc/xtables.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index 68e706b..510caf8 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -681,9 +681,9 @@ class XTablesError(Exception): _xtables_libdir = os.getenv("XTABLES_LIBDIR") if _xtables_libdir is None: import os.path - for xtdir in ["/lib/xtables", "/usr/lib/xtables", "/usr/lib/iptables", - "/usr/lib64/xtables", "/usr/lib64/iptables", - "/usr/local/lib/xtables"]: + for xtdir in ["/lib/xtables", "/lib64/xtables", "/usr/lib/xtables", + "/usr/lib/iptables", "/usr/lib64/xtables", + "/usr/lib64/iptables", "/usr/local/lib/xtables"]: if os.path.isdir(xtdir): _xtables_libdir = xtdir break From c9ba2992e7bf701c82ec16005dda1ecaf309d1f5 Mon Sep 17 00:00:00 2001 From: ldx Date: Sun, 5 Jan 2014 00:39:55 +0100 Subject: [PATCH 025/287] Modprobe ip_tables when ip4tc is loaded. --- iptc/ip4tc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 7c8176d..1efeabe 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -9,12 +9,14 @@ import sys import weakref -from util import find_library +from util import find_library, load_kernel from xtables import (XT_INV_PROTO, NFPROTO_IPV4, XTablesError, xtables, xt_align, xt_counters, xt_entry_target, xt_entry_match) __all__ = ["Table", "Chain", "Rule", "Match", "Target", "Policy", "IPTCError"] +load_kernel("ip_tables") + _IFNAMSIZ = 16 _libc = ct.CDLL("libc.so.6") From 2cbd38d49e556b8fbd147cc284716bb5c1c994a1 Mon Sep 17 00:00:00 2001 From: ldx Date: Sun, 5 Jan 2014 18:42:52 +0100 Subject: [PATCH 026/287] Add section about counters to README. --- README.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/README.md b/README.md index f1a5f00..a2eefb1 100644 --- a/README.md +++ b/README.md @@ -305,3 +305,48 @@ This is the `python-iptables` equivalent of the following iptables command: # iptables -A INPUT -p tcp –destination-port 22 -m iprange –src-range 192.168.1.100-192.168.1.200 –dst-range 172.22.33.106 -j DROP + +Counters +-------- +You can query rule and chain counters, e.g.: + + >>> import iptc + >>> table = iptc.Table(iptc.Table.FILTER) + >>> chain = iptc.Chain(table, 'OUTPUT') + >>> for rule in chain.rules: + >>> (packets, bytes) = rule.get_counters() + >>> print packets, bytes + +However, the counters are only refreshed when the underlying low-level iptables connection is refreshed in `Table` via `table.refresh()`. For example: + + import time, sys + import iptc + table = iptc.Table(iptc.Table.FILTER) + chain = iptc.Chain(table, 'OUTPUT') + for rule in chain.rules: + (packets, bytes) = rule.get_counters() + print packets, bytes + print "Please send some traffic" + sys.stdout.flush() + time.sleep(3) + for rule in chain.rules: + # Here you will get back the same counter values as above + (packets, bytes) = rule.get_counters() + print packets, bytes + +This will show you the same counter values even if there was traffic hitting your rules. You have to refresh your table to get update your counters: + + import time, sys + import iptc + table = iptc.Table(iptc.Table.FILTER) + chain = iptc.Chain(table, 'OUTPUT') + for rule in chain.rules: + (packets, bytes) = rule.get_counters() + print packets, bytes + print "Please send some traffic" + sys.stdout.flush() + time.sleep(3) + table.refresh() # Here: refresh table to update rule counters + for rule in chain.rules: + (packets, bytes) = rule.get_counters() + print packets, bytes From 190ba81db3f89f4cb98261d5657863938c93d35a Mon Sep 17 00:00:00 2001 From: ldx Date: Sun, 5 Jan 2014 18:56:03 +0100 Subject: [PATCH 027/287] Generate README.md from RST files in doc. The command to update README.md: pandoc -f rst -t markdown doc/intro.rst doc/examples.rst -o README.md --- README.md | 71 ++++++++++++++++++++++++++---------------------- doc/examples.rst | 48 ++++++++++++++++++++++++++++++++ doc/intro.rst | 11 ++++++-- 3 files changed, 95 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index a2eefb1..6dbde29 100644 --- a/README.md +++ b/README.md @@ -308,6 +308,7 @@ command: Counters -------- + You can query rule and chain counters, e.g.: >>> import iptc @@ -317,36 +318,40 @@ You can query rule and chain counters, e.g.: >>> (packets, bytes) = rule.get_counters() >>> print packets, bytes -However, the counters are only refreshed when the underlying low-level iptables connection is refreshed in `Table` via `table.refresh()`. For example: - - import time, sys - import iptc - table = iptc.Table(iptc.Table.FILTER) - chain = iptc.Chain(table, 'OUTPUT') - for rule in chain.rules: - (packets, bytes) = rule.get_counters() - print packets, bytes - print "Please send some traffic" - sys.stdout.flush() - time.sleep(3) - for rule in chain.rules: - # Here you will get back the same counter values as above - (packets, bytes) = rule.get_counters() - print packets, bytes - -This will show you the same counter values even if there was traffic hitting your rules. You have to refresh your table to get update your counters: - - import time, sys - import iptc - table = iptc.Table(iptc.Table.FILTER) - chain = iptc.Chain(table, 'OUTPUT') - for rule in chain.rules: - (packets, bytes) = rule.get_counters() - print packets, bytes - print "Please send some traffic" - sys.stdout.flush() - time.sleep(3) - table.refresh() # Here: refresh table to update rule counters - for rule in chain.rules: - (packets, bytes) = rule.get_counters() - print packets, bytes +However, the counters are only refreshed when the underlying low-level +iptables connection is refreshed in `Table` via `table.refresh()`. For +example: + + >>> import time, sys + >>> import iptc + >>> table = iptc.Table(iptc.Table.FILTER) + >>> chain = iptc.Chain(table, 'OUTPUT') + >>> for rule in chain.rules: + >>> (packets, bytes) = rule.get_counters() + >>> print packets, bytes + >>> print "Please send some traffic" + >>> sys.stdout.flush() + >>> time.sleep(3) + >>> for rule in chain.rules: + >>> # Here you will get back the same counter values as above + >>> (packets, bytes) = rule.get_counters() + >>> print packets, bytes + +This will show you the same counter values even if there was traffic +hitting your rules. You have to refresh your table to get update your +counters: + + >>> import time, sys + >>> import iptc + >>> table = iptc.Table(iptc.Table.FILTER) + >>> chain = iptc.Chain(table, 'OUTPUT') + >>> for rule in chain.rules: + >>> (packets, bytes) = rule.get_counters() + >>> print packets, bytes + >>> print "Please send some traffic" + >>> sys.stdout.flush() + >>> time.sleep(3) + >>> table.refresh() # Here: refresh table to update rule counters + >>> for rule in chain.rules: + >>> (packets, bytes) = rule.get_counters() + >>> print packets, bytes diff --git a/doc/examples.rst b/doc/examples.rst index bf87d72..c60841d 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -210,3 +210,51 @@ matches:: This is the ``python-iptables`` equivalent of the following iptables command:: # iptables -A INPUT -p tcp –destination-port 22 -m iprange –src-range 192.168.1.100-192.168.1.200 –dst-range 172.22.33.106 -j DROP + +Counters +-------- +You can query rule and chain counters, e.g.:: + + >>> import iptc + >>> table = iptc.Table(iptc.Table.FILTER) + >>> chain = iptc.Chain(table, 'OUTPUT') + >>> for rule in chain.rules: + >>> (packets, bytes) = rule.get_counters() + >>> print packets, bytes + +However, the counters are only refreshed when the underlying low-level +iptables connection is refreshed in ``Table`` via ``table.refresh()``. For +example:: + + >>> import time, sys + >>> import iptc + >>> table = iptc.Table(iptc.Table.FILTER) + >>> chain = iptc.Chain(table, 'OUTPUT') + >>> for rule in chain.rules: + >>> (packets, bytes) = rule.get_counters() + >>> print packets, bytes + >>> print "Please send some traffic" + >>> sys.stdout.flush() + >>> time.sleep(3) + >>> for rule in chain.rules: + >>> # Here you will get back the same counter values as above + >>> (packets, bytes) = rule.get_counters() + >>> print packets, bytes + +This will show you the same counter values even if there was traffic hitting +your rules. You have to refresh your table to get update your counters:: + + >>> import time, sys + >>> import iptc + >>> table = iptc.Table(iptc.Table.FILTER) + >>> chain = iptc.Chain(table, 'OUTPUT') + >>> for rule in chain.rules: + >>> (packets, bytes) = rule.get_counters() + >>> print packets, bytes + >>> print "Please send some traffic" + >>> sys.stdout.flush() + >>> time.sleep(3) + >>> table.refresh() # Here: refresh table to update rule counters + >>> for rule in chain.rules: + >>> (packets, bytes) = rule.get_counters() + >>> print packets, bytes diff --git a/doc/intro.rst b/doc/intro.rst index 33dce84..dfaff39 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -31,8 +31,15 @@ Linux iptables framework.. .. |buildstatus| image:: https://travis-ci.org/ldx/python-iptables.png?branch=master -Compiling and installing ------------------------- +Installing via pip +------------------ + +The usual way:: + + pip install --upgrade python-iptables + +Compiling from source +---------------------- First make sure you have iptables installed (most Linux distributions install it by default). ``Python-iptables`` needs the shared libraries ``libiptc.so`` From 0250cebacfbca41b850a3728ec2c10ea21108434 Mon Sep 17 00:00:00 2001 From: ldx Date: Mon, 6 Jan 2014 20:09:00 +0100 Subject: [PATCH 028/287] Release 0.3.0. --- doc/conf.py | 2 +- iptc/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index a216f15..6b627a9 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -48,7 +48,7 @@ # The short X.Y version. version = '0.3.0' # The full version, including alpha/beta/rc tags. -release = '0.3.0-dev' +release = '0.3.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/iptc/version.py b/iptc/version.py index 35615ed..80cdc18 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.3.0-dev" +__version__ = "0.3.0" From 654336c10586e74c737668494b8bd0220a262536 Mon Sep 17 00:00:00 2001 From: ldx Date: Fri, 17 Jan 2014 23:03:18 +0100 Subject: [PATCH 029/287] Start new dev version. --- doc/conf.py | 4 ++-- iptc/version.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 6b627a9..303b453 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -46,9 +46,9 @@ # built documents. # # The short X.Y version. -version = '0.3.0' +version = '0.4.0' # The full version, including alpha/beta/rc tags. -release = '0.3.0' +release = '0.4.0-dev' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/iptc/version.py b/iptc/version.py index 80cdc18..5d776fd 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.3.0" +__version__ = "0.4.0-dev" From a57402721a34190093e0543e42ac534cd935da42 Mon Sep 17 00:00:00 2001 From: ldx Date: Fri, 17 Jan 2014 23:08:12 +0100 Subject: [PATCH 030/287] Add example with negation to docs. --- README.md | 25 +++++++++++++++++++++++++ doc/examples.rst | 25 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/README.md b/README.md index 6dbde29..4c72ead 100644 --- a/README.md +++ b/README.md @@ -306,6 +306,31 @@ command: # iptables -A INPUT -p tcp –destination-port 22 -m iprange –src-range 192.168.1.100-192.168.1.200 –dst-range 172.22.33.106 -j DROP +You can of course negate matches, just like when you use `!` in front of +a match with iptables. For example: + + >>> import iptc + >>> rule = iptc.Rule() + >>> match = iptc.Match(rule, "mac") + >>> match.mac_source = "!00:11:22:33:44:55" + >>> rule.add_match(match) + >>> rule.target = iptc.Target(rule, "ACCEPT") + >>> chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), "INPUT") + >>> chain.insert_rule(rule) + +This results in: + + $ sudo iptables -L -n + Chain INPUT (policy ACCEPT) + target prot opt source destination + ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 MAC ! 00:11:22:33:44:55 + + Chain FORWARD (policy ACCEPT) + target prot opt source destination + + Chain OUTPUT (policy ACCEPT) + target prot opt source destination + Counters -------- diff --git a/doc/examples.rst b/doc/examples.rst index c60841d..0e2d39a 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -211,6 +211,31 @@ This is the ``python-iptables`` equivalent of the following iptables command:: # iptables -A INPUT -p tcp –destination-port 22 -m iprange –src-range 192.168.1.100-192.168.1.200 –dst-range 172.22.33.106 -j DROP +You can of course negate matches, just like when you use ``!`` in front of a +match with iptables. For example:: + + >>> import iptc + >>> rule = iptc.Rule() + >>> match = iptc.Match(rule, "mac") + >>> match.mac_source = "!00:11:22:33:44:55" + >>> rule.add_match(match) + >>> rule.target = iptc.Target(rule, "ACCEPT") + >>> chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), "INPUT") + >>> chain.insert_rule(rule) + +This results in:: + + $ sudo iptables -L -n + Chain INPUT (policy ACCEPT) + target prot opt source destination + ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 MAC ! 00:11:22:33:44:55 + + Chain FORWARD (policy ACCEPT) + target prot opt source destination + + Chain OUTPUT (policy ACCEPT) + target prot opt source destination + Counters -------- You can query rule and chain counters, e.g.:: From 5eb2dc54c8a73a3a0f516eb9964b6bd604966c39 Mon Sep 17 00:00:00 2001 From: ldx Date: Fri, 17 Jan 2014 23:08:49 +0100 Subject: [PATCH 031/287] Update .gitignore. --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 3a98128..807897e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ *.bak /build - /doc/_build - /.project +/MANIFEST +/dist From 789b63e25c6035d581982f2c236799b05331fc74 Mon Sep 17 00:00:00 2001 From: ldx Date: Tue, 4 Feb 2014 10:35:39 +0700 Subject: [PATCH 032/287] Only check alias when we've got our buffer. This fixes #53. --- iptc/ip4tc.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 1efeabe..9479f6a 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -460,8 +460,6 @@ def __init__(self, rule, name=None, match=None, revision=None): if self._module.next is not None: self._store_buffer(module) - self._check_alias(module[0], match) - self._match_buf = (ct.c_ubyte * self.size)() if match: ct.memmove(ct.byref(self._match_buf), ct.byref(match), self.size) @@ -530,6 +528,7 @@ def _update_pointers(self): self._ptrptr = ct.cast(ct.pointer(self._ptr), ct.POINTER(ct.POINTER(xt_entry_match))) self._module.m = self._ptr + self._check_alias(self._module, self._module.m) if self._alias is not None: self._alias.m = self._ptr self._update_name() @@ -613,8 +612,6 @@ def __init__(self, rule, name=None, target=None, revision=None): else: self._revision = self._module.revision - self._check_alias(module[0], target) - self._create_buffer(target) if self._is_standard_target(): @@ -715,6 +712,7 @@ def _update_pointers(self): self._ptrptr = ct.cast(ct.pointer(self._ptr), ct.POINTER(ct.POINTER(xt_entry_target))) self._module.t = self._ptr + self._check_alias(self._module, self._module.t) if self._alias is not None: self._alias.t = self._ptr self._update_name() From 9b3798dbcbeb00e1eb04a5d680fab146279a0e0b Mon Sep 17 00:00:00 2001 From: ldx Date: Tue, 4 Feb 2014 18:40:25 +0700 Subject: [PATCH 033/287] Check for alias before calling XTables.final_check_match(). --- iptc/ip4tc.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 9479f6a..a953f41 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -501,7 +501,11 @@ def _store_buffer(self, module): self._buffer.buffer = ct.cast(module, ct.POINTER(ct.c_ubyte)) def _final_check(self): - self._xt.final_check_match(self._module) + if self._alias is not None: + module = self._alias + else: + module = self._module + self._xt.final_check_match(module) def _parse(self, argv, inv, entry): if self._alias is not None: @@ -670,7 +674,11 @@ def _is_standard_target(self): return False def _final_check(self): - self._xt.final_check_target(self._module) + if self._alias is not None: + module = self._alias + else: + module = self._module + self._xt.final_check_target(module) def _parse(self, argv, inv, entry): if self._alias is not None: From 54a4b1f67d50c2bc5dd8e54e2b6e536c9be11d3b Mon Sep 17 00:00:00 2001 From: ldx Date: Tue, 4 Feb 2014 18:40:55 +0700 Subject: [PATCH 034/287] Add tests for matches/targets with aliases. --- iptc/test/test_matches.py | 38 +++++++++++++++++++++++++++++++++++++- iptc/test/test_targets.py | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/iptc/test/test_matches.py b/iptc/test/test_matches.py index 69b0b01..67c37ff 100755 --- a/iptc/test/test_matches.py +++ b/iptc/test/test_matches.py @@ -298,6 +298,40 @@ def test_state(self): self.assertEquals(m.state, "RELATED,ESTABLISHED") +class TestXTConntrackMatch(unittest.TestCase): + def setUp(self): + self.rule = iptc.Rule() + self.rule.src = "127.0.0.1" + self.rule.protocol = "tcp" + self.rule.target = iptc.Target(self.rule, "ACCEPT") + + self.match = iptc.Match(self.rule, "conntrack") + + self.chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), + "iptc_test_conntrack") + self.table = iptc.Table(iptc.Table.FILTER) + try: + self.chain.flush() + self.chain.delete() + except: + pass + self.table.create_chain(self.chain) + + def tearDown(self): + self.chain.flush() + self.chain.delete() + pass + + def test_state(self): + self.match.ctstate = "NEW,RELATED" + self.rule.add_match(self.match) + self.chain.insert_rule(self.rule) + rule = self.chain.rules[0] + m = rule.matches[0] + self.assertTrue(m.name, ["conntrack"]) + self.assertEquals(m.ctstate, "NEW,RELATED") + + def suite(): suite_match = unittest.TestLoader().loadTestsFromTestCase(TestMatch) suite_udp = unittest.TestLoader().loadTestsFromTestCase(TestXTUdpMatch) @@ -308,9 +342,11 @@ def suite(): suite_iprange = unittest.TestLoader().loadTestsFromTestCase( TestIprangeMatch) suite_state = unittest.TestLoader().loadTestsFromTestCase(TestXTStateMatch) + suite_conntrack = unittest.TestLoader().loadTestsFromTestCase( + TestXTConntrackMatch) return unittest.TestSuite([suite_match, suite_udp, suite_mark, suite_limit, suite_comment, suite_iprange, - suite_state]) + suite_state, suite_conntrack]) def run_tests(): diff --git a/iptc/test/test_targets.py b/iptc/test/test_targets.py index 6d83f5f..32516bd 100755 --- a/iptc/test/test_targets.py +++ b/iptc/test/test_targets.py @@ -371,6 +371,37 @@ def test_notrack(self): self.assertTrue(t.name in ["NOTRACK", "CT"]) +class TestXTCtTarget(unittest.TestCase): + def setUp(self): + self.rule = iptc.Rule() + self.rule.dst = "127.0.0.2" + self.rule.protocol = "tcp" + self.rule.out_interface = "eth0" + + self.target = iptc.Target(self.rule, "CT") + self.target.notrack = "true" + self.rule.target = self.target + + self.chain = iptc.Chain(iptc.Table(iptc.Table.RAW), + "iptc_test_ct") + try: + self.chain.flush() + self.chain.delete() + except: + pass + iptc.Table(iptc.Table.RAW).create_chain(self.chain) + + def tearDown(self): + self.chain.flush() + self.chain.delete() + + def test_ct(self): + self.chain.insert_rule(self.rule) + t = self.chain.rules[0].target + self.assertEquals(t.name, "CT") + self.assertTrue(t.notrack is not None) + + def suite(): suites = [] suite_target = unittest.TestLoader().loadTestsFromTestCase(TestTarget) @@ -383,14 +414,15 @@ def suite(): TestIPTMasqueradeTarget) suite_dnat = unittest.TestLoader().loadTestsFromTestCase( TestDnatTarget) - suite_conntrack = unittest.TestLoader().loadTestsFromTestCase( + suite_notrack = unittest.TestLoader().loadTestsFromTestCase( TestXTNotrackTarget) + suite_ct = unittest.TestLoader().loadTestsFromTestCase(TestXTCtTarget) suites.extend([suite_target, suite_cluster, suite_tos]) if is_table_available(iptc.Table.NAT): suites.extend([suite_target, suite_cluster, suite_redir, suite_tos, suite_masq, suite_dnat]) if is_table_available(iptc.Table.RAW): - suites.extend([suite_conntrack]) + suites.extend([suite_notrack, suite_ct]) return unittest.TestSuite(suites) From 39e9bb31eb805aa484bac98a324776383b48c501 Mon Sep 17 00:00:00 2001 From: ldx Date: Sun, 9 Feb 2014 21:37:47 +0700 Subject: [PATCH 035/287] Add more examples to docs. This fixes #54. --- README.md | 59 +++++++++++++++++++++++++++++++++++++++++++++++- doc/examples.rst | 59 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4c72ead..b67293b 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,64 @@ To set up a rule that matches packets marked with 0xff: >>> match = rule.create_match("mark") >>> match.mark = "0xff" -Parameters are always strings. +Parameters are always strings. You can supply any string as the +parameter value, but note that most extensions validate their +parameters. For example this: + + >>> rule = iptc.Rule() + >>> rule.protocol = "tcp" + >>> rule.target = iptc.Target(rule, "ACCEPT") + >>> match = iptc.Match(rule, "state") + >>> chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), "INPUT") + >>> match.state = "RELATED,ESTABLISHED" + >>> rule.add_match(match) + >>> chain.insert_rule(rule) + +will work. However, if you change the state parameter: + + >>> rule = iptc.Rule() + >>> rule.protocol = "tcp" + >>> rule.target = iptc.Target(rule, "ACCEPT") + >>> match = iptc.Match(rule, "state") + >>> chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), "INPUT") + >>> match.state = "RELATED,ESTABLISHED,FOOBAR" + >>> rule.add_match(match) + >>> chain.insert_rule(rule) + +`python-iptables` will throw an exception: + + Traceback (most recent call last): + File "state.py", line 7, in + match.state = "RELATED,ESTABLISHED,FOOBAR" + File "/home/user/Projects/python-iptables/iptc/ip4tc.py", line 369, in __setattr__ + self.parse(name.replace("_", "-"), value) + File "/home/user/Projects/python-iptables/iptc/ip4tc.py", line 286, in parse + self._parse(argv, inv, entry) + File "/home/user/Projects/python-iptables/iptc/ip4tc.py", line 516, in _parse + ct.cast(self._ptrptr, ct.POINTER(ct.c_void_p))) + File "/home/user/Projects/python-iptables/iptc/xtables.py", line 736, in new + ret = fn(*args) + File "/home/user/Projects/python-iptables/iptc/xtables.py", line 1031, in parse_match + argv[1])) + iptc.xtables.XTablesError: state: parameter error -2 (RELATED,ESTABLISHED,FOOBAR) + +In certain cases you might need to use quoting inside the parameter +string, for example: + + >>> rule = iptc.Rule() + >>> rule.src = "127.0.0.1" + >>> rule.protocol = "udp" + >>> rule.target = rule.create_target("ACCEPT") + >>> match = rule.create_match("comment") + >>> match.comment = "this is a test comment" + >>> chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), "INPUT") + >>> chain.insert_rule(rule) + +will only add the comment this instead of the expected this is a test +comment. Use quoting inside the comment string itself: + + >>> comment = "this is a test comment" + >>> match.comment = "\"%s\"" % (comment) When you are ready constructing your rule, add them to the chain you want it to show up in: diff --git a/doc/examples.rst b/doc/examples.rst index 0e2d39a..7c0c916 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -52,7 +52,64 @@ To set up a rule that matches packets marked with 0xff:: >>> match = rule.create_match("mark") >>> match.mark = "0xff" -Parameters are always strings. +Parameters are always strings. You can supply any string as the parameter +value, but note that most extensions validate their parameters. For example +this:: + + >>> rule = iptc.Rule() + >>> rule.protocol = "tcp" + >>> rule.target = iptc.Target(rule, "ACCEPT") + >>> match = iptc.Match(rule, "state") + >>> chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), "INPUT") + >>> match.state = "RELATED,ESTABLISHED" + >>> rule.add_match(match) + >>> chain.insert_rule(rule) + +will work. However, if you change the `state` parameter:: + + >>> rule = iptc.Rule() + >>> rule.protocol = "tcp" + >>> rule.target = iptc.Target(rule, "ACCEPT") + >>> match = iptc.Match(rule, "state") + >>> chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), "INPUT") + >>> match.state = "RELATED,ESTABLISHED,FOOBAR" + >>> rule.add_match(match) + >>> chain.insert_rule(rule) + +``python-iptables`` will throw an exception:: + + Traceback (most recent call last): + File "state.py", line 7, in + match.state = "RELATED,ESTABLISHED,FOOBAR" + File "/home/user/Projects/python-iptables/iptc/ip4tc.py", line 369, in __setattr__ + self.parse(name.replace("_", "-"), value) + File "/home/user/Projects/python-iptables/iptc/ip4tc.py", line 286, in parse + self._parse(argv, inv, entry) + File "/home/user/Projects/python-iptables/iptc/ip4tc.py", line 516, in _parse + ct.cast(self._ptrptr, ct.POINTER(ct.c_void_p))) + File "/home/user/Projects/python-iptables/iptc/xtables.py", line 736, in new + ret = fn(*args) + File "/home/user/Projects/python-iptables/iptc/xtables.py", line 1031, in parse_match + argv[1])) + iptc.xtables.XTablesError: state: parameter error -2 (RELATED,ESTABLISHED,FOOBAR) + +In certain cases you might need to use quoting inside the parameter string, for +example:: + + >>> rule = iptc.Rule() + >>> rule.src = "127.0.0.1" + >>> rule.protocol = "udp" + >>> rule.target = rule.create_target("ACCEPT") + >>> match = rule.create_match("comment") + >>> match.comment = "this is a test comment" + >>> chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), "INPUT") + >>> chain.insert_rule(rule) + +will only add the comment `this` instead of the expected `this is a test +comment`. Use quoting inside the comment string itself:: + + >>> comment = "this is a test comment" + >>> match.comment = "\"%s\"" % (comment) When you are ready constructing your rule, add them to the chain you want it to show up in:: From 0ece2379883d068eae5d0f806eae948141a0531b Mon Sep 17 00:00:00 2001 From: ldx Date: Sun, 16 Feb 2014 08:01:42 +0100 Subject: [PATCH 036/287] Add Bitdeli badge. --- README.md | 2 ++ doc/intro.rst | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/README.md b/README.md index b67293b..28a5fb6 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ wish to interface with the Linux iptables framework.. ![buildstatus](https://travis-ci.org/ldx/python-iptables.png?branch=master) +![Bitdeli](https://d2weczhvl823v0.cloudfront.net/ldx/python-iptables/trend.png) + Installing via pip ------------------ diff --git a/doc/intro.rst b/doc/intro.rst index dfaff39..89784af 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -31,6 +31,10 @@ Linux iptables framework.. .. |buildstatus| image:: https://travis-ci.org/ldx/python-iptables.png?branch=master +|Bitdeli| + +.. |Bitdeli| image:: https://d2weczhvl823v0.cloudfront.net/ldx/python-iptables/trend.png + Installing via pip ------------------ From 6c30ca04656062bf95a1039d6e4bc7440150b9bc Mon Sep 17 00:00:00 2001 From: Tim Harder Date: Sat, 22 Feb 2014 01:11:40 -0800 Subject: [PATCH 037/287] Fix return and argument types for various libiptc API calls. --- iptc/ip4tc.py | 6 +++--- iptc/ip6tc.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index a953f41..fd3fc78 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -103,7 +103,7 @@ class IPTCError(Exception): class iptc(object): """This class contains all libiptc API calls.""" iptc_init = _libiptc.iptc_init - iptc_init.restype = ct.c_void_p + iptc_init.restype = ct.POINTER(ct.c_int) iptc_init.argstype = [ct.c_char_p] iptc_free = _libiptc.iptc_free @@ -120,11 +120,11 @@ class iptc(object): iptc_first_chain = _libiptc.iptc_first_chain iptc_first_chain.restype = ct.c_char_p - iptc_first_chain.argstype = [ct.c_char_p, ct.c_void_p] + iptc_first_chain.argstype = [ct.c_void_p] iptc_next_chain = _libiptc.iptc_next_chain iptc_next_chain.restype = ct.c_char_p - iptc_next_chain.argstype = [ct.c_char_p, ct.c_void_p] + iptc_next_chain.argstype = [ct.c_void_p] iptc_is_chain = _libiptc.iptc_is_chain iptc_is_chain.restype = ct.c_int diff --git a/iptc/ip6tc.py b/iptc/ip6tc.py index dbf034d..10422d8 100644 --- a/iptc/ip6tc.py +++ b/iptc/ip6tc.py @@ -82,7 +82,7 @@ class ip6t_entry(ct.Structure): class ip6tc(object): """This class contains all libip6tc API calls.""" iptc_init = _libiptc.ip6tc_init - iptc_init.restype = ct.c_void_p + iptc_init.restype = ct.POINTER(ct.c_int) iptc_init.argstype = [ct.c_char_p] iptc_free = _libiptc.ip6tc_free @@ -99,11 +99,11 @@ class ip6tc(object): iptc_first_chain = _libiptc.ip6tc_first_chain iptc_first_chain.restype = ct.c_char_p - iptc_first_chain.argstype = [ct.c_char_p, ct.c_void_p] + iptc_first_chain.argstype = [ct.c_void_p] iptc_next_chain = _libiptc.ip6tc_next_chain iptc_next_chain.restype = ct.c_char_p - iptc_next_chain.argstype = [ct.c_char_p, ct.c_void_p] + iptc_next_chain.argstype = [ct.c_void_p] iptc_is_chain = _libiptc.ip6tc_is_chain iptc_is_chain.restype = ct.c_int From addf6794c1e34ab5190aea5ed82cf179c98ac387 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 30 Mar 2014 14:13:37 +0200 Subject: [PATCH 038/287] Reset extension before calling parse(). --- iptc/ip4tc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index fd3fc78..324218e 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -361,7 +361,9 @@ def get_all_parameters(self): return params def _update_parameters(self): - for k, v in self.get_all_parameters().iteritems(): + params = self.get_all_parameters().iteritems() + self.reset() + for k, v in params: self.__setattr__(k, v) def __setattr__(self, name, value): From 405feb2aa483b7c686b66f78ea82dc675fccc11a Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 30 Mar 2014 14:13:47 +0200 Subject: [PATCH 039/287] Disable option checks. --- iptc/ip4tc.py | 39 --------------------- iptc/xtables.py | 93 ------------------------------------------------- 2 files changed, 132 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 324218e..3d66936 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -288,13 +288,6 @@ def parse(self, parameter, value): def _parse(self, argv, inv, entry): raise NotImplementedError() - def final_check(self): - if self._module: - self._final_check() # subclasses override this - - def _final_check(self): - raise NotImplementedError() - def _get_saved_buf(self, ip): if not self._module or not self._module.save: return None @@ -360,12 +353,6 @@ def get_all_parameters(self): params[x[1]] = "%s%s" % ((x[0] or x[2]) and "!" or "", x[3]) return params - def _update_parameters(self): - params = self.get_all_parameters().iteritems() - self.reset() - for k, v in params: - self.__setattr__(k, v) - def __setattr__(self, name, value): if not name.startswith('_') and name not in dir(self): self.parse(name.replace("_", "-"), value) @@ -466,7 +453,6 @@ def __init__(self, rule, name=None, match=None, revision=None): if match: ct.memmove(ct.byref(self._match_buf), ct.byref(match), self.size) self._update_pointers() - self._update_parameters() else: self.reset() @@ -502,13 +488,6 @@ def _store_buffer(self, module): self._buffer = _Buffer() self._buffer.buffer = ct.cast(module, ct.POINTER(ct.c_ubyte)) - def _final_check(self): - if self._alias is not None: - module = self._alias - else: - module = self._module - self._xt.final_check_match(module) - def _parse(self, argv, inv, entry): if self._alias is not None: module = self._alias @@ -665,7 +644,6 @@ def _create_buffer(self, target): if target: ct.memmove(self._target_buf, ct.byref(target), self.size) self._update_pointers() - self._update_parameters() else: self.reset() @@ -675,13 +653,6 @@ def _is_standard_target(self): return True return False - def _final_check(self): - if self._alias is not None: - module = self._alias - else: - module = self._module - self._xt.final_check_target(module) - def _parse(self, argv, inv, entry): if self._alias is not None: module = self._alias @@ -845,13 +816,6 @@ def _get_tables(self): tables = property(_get_tables) """This is the list of tables for our protocol.""" - def final_check(self): - """Do a final check on the target and the matches.""" - if self.target: - self.target.final_check() - for match in self.matches: - match.final_check() - def create_match(self, name, revision=None): """Create a *match*, and add it to the list of matches in this rule. *name* is the name of the match extension, *revision* is the revision @@ -1307,7 +1271,6 @@ def is_builtin(self): def append_rule(self, rule): """Append *rule* to the end of the chain.""" - rule.final_check() rbuf = rule.rule if not rbuf: raise ValueError("invalid rule") @@ -1316,7 +1279,6 @@ def append_rule(self, rule): def insert_rule(self, rule, position=0): """Insert *rule* as the first entry in the chain if *position* is 0 or not specified, else *rule* is inserted in the given position.""" - rule.final_check() rbuf = rule.rule if not rbuf: raise ValueError("invalid rule") @@ -1324,7 +1286,6 @@ def insert_rule(self, rule, position=0): def delete_rule(self, rule): """Removes *rule* from the chain.""" - rule.final_check() rbuf = rule.rule if not rbuf: raise ValueError("invalid rule") diff --git a/iptc/xtables.py b/iptc/xtables.py index 510caf8..7b1e21a 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -1036,96 +1036,3 @@ def parse_match(self, argv, invert, m, fw, ptr): flags = ct.pointer(ct.c_uint(0)) self._parse(m, argv, invert, flags, fw, ptr) m.mflags |= flags[0] - - # Check that all option constraints have been met. This effectively - # replaces ->final_check of the older API. - def _options_fcheck(self, name, xflags, table): - for entry in table: - if entry.name is None: - break - if entry.flags & XTOPT_MAND and not xflags & (1 << entry.id): - raise XTablesError("%s: --%s must be specified" % (name, - entry.name)) - if not xflags & (1 << entry.id): - continue - # XXX: check for conflicting options - - def _fcheck_target_old(self, target): - # old API - if not target.final_check: - return - rv = _wrap_uintfn(target.final_check, target.tflags) - if rv: - raise XTablesError("%s.final_check() has failed" % - (target.name)) - - def _fcheck_target_new(self, target): - # new API - cb = xt_fcheck_call() - cb.ext_name = target.name - cb.data = ct.cast(target.t[0].data, ct.c_void_p) - cb.xflags = target.tflags - cb.udata = target.udata - rv = _wrap_x6fn(target.x6_fcheck, ct.pointer(cb)) - if rv: - raise XTablesError("%s.x6_fcheck has failed" % (target.name)) - if target.x6_options: - self._options_fcheck(target.name, target.tflags, - target.x6_options) - - # Dispatch arguments to the appropriate final_check function, based upon - # the extension's choice of API. - @preserve_globals - def final_check_target(self, target): - x6_fcheck = None - try: - # new API? - x6_fcheck = target.x6_fcheck - except AttributeError: - # old API - pass - - if x6_fcheck: - self._fcheck_target_new(target) - else: - self._fcheck_target_old(target) - - def _fcheck_match_old(self, match): - # old API - if not match.final_check: - return - rv = _wrap_uintfn(match.final_check, match.mflags) - if rv: - raise XTablesError("%s.final_check() has failed" % - (match.name)) - - def _fcheck_match_new(self, match): - # new API - cb = xt_fcheck_call() - cb.ext_name = match.name - cb.data = ct.cast(match.m[0].data, ct.c_void_p) - cb.xflags = match.mflags - cb.udata = match.udata - rv = _wrap_x6fn(match.x6_fcheck, ct.pointer(cb)) - if rv: - raise XTablesError("%s.x6_fcheck has failed" % (match.name)) - if match.x6_options: - self._options_fcheck(match.name, match.mflags, - match.x6_options) - - # Dispatch arguments to the appropriate final_check function, based upon - # the extension's choice of API. - @preserve_globals - def final_check_match(self, match): - x6_fcheck = None - try: - # new API? - x6_fcheck = match.x6_fcheck - except AttributeError: - # old API - pass - - if x6_fcheck: - self._fcheck_match_new(match) - else: - self._fcheck_match_old(match) From 0ab21e494167b3fb6e45c06e427fd1ff639b7293 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 30 Mar 2014 14:13:52 +0200 Subject: [PATCH 040/287] Add test case for #60. --- iptc/test/test_iptc.py | 64 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/iptc/test/test_iptc.py b/iptc/test/test_iptc.py index ba27bd5..8bd486b 100755 --- a/iptc/test/test_iptc.py +++ b/iptc/test/test_iptc.py @@ -512,13 +512,28 @@ def test_rule_insert(self): class TestRule(unittest.TestCase): def setUp(self): - self.chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), - "iptc_test_chain") - iptc.Table(iptc.Table.FILTER).create_chain(self.chain) + self.table = iptc.Table(iptc.Table.FILTER) + self.chain = iptc.Chain(self.table, "iptc_test_chain") + try: + self.table.create_chain(self.chain) + except: + self.chain.flush() + if is_table_available(iptc.Table.NAT): + self.table_nat = iptc.Table(iptc.Table.NAT) + self.chain_nat = iptc.Chain(self.table_nat, "iptc_test_nat_chain") + try: + self.table_nat.create_chain(self.chain_nat) + except: + self.chain_nat.flush() def tearDown(self): + self.table.autocommit = True self.chain.flush() self.chain.delete() + if is_table_available(iptc.Table.NAT): + self.table_nat.autocommit = True + self.chain_nat.flush() + self.chain_nat.delete() def test_rule_address(self): # valid addresses @@ -699,6 +714,49 @@ def test_rule_insert(self): self.failUnless(rule in crules) crules.remove(rule) + def test_rule_delete(self): + self.table.autocommit = False + self.table.refresh() + for p in ['8001', '8002', '8003']: + rule = iptc.Rule() + rule.dst = "127.0.0.1" + rule.protocol = "tcp" + rule.dport = "8080" + target = rule.create_target("REJECT") + target.reject_with = "icmp-host-unreachable" + self.chain.insert_rule(rule) + self.table.commit() + self.table.refresh() + + rules = self.chain.rules + for rule in rules: + self.chain.delete_rule(rule) + self.table.commit() + self.table.refresh() + + def test_rule_delete_nat(self): + if not is_table_available(iptc.Table.NAT): + return + + self.table_nat.autocommit = False + self.table_nat.refresh() + for p in ['8001', '8002', '8003']: + rule = iptc.Rule() + rule.dst = "127.0.0.1" + rule.protocol = "udp" + rule.dport = "8080" + target = rule.create_target("DNAT") + target.to_destination = '127.0.0.0:' + p + self.chain_nat.insert_rule(rule) + self.table_nat.commit() + self.table_nat.refresh() + + rules = self.chain_nat.rules + for rule in rules: + self.chain_nat.delete_rule(rule) + self.table_nat.commit() + self.table_nat.refresh() + def suite(): suite_table6 = unittest.TestLoader().loadTestsFromTestCase(TestTable6) From 74ea33281e58589b5b357b7d03d7bcb2b36c2599 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 26 Apr 2014 20:19:39 +0200 Subject: [PATCH 041/287] Add missing socket.IPPROTO_* protocols. --- iptc/ip4tc.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 3d66936..3ad30de 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -774,12 +774,32 @@ class Rule(object): * One target. This determines what happens with the packet if it is matched. """ + protocols = {0: "all", + socket.IPPROTO_AH: "ah", + socket.IPPROTO_DSTOPTS: "dstopts", + socket.IPPROTO_EGP: "egp", + socket.IPPROTO_ESP: "esp", + socket.IPPROTO_FRAGMENT: "fragment", + socket.IPPROTO_GRE: "gre", + socket.IPPROTO_HOPOPTS: "hopopts", + socket.IPPROTO_ICMP: "icmp", + socket.IPPROTO_ICMPV6: "icmpv6", + socket.IPPROTO_IDP: "idp", + socket.IPPROTO_IGMP: "igmp", + socket.IPPROTO_IP: "ip", + socket.IPPROTO_IPIP: "ipip", + socket.IPPROTO_IPV6: "ipv6", + socket.IPPROTO_NONE: "none", + socket.IPPROTO_PIM: "pim", + socket.IPPROTO_PUP: "pup", + socket.IPPROTO_RAW: "raw", + socket.IPPROTO_ROUTING: "routing", + socket.IPPROTO_RSVP: "rsvp", socket.IPPROTO_TCP: "tcp", + socket.IPPROTO_TP: "tp", socket.IPPROTO_UDP: "udp", - socket.IPPROTO_ICMP: "icmp", - socket.IPPROTO_ESP: "esp", - socket.IPPROTO_AH: "ah"} + } def __init__(self, entry=None, chain=None): """ From bbe78212d567ae2990f90d81a03b5190920a84e9 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 26 Apr 2014 20:31:18 +0200 Subject: [PATCH 042/287] Add icmpv6 test case. --- iptc/test/test_matches.py | 40 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/iptc/test/test_matches.py b/iptc/test/test_matches.py index 67c37ff..75eab37 100755 --- a/iptc/test/test_matches.py +++ b/iptc/test/test_matches.py @@ -4,6 +4,9 @@ import iptc +is_table6_available = iptc.is_table6_available + + class TestMatch(unittest.TestCase): def setUp(self): pass @@ -197,6 +200,36 @@ def test_limit_insert(self): self.fail("inserted rule does not match original") +class TestIcmpv6Match(unittest.TestCase): + def setUp(self): + self.rule = iptc.Rule6() + self.rule.protocol = "icmpv6" + self.rule.in_interface = "eth0" + + self.target = self.rule.create_target("ACCEPT") + + self.match = self.rule.create_match("icmp6") + self.match.icmpv6_type = "echo-request" + + self.table = iptc.Table6(iptc.Table6.FILTER) + + self.chain = iptc.Chain(self.table, "ip6tc_test_icmpv6") + try: + self.table.delete_chain(self.chain) + except: + pass + self.table.create_chain(self.chain) + + def tearDown(self): + self.chain.flush() + self.chain.delete() + + def test_icmpv6(self): + self.chain.insert_rule(self.rule) + rule = self.chain.rules[0] + self.assertEquals(self.rule, rule) + + class TestCommentMatch(unittest.TestCase): def setUp(self): self.rule = iptc.Rule() @@ -344,9 +377,14 @@ def suite(): suite_state = unittest.TestLoader().loadTestsFromTestCase(TestXTStateMatch) suite_conntrack = unittest.TestLoader().loadTestsFromTestCase( TestXTConntrackMatch) + extra_suites = [] + if is_table6_available(iptc.Table6.FILTER): + extra_suites += unittest.TestLoader().loadTestsFromTestCase( + TestIcmpv6Match) + return unittest.TestSuite([suite_match, suite_udp, suite_mark, suite_limit, suite_comment, suite_iprange, - suite_state, suite_conntrack]) + suite_state, suite_conntrack] + extra_suites) def run_tests(): From ef02dd6484689764f5e8511c5ac2c83403d82bfc Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Wed, 21 May 2014 12:17:19 +0200 Subject: [PATCH 043/287] Use 0xff to mask interface name in iniface_mask/outiface.mask. --- iptc/ip4tc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 3ad30de..6efbf2a 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -1023,7 +1023,7 @@ def set_in_interface(self, intf): self.entry.ip.iniface = "".join([intf, '\x00' * (_IFNAMSIZ - len(intf))]) - self.entry.ip.iniface_mask = "".join(['\x01' * masklen, '\x00' * + self.entry.ip.iniface_mask = "".join(['\xff' * masklen, '\x00' * (_IFNAMSIZ - masklen)]) in_interface = property(get_in_interface, set_in_interface) @@ -1067,7 +1067,7 @@ def set_out_interface(self, intf): self.entry.ip.outiface = "".join([intf, '\x00' * (_IFNAMSIZ - len(intf))]) - self.entry.ip.outiface_mask = "".join(['\x01' * masklen, '\x00' * + self.entry.ip.outiface_mask = "".join(['\xff' * masklen, '\x00' * (_IFNAMSIZ - masklen)]) out_interface = property(get_out_interface, set_out_interface) From 0dcc6d2572d50f657e7bf8a86f9a72807288eab3 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Thu, 22 May 2014 12:22:54 +0200 Subject: [PATCH 044/287] Test rule by inserting and comparing it. --- iptc/test/test_iptc.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/iptc/test/test_iptc.py b/iptc/test/test_iptc.py index 8bd486b..3c69d97 100755 --- a/iptc/test/test_iptc.py +++ b/iptc/test/test_iptc.py @@ -573,6 +573,12 @@ def test_rule_interface(self): self.assertEquals(intf, rule.in_interface) rule.out_interface = intf self.assertEquals(intf, rule.out_interface) + rule.create_target("ACCEPT") + self.chain.insert_rule(rule) + r = self.chain.rules[0] + eq = r == rule + self.chain.flush() + self.assertTrue(eq) # invalid interfaces for intf in ["itsaverylonginterfacename"]: From c6456a205b4e0cef5f4978259b48e0c65525bcd8 Mon Sep 17 00:00:00 2001 From: Nizzan Kruvi Date: Thu, 29 May 2014 19:32:29 +0300 Subject: [PATCH 045/287] Fix issue 71 - Table.flush --- iptc/ip4tc.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 6efbf2a..9c4fdc3 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -1611,10 +1611,11 @@ def _get_chains(self): def flush(self): """Flush and delete all non-builtin chains the table.""" + for chain in self.chains: + chain.flush() for chain in self.chains: if not self.builtin_chain(chain): - chain.flush() - chain.delete() + self.delete_chain(chain) def create_rule(self, entry=None, chain=None): return Rule(entry, chain) From 5348bedb689d872465fbf3d7c6ab511bdb325086 Mon Sep 17 00:00:00 2001 From: Nizzan Kruvi Date: Tue, 3 Jun 2014 07:56:50 +0300 Subject: [PATCH 046/287] Add test for table flush --- iptc/test/test_iptc.py | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/iptc/test/test_iptc.py b/iptc/test/test_iptc.py index 3c69d97..464a0b5 100755 --- a/iptc/test/test_iptc.py +++ b/iptc/test/test_iptc.py @@ -65,10 +65,11 @@ class TestTable(unittest.TestCase): def setUp(self): self.chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), "iptc_test_chain") + iptc.Table(iptc.Table.FILTER).create_chain(self.chain) def tearDown(self): - iptc.Table(iptc.Table.FILTER).delete_chain(self.chain) + iptc.Table(iptc.Table.FILTER).flush() def test_table(self): filt = None @@ -107,6 +108,40 @@ def test_refresh(self): self.chain.insert_rule(rule) self.chain.delete_rule(rule) + def test_flush_user_chains(self): + + chain1 = iptc.Chain(iptc.Table(iptc.Table.FILTER), + "iptc_test_flush_chain1") + chain2 = iptc.Chain(iptc.Table(iptc.Table.FILTER), + "iptc_test_flush_chain2") + iptc.Table(iptc.Table.FILTER).create_chain(chain1) + iptc.Table(iptc.Table.FILTER).create_chain(chain2) + + rule = iptc.Rule() + rule.target = iptc.Target(rule, chain2.name) + chain1.append_rule(rule) + + rule = iptc.Rule() + rule.target = iptc.Target(rule, chain1.name) + chain2.append_rule(rule) + + filter_table = iptc.Table(iptc.Table.FILTER) + filter_table.flush() + + self.assertTrue(not filter_table.is_chain(chain1.name)) + self.assertTrue(not filter_table.is_chain(chain2.name)) + + def test_flush_builtin(self): + rule = iptc.Rule() + rule.target = iptc.Target(rule, "ACCEPT") + filter_table = iptc.Table(iptc.Table.FILTER) + iptc.Chain(filter_table, "OUTPUT").append_rule(rule) + self.assertEquals(len(iptc.Chain(filter_table, "OUTPUT").rules), 1) + + filter_table.flush() + + self.assertEquals(len(iptc.Chain(filter_table, "OUTPUT").rules), 0) + class TestChain(unittest.TestCase): def setUp(self): From 3c3ad1f61818161115d4ff59b21f85c0edb3715a Mon Sep 17 00:00:00 2001 From: Nizzan Kruvi Date: Tue, 3 Jun 2014 08:00:09 +0300 Subject: [PATCH 047/287] table flush test assert rule appendage --- iptc/test/test_iptc.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/iptc/test/test_iptc.py b/iptc/test/test_iptc.py index 464a0b5..85ef3e8 100755 --- a/iptc/test/test_iptc.py +++ b/iptc/test/test_iptc.py @@ -125,6 +125,9 @@ def test_flush_user_chains(self): rule.target = iptc.Target(rule, chain1.name) chain2.append_rule(rule) + self.assertEquals(len(chain1.rules), 1) + self.assertEquals(len(chain2.rules), 1) + filter_table = iptc.Table(iptc.Table.FILTER) filter_table.flush() From d3225c2e6383bcb44512bb07fbe194d34a07703f Mon Sep 17 00:00:00 2001 From: Nizzan Kruvi Date: Tue, 3 Jun 2014 08:14:29 +0300 Subject: [PATCH 048/287] fix flush test to check initial # of rules in OUTPUT table --- iptc/test/test_iptc.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/iptc/test/test_iptc.py b/iptc/test/test_iptc.py index 85ef3e8..a691f85 100755 --- a/iptc/test/test_iptc.py +++ b/iptc/test/test_iptc.py @@ -135,11 +135,14 @@ def test_flush_user_chains(self): self.assertTrue(not filter_table.is_chain(chain2.name)) def test_flush_builtin(self): + output_rule_count = len(iptc.Chain(filter_table, "OUTPUT").rules) + rule = iptc.Rule() rule.target = iptc.Target(rule, "ACCEPT") filter_table = iptc.Table(iptc.Table.FILTER) iptc.Chain(filter_table, "OUTPUT").append_rule(rule) - self.assertEquals(len(iptc.Chain(filter_table, "OUTPUT").rules), 1) + + self.assertEquals(len(iptc.Chain(filter_table, "OUTPUT").rules), output_rule_count + 1) filter_table.flush() From 442f055d39230b65527069526454d6bc8ebc7d6c Mon Sep 17 00:00:00 2001 From: Nizzan Kruvi Date: Tue, 3 Jun 2014 08:18:23 +0300 Subject: [PATCH 049/287] fix test --- iptc/test/test_iptc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/iptc/test/test_iptc.py b/iptc/test/test_iptc.py index a691f85..4fc7e9f 100755 --- a/iptc/test/test_iptc.py +++ b/iptc/test/test_iptc.py @@ -135,11 +135,12 @@ def test_flush_user_chains(self): self.assertTrue(not filter_table.is_chain(chain2.name)) def test_flush_builtin(self): + filter_table = iptc.Table(iptc.Table.FILTER) output_rule_count = len(iptc.Chain(filter_table, "OUTPUT").rules) rule = iptc.Rule() rule.target = iptc.Target(rule, "ACCEPT") - filter_table = iptc.Table(iptc.Table.FILTER) + iptc.Chain(filter_table, "OUTPUT").append_rule(rule) self.assertEquals(len(iptc.Chain(filter_table, "OUTPUT").rules), output_rule_count + 1) From fc072769379a0a6b9f246f238bd474d5a1e4e6a1 Mon Sep 17 00:00:00 2001 From: Nizzan Kruvi Date: Thu, 5 Jun 2014 10:36:04 +0300 Subject: [PATCH 050/287] Support bitmask notation for address netmask --- iptc/ip4tc.py | 29 +++++++++++++++++++++-------- iptc/test/test_iptc.py | 20 ++++++++++++++------ 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 9c4fdc3..baf3388 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -920,12 +920,19 @@ def set_src(self, src): ina.s_addr = ct.c_uint32(saddr) self.entry.ip.src = ina - try: - nmask = _a_to_i(socket.inet_pton(socket.AF_INET, netm)) - except socket.error: - raise ValueError("invalid netmask %s" % (netm)) + if not netm.isdigit(): + try: + nmask = _a_to_i(socket.inet_pton(socket.AF_INET, netm)) + except socket.error: + raise ValueError("invalid netmask %s" % (netm)) + else: + nmask = int(netm) + if nmask > 32 or nmask < 0: + raise ValueError("invalid netmask %s" % (netm)) + nmask = socket.htonl((0xffffffff << (32 - nmask)) & 0xffffffff ) neta = in_addr() neta.s_addr = ct.c_uint32(nmask) + self.entry.ip.smsk = neta src = property(get_src, set_src) @@ -974,10 +981,16 @@ def set_dst(self, dst): ina.s_addr = ct.c_uint32(daddr) self.entry.ip.dst = ina - try: - nmask = _a_to_i(socket.inet_pton(socket.AF_INET, netm)) - except socket.error: - raise ValueError("invalid netmask %s" % (netm)) + if not netm.isdigit(): + try: + nmask = _a_to_i(socket.inet_pton(socket.AF_INET, netm)) + except socket.error: + raise ValueError("invalid netmask %s" % (netm)) + else: + nmask = int(netm) + if nmask > 32 or nmask < 0: + raise ValueError("invalid netmask %s" % (netm)) + nmask = socket.htonl((0xffffffff << (32 - nmask)) & 0xffffffff ) neta = in_addr() neta.s_addr = ct.c_uint32(nmask) self.entry.ip.dmsk = neta diff --git a/iptc/test/test_iptc.py b/iptc/test/test_iptc.py index 4fc7e9f..7df64da 100755 --- a/iptc/test/test_iptc.py +++ b/iptc/test/test_iptc.py @@ -580,11 +580,17 @@ def tearDown(self): def test_rule_address(self): # valid addresses rule = iptc.Rule() - for addr in ["127.0.0.1/255.255.255.0", "!127.0.0.1/255.255.255.0"]: - rule.src = addr - self.assertEquals(rule.src, addr) - rule.dst = addr - self.assertEquals(rule.dst, addr) + for addr in [("127.0.0.1/255.255.255.0", "127.0.0.1/255.255.255.0"), + ("!127.0.0.1/255.255.255.0", "!127.0.0.1/255.255.255.0"), + ("127.0.0.1/255.255.128.0", "127.0.0.1/255.255.128.0"), + ("127.0.0.1/16", "127.0.0.1/255.255.0.0"), + ("127.0.0.1/24", "127.0.0.1/255.255.255.0"), + ("127.0.0.1/17", "127.0.0.1/255.255.128.0"), + ("!127.0.0.1/17", "!127.0.0.1/255.255.128.0")]: + rule.src = addr[0] + self.assertEquals(rule.src, addr[1]) + rule.dst = addr[0] + self.assertEquals(rule.dst, addr[1]) addr = "127.0.0.1" rule.src = addr self.assertEquals("127.0.0.1/255.255.255.255", rule.src) @@ -593,7 +599,8 @@ def test_rule_address(self): # invalid addresses for addr in ["127.256.0.1/255.255.255.0", "127.0.1/255.255.255.0", - "127.0.0.1/255.255.255.", "127.0.0.1 255.255.255.0"]: + "127.0.0.1/255.255.255.", "127.0.0.1 255.255.255.0", + "127.0.0.1/33", "127.0.0.1/-5", "127.0.0.1/255.5"]: try: rule.src = addr except ValueError: @@ -607,6 +614,7 @@ def test_rule_address(self): else: self.fail("rule accepted invalid address %s" % (addr)) + def test_rule_interface(self): # valid interfaces rule = iptc.Rule() From 547de16a61c50330bd65558d1c34baeec8ed21be Mon Sep 17 00:00:00 2001 From: Nizzan Kruvi Date: Fri, 6 Jun 2014 20:28:34 +0300 Subject: [PATCH 051/287] Make bitmask conversion more readable --- iptc/ip4tc.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index baf3388..5199f31 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -926,10 +926,10 @@ def set_src(self, src): except socket.error: raise ValueError("invalid netmask %s" % (netm)) else: - nmask = int(netm) - if nmask > 32 or nmask < 0: + imask = int(netm) + if imask > 32 or imask < 0: raise ValueError("invalid netmask %s" % (netm)) - nmask = socket.htonl((0xffffffff << (32 - nmask)) & 0xffffffff ) + nmask = socket.htonl((2**imask-1) << (32-imask)) neta = in_addr() neta.s_addr = ct.c_uint32(nmask) @@ -987,10 +987,10 @@ def set_dst(self, dst): except socket.error: raise ValueError("invalid netmask %s" % (netm)) else: - nmask = int(netm) - if nmask > 32 or nmask < 0: + imask = int(netm) + if imask > 32 or imask < 0: raise ValueError("invalid netmask %s" % (netm)) - nmask = socket.htonl((0xffffffff << (32 - nmask)) & 0xffffffff ) + nmask = socket.htonl((2**imask-1) << (32-imask)) neta = in_addr() neta.s_addr = ct.c_uint32(nmask) self.entry.ip.dmsk = neta From 1f53d47ec2b1bf2da95feb21584da81b2f5d0cb8 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Fri, 6 Jun 2014 15:39:46 -0400 Subject: [PATCH 052/287] Avoid destroying sys.stdout. --- iptc/ip4tc.py | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 9c4fdc3..44af829 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -291,23 +291,31 @@ def _parse(self, argv, inv, entry): def _get_saved_buf(self, ip): if not self._module or not self._module.save: return None + # redirect C stdout to a pipe and read back the output of m->save - fd = sys.stdout.fileno() - with os.fdopen(os.dup(fd), 'w') as old_stdout: - try: - pipes = os.pipe() - sys.stdout.close() - os.dup2(pipes[1], fd) - sys.stdout = os.fdopen(fd, 'w') - self._xt.save(self._module, ip, self._ptr) - buf = os.read(pipes[0], 1024) - os.close(pipes[0]) - os.close(pipes[1]) - return buf - finally: - sys.stdout.close() - os.dup2(old_stdout.fileno(), fd) - sys.stdout = os.fdopen(fd, 'w') + + # Save the current C stdout. + stdout = os.dup(1) + try: + # Create a pipe and use the write end to replace the original C + # stdout. + pipes = os.pipe() + os.dup2(pipes[1], 1) + self._xt.save(self._module, ip, self._ptr) + + # Use the read end to read whatever was written. + buf = os.read(pipes[0], 1024) + + # Clean up the pipe. + os.close(pipes[0]) + os.close(pipes[1]) + return buf + finally: + # Put the original C stdout back in place. + os.dup2(stdout, 1) + + # Clean up the copy we made. + os.close(stdout) def save(self, name): return self._save(name, self.rule.get_ip()) From bad7996a45c49813bf6d30c5c18a5d256d8a568a Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 15 Jun 2014 15:36:40 +0200 Subject: [PATCH 053/287] Only accept negating '!' as first character. --- iptc/ip4tc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 8c3de29..4ae2940 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -266,9 +266,9 @@ def parse(self, parameter, value): parameter = parameter.rstrip().lstrip() value = value.rstrip().lstrip() - if "!" in value: + if value.startswith("!"): inv = ct.c_int(1) - value = value.replace("!", "") + value = value[1:] else: inv = ct.c_int(0) From 5585a0091c29f699898002dea30569697e56a09d Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 15 Jun 2014 16:28:19 +0200 Subject: [PATCH 054/287] Remove unused import. --- iptc/ip4tc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 4ae2940..44c6fc6 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -6,7 +6,6 @@ import shlex import socket import struct -import sys import weakref from util import find_library, load_kernel From dd627e0fe81cbf3ddcca8eb46f57d72306b7c8cf Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 15 Jun 2014 16:29:06 +0200 Subject: [PATCH 055/287] PEP-8 fixes. --- iptc/ip4tc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 44c6fc6..78df8f8 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -936,7 +936,7 @@ def set_src(self, src): imask = int(netm) if imask > 32 or imask < 0: raise ValueError("invalid netmask %s" % (netm)) - nmask = socket.htonl((2**imask-1) << (32-imask)) + nmask = socket.htonl((2 ** imask - 1) << (32 - imask)) neta = in_addr() neta.s_addr = ct.c_uint32(nmask) @@ -997,7 +997,7 @@ def set_dst(self, dst): imask = int(netm) if imask > 32 or imask < 0: raise ValueError("invalid netmask %s" % (netm)) - nmask = socket.htonl((2**imask-1) << (32-imask)) + nmask = socket.htonl((2 ** imask - 1) << (32 - imask)) neta = in_addr() neta.s_addr = ct.c_uint32(nmask) self.entry.ip.dmsk = neta From cee7d5df979ffc680a2f4fc1dfd6d2d263e1f303 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 15 Jun 2014 16:29:40 +0200 Subject: [PATCH 056/287] Accept list for parameter values. Don't split string values, instead optionally accept lists as parameter values. This fixes #57. --- iptc/ip4tc.py | 9 +++++---- iptc/test/test_matches.py | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 78df8f8..eac8fd5 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -3,7 +3,6 @@ import os import re import ctypes as ct -import shlex import socket import struct import weakref @@ -247,7 +246,7 @@ class iptc(object): class IPTCModule(object): """Superclass for Match and Target.""" pattern = re.compile( - '\s*(!)?\s*--([-\w]+)\s+(!)?\s*("?[^"]*?"?)(?=\s*(?:!?\s*--|$))') + '\s*(!)?\s*--([-\w]+)\s+(!)?\s*"?([^"]*?)"?(?=\s*(?:!?\s*--|$))') def __init__(self): self._name = None @@ -271,8 +270,10 @@ def parse(self, parameter, value): else: inv = ct.c_int(0) - args = shlex.split(value) - if not args: + N = 1 + if isinstance(value, list): + args = value + else: args = [value] N = len(args) argv = (ct.c_char_p * (N + 1))() diff --git a/iptc/test/test_matches.py b/iptc/test/test_matches.py index 75eab37..5fa7f40 100755 --- a/iptc/test/test_matches.py +++ b/iptc/test/test_matches.py @@ -249,9 +249,9 @@ def tearDown(self): def test_comment(self): comment = "comment test" self.match.reset() - self.match.comment = "\"%s\"" % (comment) + self.match.comment = comment self.chain.insert_rule(self.rule) - self.assertEquals(self.match.comment.replace('"', ''), comment) + self.assertEquals(self.match.comment, comment) class TestIprangeMatch(unittest.TestCase): From e25f5dde3f29a9ac65a191959fdb94ca230a7465 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 15 Jun 2014 18:00:26 +0200 Subject: [PATCH 057/287] Use new _Buffer if iptables changes _target_buf. Iptables can reallocate the backing buffer used for Target._target_buf. Use a new _Buffer in this case, so the old backing heap allocation is also freed properly in _Buffer.__del__(). --- iptc/ip4tc.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index eac8fd5..3d594d9 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -669,7 +669,11 @@ def _parse(self, argv, inv, entry): self._xt.parse_target(argv, inv, module, entry, ct.cast(self._ptrptr, ct.POINTER(ct.c_void_p))) self._target_buf = ct.cast(self._module.t, ct.POINTER(ct.c_ubyte)) - self._buffer.buffer = self._target_buf + if self._buffer.buffer != self._target_buf: + if self._buffer.buffer is not None: + self._buffer.buffer = None # Buffer was freed by iptables. + self._buffer = _Buffer() + self._buffer.buffer = self._target_buf self._update_pointers() def _get_size(self): From 85dcdbd3ae8de2c49fa5c07efbee355a16f7efdd Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Mon, 21 Jul 2014 12:37:07 +0200 Subject: [PATCH 058/287] Use relative imports. --- iptc/ip4tc.py | 6 +++--- iptc/ip6tc.py | 6 +++--- iptc/xtables.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 3d594d9..e1dd897 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -7,9 +7,9 @@ import struct import weakref -from util import find_library, load_kernel -from xtables import (XT_INV_PROTO, NFPROTO_IPV4, XTablesError, xtables, - xt_align, xt_counters, xt_entry_target, xt_entry_match) +from .util import find_library, load_kernel +from .xtables import (XT_INV_PROTO, NFPROTO_IPV4, XTablesError, xtables, + xt_align, xt_counters, xt_entry_target, xt_entry_match) __all__ = ["Table", "Chain", "Rule", "Match", "Target", "Policy", "IPTCError"] diff --git a/iptc/ip6tc.py b/iptc/ip6tc.py index 10422d8..b07b764 100644 --- a/iptc/ip6tc.py +++ b/iptc/ip6tc.py @@ -4,9 +4,9 @@ import socket import weakref -from ip4tc import Rule, Table, IPTCError -from util import find_library, load_kernel -from xtables import (XT_INV_PROTO, NFPROTO_IPV6, xt_align, xt_counters) +from .ip4tc import Rule, Table, IPTCError +from .util import find_library, load_kernel +from .xtables import (XT_INV_PROTO, NFPROTO_IPV6, xt_align, xt_counters) __all__ = ["Table6", "Rule6"] diff --git a/iptc/xtables.py b/iptc/xtables.py index 7b1e21a..eb746d3 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -6,7 +6,7 @@ import weakref import version -from util import find_library +from .util import find_library XT_INV_PROTO = 0x40 # invert the sense of PROTO From 36365ba54a1e95e296cc8fdb0cc44482d7f30377 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Mon, 21 Jul 2014 12:39:27 +0200 Subject: [PATCH 059/287] Use sysconfig.get_config_var() to get shared lib extension. --- iptc/util.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/iptc/util.py b/iptc/util.py index 4b4215c..047c3e5 100644 --- a/iptc/util.py +++ b/iptc/util.py @@ -2,6 +2,7 @@ import ctypes import ctypes.util from subprocess import Popen, PIPE +from sysconfig import get_config_var def _insert_ko(modprobe, modname): @@ -58,8 +59,9 @@ def _do_find_library(name): def _find_library(*names): + ext = get_config_var('SO') for name in names: - for n in (name, "lib" + name, name + ".so", "lib" + name + ".so"): + for n in (name, "lib" + name, name + ext, "lib" + name + ext): lib = _do_find_library(n) if lib is not None: yield lib From 707618b95ceb9f3a140f325d56737962419a83ce Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Mon, 21 Jul 2014 12:44:06 +0200 Subject: [PATCH 060/287] Use relative imports. --- iptc/xtables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index eb746d3..64d12f2 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -4,8 +4,8 @@ import os import sys import weakref -import version +from . import version from .util import find_library XT_INV_PROTO = 0x40 # invert the sense of PROTO From 4fd26bb3649d0a5ab40b1a34b90ee38cc8d80b68 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Mon, 21 Jul 2014 13:46:44 +0200 Subject: [PATCH 061/287] Python2.6 does not have sysconfig. --- iptc/util.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/iptc/util.py b/iptc/util.py index 047c3e5..b6e92f6 100644 --- a/iptc/util.py +++ b/iptc/util.py @@ -2,7 +2,11 @@ import ctypes import ctypes.util from subprocess import Popen, PIPE -from sysconfig import get_config_var +try: + from sysconfig import get_config_var +except ImportError: + def get_config_var(): + return '.so' def _insert_ko(modprobe, modname): From 9b4393d15b6b2f04dd59ac81ede052e4a8e56422 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Mon, 21 Jul 2014 13:57:50 +0200 Subject: [PATCH 062/287] Fix last commit. --- iptc/util.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/iptc/util.py b/iptc/util.py index b6e92f6..ec1f505 100644 --- a/iptc/util.py +++ b/iptc/util.py @@ -5,8 +5,10 @@ try: from sysconfig import get_config_var except ImportError: - def get_config_var(): - return '.so' + def get_config_var(name): + if name == 'SO': + return '.so' + raise Exception('Not implemented') def _insert_ko(modprobe, modname): From a8c27ec8cd91523ab5295d524dd9d29f648de1cc Mon Sep 17 00:00:00 2001 From: Fr3ya Date: Tue, 22 Jul 2014 19:04:32 +0900 Subject: [PATCH 063/287] changing xrange to range, solving some encoding issues --- iptc/ip4tc.py | 155 +++++++++++++++++++++-------------------- iptc/ip6tc.py | 32 ++++----- iptc/test/test_iptc.py | 2 +- iptc/xtables.py | 11 +-- 4 files changed, 104 insertions(+), 96 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 04cb558..74bfa1f 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -258,13 +258,18 @@ def __init__(self): raise NotImplementedError() def parse(self, parameter, value): + if isinstance(parameter, str): + parameter = parameter.encode() + if isinstance(value, str): + value = value.encode() + if not self._module.extra_opts and not self._module.x6_options: raise AttributeError("%s: invalid parameter %s" % (self._module.name, parameter)) parameter = parameter.rstrip().lstrip() value = value.rstrip().lstrip() - if value.startswith("!"): + if value.startswith(b"!"): inv = ct.c_int(1) value = value[1:] else: @@ -278,7 +283,7 @@ def parse(self, parameter, value): N = len(args) argv = (ct.c_char_p * (N + 1))() argv[0] = parameter - for i in xrange(N): + for i in range(N): argv[i + 1] = args[i] entry = self._rule.entry and ct.pointer(self._rule.entry) or None @@ -529,9 +534,9 @@ def _update_pointers(self): def _update_name(self): m = self._ptr[0] if self._real_name is not None: - m.u.user.name = self._real_name + m.u.user.name = self._real_name.encode("ascii") else: - m.u.user.name = self.name + m.u.user.name = self.name.encode("ascii") def reset(self): """Reset the match. @@ -587,7 +592,7 @@ def __init__(self, rule, name=None, target=None, revision=None): if name is None and target is None: raise ValueError("can't create target based on nothing") if name is None: - name = target.u.user.name + name = target.u.user.name.decode() self._name = name self._rule = rule @@ -596,6 +601,7 @@ def __init__(self, rule, name=None, target=None, revision=None): module = (self._is_standard_target() and self._xt.find_target('standard') or self._xt.find_target(name)) + if not module: raise XTablesError("can't find target %s" % (name)) self._module = module[0] @@ -616,12 +622,12 @@ def __eq__(self, targ): self.target.u.user.name != targ.target.u.user.name or self.target.u.user.revision != targ.target.u.user.revision): return False - if (self.target.u.user.name == "" or - self.target.u.user.name == "standard" or - self.target.u.user.name == "ACCEPT" or - self.target.u.user.name == "DROP" or - self.target.u.user.name == "RETURN" or - self.target.u.user.name == "ERROR"): + if (self.target.u.user.name == b"" or + self.target.u.user.name == b"standard" or + self.target.u.user.name == b"ACCEPT" or + self.target.u.user.name == b"DROP" or + self.target.u.user.name == b"RETURN" or + self.target.u.user.name == b"ERROR"): return True if (self._target_buf[basesz:self.usersize] == targ._target_buf[basesz:targ.usersize]): @@ -689,11 +695,15 @@ def _get_user_size(self): def _get_standard_target(self): t = self._ptr[0] - return t.u.user.name + return t.u.user.name.decode() def _set_standard_target(self, name): t = self._ptr[0] + if isinstance(name, str): + name = name.encode() t.u.user.name = name + if isinstance(name, bytes): + name = name.decode() self._name = name standard_target = property(_get_standard_target, _set_standard_target) """This attribute is used for standard targets. It can be set to @@ -713,9 +723,9 @@ def _update_pointers(self): def _update_name(self): m = self._ptr[0] if self._real_name is not None: - m.u.user.name = self._real_name + m.u.user.name = self._real_name.encode() else: - m.u.user.name = self.name + m.u.user.name = self.name.encode() def reset(self): """Reset the target. Parameters are set to their default values, any @@ -1014,22 +1024,19 @@ def set_dst(self, dst): def get_in_interface(self): intf = "" if self.entry.ip.invflags & ipt_ip.IPT_INV_VIA_IN: - intf = "".join(["!", intf]) - iface = bytearray(_IFNAMSIZ) - iface[:len(self.entry.ip.iniface)] = self.entry.ip.iniface - mask = bytearray(_IFNAMSIZ) - mask[:len(self.entry.ip.iniface_mask)] = self.entry.ip.iniface_mask - if mask[0] == 0: + intf = "!" + + iface = self.entry.ip.iniface.decode() + mask = self.entry.ip.iniface_mask.decode() + + if len(mask) == 0: return None - for i in xrange(_IFNAMSIZ): - if mask[i] != 0: - intf = "".join([intf, chr(iface[i])]) - else: - if iface[i - 1] != 0: - intf = "".join([intf, "+"]) - else: - intf = intf[:-1] - break + + intf += iface + if len(iface) == len(mask): + intf += '+' + intf = intf[:_IFNAMSIZ] + return intf def set_in_interface(self, intf): @@ -1046,10 +1053,10 @@ def set_in_interface(self, intf): intf = intf[:-1] masklen -= 2 - self.entry.ip.iniface = "".join([intf, '\x00' * (_IFNAMSIZ - - len(intf))]) - self.entry.ip.iniface_mask = "".join(['\xff' * masklen, '\x00' * - (_IFNAMSIZ - masklen)]) + self.entry.ip.iniface = ("".join([intf, '\x00' * (_IFNAMSIZ - + len(intf))])).encode() + self.entry.ip.iniface_mask = ("".join(['\xff' * masklen, '\x00' * + (_IFNAMSIZ - masklen)])).encode() in_interface = property(get_in_interface, set_in_interface) """This is the input network interface e.g. *eth0*. A wildcard match can @@ -1058,22 +1065,19 @@ def set_in_interface(self, intf): def get_out_interface(self): intf = "" if self.entry.ip.invflags & ipt_ip.IPT_INV_VIA_OUT: - intf = "".join(["!", intf]) - iface = bytearray(_IFNAMSIZ) - iface[:len(self.entry.ip.outiface)] = self.entry.ip.outiface - mask = bytearray(_IFNAMSIZ) - mask[:len(self.entry.ip.outiface_mask)] = self.entry.ip.outiface_mask - if mask[0] == 0: + intf = "!" + + iface = self.entry.ip.outiface.decode() + mask = self.entry.ip.outiface_mask.decode() + + if len(mask) == 0: return None - for i in xrange(_IFNAMSIZ): - if mask[i] != 0: - intf = "".join([intf, chr(iface[i])]) - else: - if iface[i - 1] != 0: - intf = "".join([intf, "+"]) - else: - intf = intf[:-1] - break + + intf += iface + if len(iface) == len(mask): + intf += '+' + intf = intf[:_IFNAMSIZ] + return intf def set_out_interface(self, intf): @@ -1090,10 +1094,10 @@ def set_out_interface(self, intf): intf = intf[:-1] masklen -= 2 - self.entry.ip.outiface = "".join([intf, '\x00' * (_IFNAMSIZ - - len(intf))]) - self.entry.ip.outiface_mask = "".join(['\xff' * masklen, '\x00' * - (_IFNAMSIZ - masklen)]) + self.entry.ip.outiface = ("".join([intf, '\x00' * (_IFNAMSIZ - + len(intf))])).encode() + self.entry.ip.outiface_mask = ("".join(['\xff' * masklen, '\x00' * + (_IFNAMSIZ - masklen)])).encode() out_interface = property(get_out_interface, set_out_interface) """This is the output network interface e.g. *eth0*. A wildcard match can @@ -1234,14 +1238,14 @@ def _get_mask(self): # fill it out pos = 0 - for i in xrange(pos, pos + entrysz): + for i in range(pos, pos + entrysz): mask[i] = 0xff pos += entrysz for m in self._matches: - for i in xrange(pos, pos + m.usersize): + for i in range(pos, pos + m.usersize): mask[i] = 0xff pos += m.size - for i in xrange(pos, pos + self._target.usersize): + for i in range(pos, pos + self._target.usersize): mask[i] = 0xff return mask @@ -1388,15 +1392,15 @@ class Table(object): low-level details from the user. """ - FILTER = b"filter" + FILTER = "filter" """This is the constant for the filter table.""" - MANGLE = b"mangle" + MANGLE = "mangle" """This is the constant for the mangle table.""" - RAW = b"raw" + RAW = "raw" """This is the constant for the raw table.""" - NAT = b"nat" + NAT = "nat" """This is the constant for the nat table.""" - ALL = [b"filter", b"mangle", b"raw", b"nat"] + ALL = ["filter", "mangle", "raw", "nat"] """This is the constant for all tables.""" _cache = dict() @@ -1457,7 +1461,7 @@ def refresh(self): if self._handle: self._free() - handle = self._iptc.iptc_init(self.name) + handle = self._iptc.iptc_init(self.name.encode()) if not handle: raise IPTCError("can't initialize %s: %s" % (self.name, self.strerror())) @@ -1467,7 +1471,7 @@ def is_chain(self, chain): """Returns *True* if *chain* exists as a chain.""" if isinstance(chain, Chain): chain = chain.name - if self._iptc.iptc_is_chain(chain, self._handle): + if self._iptc.iptc_is_chain(chain.encode(), self._handle): return True else: return False @@ -1476,7 +1480,7 @@ def builtin_chain(self, chain): """Returns *True* if *chain* is a built-in chain.""" if isinstance(chain, Chain): chain = chain.name - if self._iptc.iptc_builtin(chain, self._handle): + if self._iptc.iptc_builtin(chain.encode("ascii"), self._handle): return True else: return False @@ -1493,7 +1497,7 @@ def create_chain(self, chain): """Create a new chain *chain*.""" if isinstance(chain, Chain): chain = chain.name - rv = self._iptc.iptc_create_chain(chain, self._handle) + rv = self._iptc.iptc_create_chain(chain.encode(), self._handle) if rv != 1: raise IPTCError("can't create chain %s: %s" % (chain, self.strerror())) @@ -1504,7 +1508,7 @@ def delete_chain(self, chain): """Delete chain *chain* from the table.""" if isinstance(chain, Chain): chain = chain.name - rv = self._iptc.iptc_delete_chain(chain, self._handle) + rv = self._iptc.iptc_delete_chain(chain.encode(), self._handle) if rv != 1: raise IPTCError("can't delete chain %s: %s" % (chain, self.strerror())) @@ -1514,7 +1518,7 @@ def rename_chain(self, chain, new_name): """Rename chain *chain* to *new_name*.""" if isinstance(chain, Chain): chain = chain.name - rv = self._iptc.iptc_rename_chain(chain, new_name, self._handle) + rv = self._iptc.iptc_rename_chain(chain.encode(), new_name.encode(), self._handle) if rv != 1: raise IPTCError("can't rename chain %s: %s" % (chain, self.strerror())) @@ -1524,7 +1528,7 @@ def flush_entries(self, chain): """Flush all rules from *chain*.""" if isinstance(chain, Chain): chain = chain.name - rv = self._iptc.iptc_flush_entries(chain, self._handle) + rv = self._iptc.iptc_flush_entries(chain.encode(), self._handle) if rv != 1: raise IPTCError("can't flush chain %s: %s" % (chain, self.strerror())) @@ -1534,7 +1538,7 @@ def zero_entries(self, chain): """Zero the packet and byte counters of *chain*.""" if isinstance(chain, Chain): chain = chain.name - rv = self._iptc.iptc_zero_entries(chain, self._handle) + rv = self._iptc.iptc_zero_entries(chain.encode(), self._handle) if rv != 1: raise IPTCError("can't zero chain %s counters: %s" % (chain, self.strerror())) @@ -1554,7 +1558,7 @@ def set_policy(self, chain, policy, counters=None): cntrs = ct.pointer(cntrs) else: cntrs = None - rv = self._iptc.iptc_set_policy(chain, policy, cntrs, self._handle) + rv = self._iptc.iptc_set_policy(chain.encode(), policy.encode(), cntrs, self._handle) if rv != 1: raise IPTCError("can't set policy %s on chain %s: %s)" % (policy, chain, self.strerror())) @@ -1567,8 +1571,8 @@ def get_policy(self, chain): if not self.builtin_chain(chain): return None, None cntrs = xt_counters() - pol = self._iptc.iptc_get_policy(chain, ct.pointer(cntrs), - self._handle) + pol = self._iptc.iptc_get_policy(chain.encode(), ct.pointer(cntrs), + self._handle).decode() if not pol: raise IPTCError("can't get policy on chain %s: %s" % (chain, self.strerror())) @@ -1577,7 +1581,7 @@ def get_policy(self, chain): @autocommit def append_entry(self, chain, entry): """Appends rule *entry* to *chain*.""" - rv = self._iptc.iptc_append_entry(chain, ct.cast(entry, ct.c_void_p), + rv = self._iptc.iptc_append_entry(chain.encode(), ct.cast(entry, ct.c_void_p), self._handle) if rv != 1: raise IPTCError("can't append entry to chain %s: %s)" % @@ -1586,7 +1590,7 @@ def append_entry(self, chain, entry): @autocommit def insert_entry(self, chain, entry, position): """Inserts rule *entry* into *chain* at position *position*.""" - rv = self._iptc.iptc_insert_entry(chain, ct.cast(entry, ct.c_void_p), + rv = self._iptc.iptc_insert_entry(chain.encode(), ct.cast(entry, ct.c_void_p), position, self._handle) if rv != 1: raise IPTCError("can't insert entry into chain %s: %s)" % @@ -1595,7 +1599,7 @@ def insert_entry(self, chain, entry, position): @autocommit def delete_entry(self, chain, entry, mask): """Removes rule *entry* with *mask* from *chain*.""" - rv = self._iptc.iptc_delete_entry(chain, ct.cast(entry, ct.c_void_p), + rv = self._iptc.iptc_delete_entry(chain.encode(), ct.cast(entry, ct.c_void_p), mask, self._handle) if rv != 1: raise IPTCError("can't delete entry from chain %s: %s)" % @@ -1603,7 +1607,7 @@ def delete_entry(self, chain, entry, mask): def first_rule(self, chain): """Returns the first rule in *chain* or *None* if it is empty.""" - rule = self._iptc.iptc_first_rule(chain, self._handle) + rule = self._iptc.iptc_first_rule(chain.encode(), self._handle) if rule: return rule[0] else: @@ -1627,6 +1631,7 @@ def _get_chains(self): chains = [] chain = self._iptc.iptc_first_chain(self._handle) while chain: + chain = chain.decode() chains.append(Chain(self, chain)) chain = self._iptc.iptc_next_chain(self._handle) return chains diff --git a/iptc/ip6tc.py b/iptc/ip6tc.py index f3944c0..91ce747 100644 --- a/iptc/ip6tc.py +++ b/iptc/ip6tc.py @@ -266,7 +266,7 @@ def _count_bits(self, n): return bits def _create_mask(self, plen): - mask = [0 for x in xrange(16)] + mask = [0 for x in range(16)] i = 0 while plen > 0: if plen >= 8: @@ -419,7 +419,7 @@ def get_in_interface(self): mask[:len(self.entry.ipv6.iniface_mask)] = self.entry.ipv6.iniface_mask if mask[0] == 0: return None - for i in xrange(_IFNAMSIZ): + for i in range(_IFNAMSIZ): if mask[i] != 0: intf = "".join([intf, chr(iface[i])]) else: @@ -444,10 +444,10 @@ def set_in_interface(self, intf): intf = intf[:-1] masklen -= 2 - self.entry.ipv6.iniface = "".join([intf, '\x00' * (_IFNAMSIZ - - len(intf))]) - self.entry.ipv6.iniface_mask = "".join(['\x01' * masklen, '\x00' * - (_IFNAMSIZ - masklen)]) + self.entry.ipv6.iniface = ("".join([intf, '\x00' * (_IFNAMSIZ - + len(intf))])).encode() + self.entry.ipv6.iniface_mask = ("".join(['\x01' * masklen, '\x00' * + (_IFNAMSIZ - masklen)])).encode() in_interface = property(get_in_interface, set_in_interface) """This is the input network interface e.g. *eth0*. A wildcard match can @@ -464,7 +464,7 @@ def get_out_interface(self): self.entry.ipv6.outiface_mask if mask[0] == 0: return None - for i in xrange(_IFNAMSIZ): + for i in range(_IFNAMSIZ): if mask[i] != 0: intf = "".join([intf, chr(iface[i])]) else: @@ -489,10 +489,10 @@ def set_out_interface(self, intf): intf = intf[:-1] masklen -= 2 - self.entry.ipv6.outiface = "".join([intf, '\x00' * (_IFNAMSIZ - - len(intf))]) - self.entry.ipv6.outiface_mask = "".join(['\x01' * masklen, '\x00' * - (_IFNAMSIZ - masklen)]) + self.entry.ipv6.outiface = ("".join([intf, '\x00' * (_IFNAMSIZ - + len(intf))])).encode() + self.entry.ipv6.outiface_mask = ("".join(['\x01' * masklen, '\x00' * + (_IFNAMSIZ - masklen)])).encode() out_interface = property(get_out_interface, set_out_interface) """This is the output network interface e.g. *eth0*. A wildcard match can @@ -558,15 +558,15 @@ class Table6(Table): low-level details from the user. """ - FILTER = b"filter" + FILTER = "filter" """This is the constant for the filter table.""" - MANGLE = b"mangle" + MANGLE = "mangle" """This is the constant for the mangle table.""" - RAW = b"raw" + RAW = "raw" """This is the constant for the raw table.""" - SECURITY = b"security" + SECURITY = "security" """This is the constant for the security table.""" - ALL = [b"filter", b"mangle", b"raw", b"security"] + ALL = ["filter", "mangle", "raw", "security"] """This is the constant for all tables.""" _cache = dict() diff --git a/iptc/test/test_iptc.py b/iptc/test/test_iptc.py index 7df64da..c2e32ba 100755 --- a/iptc/test/test_iptc.py +++ b/iptc/test/test_iptc.py @@ -288,7 +288,7 @@ def test_chain_counters(self): for chain in (chain for table in tables for chain in table.chains): counters = chain.get_counters() fails = 0 - for x in xrange(3): # try 3 times + for x in range(3): # try 3 times chain.zero_counters() counters = chain.get_counters() if counters: # only built-in chains diff --git a/iptc/xtables.py b/iptc/xtables.py index 64d12f2..fa10bea 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -787,8 +787,8 @@ def _xtinit(self, proto): self.proto = proto self._xt_globals = xtables_globals() self._xt_globals.option_offset = 0 - self._xt_globals.program_name = version.__pkgname__ - self._xt_globals.program_version = version.__version__ + self._xt_globals.program_name = version.__pkgname__.encode() + self._xt_globals.program_version = version.__version__.encode() self._xt_globals.orig_opts = None self._xt_globals.opts = None self._xt_globals.exit_err = _xt_exit @@ -888,6 +888,8 @@ def _get_prefix(self): raise XTablesError("Unknown protocol %d" % (self.proto)) def _try_register(self, name): + if isinstance(name, bytes): + name = name.decode() if self._try_extinit(name, _lib_xtables): return prefix = self._get_prefix() @@ -899,7 +901,7 @@ def _try_register(self, name): @preserve_globals def find_match(self, name): - name = self._check_extname(name) + name = self._check_extname(name.encode()) match = xtables._xtables_find_match(name, XTF_TRY_LOAD, None) if not match: self._try_register(name) @@ -912,8 +914,9 @@ def find_match(self, name): @preserve_globals def find_target(self, name): - name = self._check_extname(name) + name = self._check_extname(name.encode()) target = xtables._xtables_find_target(name, XTF_TRY_LOAD) + return ct.cast(target, ct.POINTER(self._target_struct)) if not target: self._try_register(name) target = xtables._xtables_find_target(name, XTF_TRY_LOAD) From 6f749a79840f60966aff6093770d2e5fb5985a2f Mon Sep 17 00:00:00 2001 From: Fr3ya Date: Tue, 22 Jul 2014 19:28:45 +0900 Subject: [PATCH 064/287] correcting another compatibility issue --- iptc/ip4tc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 74bfa1f..c79f515 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -326,7 +326,7 @@ def save(self, name): return self._save(name, self.rule.get_ip()) def _save(self, name, ip): - buf = self._get_saved_buf(ip) + buf = self._get_saved_buf(ip).decode() if buf is None: return None if not self._module or not self._module.save: From ffd145d56a9889e5ee3535c598b5ccbe71b5f055 Mon Sep 17 00:00:00 2001 From: Fr3ya Date: Tue, 22 Jul 2014 19:47:54 +0900 Subject: [PATCH 065/287] correcting errors in test file --- iptc/test/test_targets.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/iptc/test/test_targets.py b/iptc/test/test_targets.py index 32516bd..a28f655 100755 --- a/iptc/test/test_targets.py +++ b/iptc/test/test_targets.py @@ -419,8 +419,7 @@ def suite(): suite_ct = unittest.TestLoader().loadTestsFromTestCase(TestXTCtTarget) suites.extend([suite_target, suite_cluster, suite_tos]) if is_table_available(iptc.Table.NAT): - suites.extend([suite_target, suite_cluster, suite_redir, suite_tos, - suite_masq, suite_dnat]) + suites.extend([suite_redir, suite_masq, suite_dnat]) if is_table_available(iptc.Table.RAW): suites.extend([suite_notrack, suite_ct]) return unittest.TestSuite(suites) From ce3067db803c9d01123f7a4ae24cee05fae8cc62 Mon Sep 17 00:00:00 2001 From: Fr3ya Date: Tue, 22 Jul 2014 20:54:14 +0900 Subject: [PATCH 066/287] encoding issues --- iptc/ip4tc.py | 10 +++++----- iptc/xtables.py | 8 ++++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index c79f515..e8ad018 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -442,7 +442,7 @@ def __init__(self, rule, name=None, match=None, revision=None): if not name and not match: raise ValueError("can't create match based on nothing") if not name: - name = match.u.user.name + name = match.u.user.name.decode() self._name = name self._rule = rule self._alias = None @@ -534,9 +534,9 @@ def _update_pointers(self): def _update_name(self): m = self._ptr[0] if self._real_name is not None: - m.u.user.name = self._real_name.encode("ascii") + m.u.user.name = self._real_name.encode() else: - m.u.user.name = self.name.encode("ascii") + m.u.user.name = self.name.encode() def reset(self): """Reset the match. @@ -643,7 +643,7 @@ def _check_alias(self, module, target): # will be used to parse parameters, since the 'real' extension # probably won't understand them. if getattr(module, "real_name", None) is not None and module.real_name: - self._real_name = module.real_name + self._real_name = module.real_name.decode() if getattr(module, "alias", None) is not None and module.alias: self._alias_name = module.alias(target) alias = self._xt.find_target(self._alias_name) @@ -1480,7 +1480,7 @@ def builtin_chain(self, chain): """Returns *True* if *chain* is a built-in chain.""" if isinstance(chain, Chain): chain = chain.name - if self._iptc.iptc_builtin(chain.encode("ascii"), self._handle): + if self._iptc.iptc_builtin(chain.encode(), self._handle): return True else: return False diff --git a/iptc/xtables.py b/iptc/xtables.py index fa10bea..3f0bdee 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -901,7 +901,9 @@ def _try_register(self, name): @preserve_globals def find_match(self, name): - name = self._check_extname(name.encode()) + if isinstance(name, str): + name = name.encode() + name = self._check_extname(name) match = xtables._xtables_find_match(name, XTF_TRY_LOAD, None) if not match: self._try_register(name) @@ -914,7 +916,9 @@ def find_match(self, name): @preserve_globals def find_target(self, name): - name = self._check_extname(name.encode()) + if isinstance(name, str): + name = name.encode() + name = self._check_extname(name) target = xtables._xtables_find_target(name, XTF_TRY_LOAD) return ct.cast(target, ct.POINTER(self._target_struct)) if not target: From 79da2f720b9577a0f0eba409690eb3c0341457dd Mon Sep 17 00:00:00 2001 From: Fr3ya Date: Tue, 22 Jul 2014 21:07:46 +0900 Subject: [PATCH 067/287] set() need a __hash__ function to work --- iptc/ip4tc.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index e8ad018..a76d98d 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -479,6 +479,10 @@ def __eq__(self, match): return True return False + def __hash__(self): + basesz = ct.sizeof(xt_entry_match) + return hash(self.match.u.match_size) ^ hash(self.match.u.user.name) ^ hash(self.match.u.user.revision) + hash(bytes(self.match_buf)) + def __ne__(self, match): return not self.__eq__(match) From 0c72fd49f963664c5e311534ff07f1f91f9ee627 Mon Sep 17 00:00:00 2001 From: Fr3ya Date: Tue, 22 Jul 2014 21:10:00 +0900 Subject: [PATCH 068/287] small mistake in previous commit --- iptc/ip4tc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index a76d98d..a783981 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -481,7 +481,7 @@ def __eq__(self, match): def __hash__(self): basesz = ct.sizeof(xt_entry_match) - return hash(self.match.u.match_size) ^ hash(self.match.u.user.name) ^ hash(self.match.u.user.revision) + hash(bytes(self.match_buf)) + return hash(self.match.u.match_size) ^ hash(self.match.u.user.name) ^ hash(self.match.u.user.revision) ^ hash(bytes(self.match_buf)) def __ne__(self, match): return not self.__eq__(match) From 855c72f6bd937793da10d48d738a184792fa8587 Mon Sep 17 00:00:00 2001 From: Fr3ya Date: Wed, 23 Jul 2014 00:17:56 +0900 Subject: [PATCH 069/287] typing error --- iptc/xtables.py | 1 - 1 file changed, 1 deletion(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index 3f0bdee..e127204 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -920,7 +920,6 @@ def find_target(self, name): name = name.encode() name = self._check_extname(name) target = xtables._xtables_find_target(name, XTF_TRY_LOAD) - return ct.cast(target, ct.POINTER(self._target_struct)) if not target: self._try_register(name) target = xtables._xtables_find_target(name, XTF_TRY_LOAD) From 000d7e21cffdfe273a9d41698512a59dc6dc177b Mon Sep 17 00:00:00 2001 From: Fr3ya Date: Wed, 23 Jul 2014 01:52:39 +0900 Subject: [PATCH 070/287] TypeError: expected an object with a buffer interface is now solved --- iptc/ip4tc.py | 2 +- iptc/ip6tc.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index a783981..f70854f 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -492,7 +492,7 @@ def _check_alias(self, module, match): # will be used to parse parameters, since the 'real' extension # probably won't understand them. if getattr(module, "real_name", None) is not None and module.real_name: - self._real_name = module.real_name + self._real_name = module.real_name.decode() if getattr(module, "alias", None) is not None and module.alias: self._alias_name = module.alias(match) alias = self._xt.find_match(self._alias_name) diff --git a/iptc/ip6tc.py b/iptc/ip6tc.py index 91ce747..be27e54 100644 --- a/iptc/ip6tc.py +++ b/iptc/ip6tc.py @@ -266,16 +266,16 @@ def _count_bits(self, n): return bits def _create_mask(self, plen): - mask = [0 for x in range(16)] - i = 0 - while plen > 0: + mask = bytes() + for i in range(16): if plen >= 8: - mask[i] = 0xff + mask += bytes([0xff]) + elif plen > 0: + mask += bytes([2 ** plen - 1]) else: - mask[i] = 2 ** plen - 1 - i += 1 + mask += bytes([0x00]) plen -= 8 - return "".join([chr(x) for x in mask]) + return mask def get_src(self): src = "" From d8881cf6d9711de7a30bc6eee43afd5761f6f072 Mon Sep 17 00:00:00 2001 From: Fr3ya Date: Wed, 23 Jul 2014 02:51:12 +0900 Subject: [PATCH 071/287] XTableError solved --- iptc/xtables.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index e127204..045a712 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -846,8 +846,8 @@ def _restore_globals(self): xtables._xtables_pending_targets.value = self._pending_targets def _check_extname(self, name): - if name in ["", "ACCEPT", "DROP", "QUEUE", "RETURN"]: - name = "standard" + if name in [b"", b"ACCEPT", b"DROP", b"QUEUE", b"RETURN"]: + name = b"standard" return name def _loaded(self, name): From 59bb4c6f193916e019fa3e17ea3163fdabdef536 Mon Sep 17 00:00:00 2001 From: Fr3ya Date: Wed, 23 Jul 2014 07:31:52 +0900 Subject: [PATCH 072/287] unit tests working in both python2.7 and python3 --- iptc/ip4tc.py | 20 ++++++++++---------- iptc/ip6tc.py | 14 ++++++-------- test.py | 5 +++++ 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index f70854f..22b121b 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -1031,7 +1031,7 @@ def get_in_interface(self): intf = "!" iface = self.entry.ip.iniface.decode() - mask = self.entry.ip.iniface_mask.decode() + mask = self.entry.ip.iniface_mask if len(mask) == 0: return None @@ -1057,10 +1057,10 @@ def set_in_interface(self, intf): intf = intf[:-1] masklen -= 2 - self.entry.ip.iniface = ("".join([intf, '\x00' * (_IFNAMSIZ - - len(intf))])).encode() - self.entry.ip.iniface_mask = ("".join(['\xff' * masklen, '\x00' * - (_IFNAMSIZ - masklen)])).encode() + self.entry.ip.iniface = b"".join([intf.encode(), b'\x00' * (_IFNAMSIZ - + len(intf))]) + self.entry.ip.iniface_mask = b"".join([b'\xff' * masklen, b'\x00' * + (_IFNAMSIZ - masklen)]) in_interface = property(get_in_interface, set_in_interface) """This is the input network interface e.g. *eth0*. A wildcard match can @@ -1072,7 +1072,7 @@ def get_out_interface(self): intf = "!" iface = self.entry.ip.outiface.decode() - mask = self.entry.ip.outiface_mask.decode() + mask = self.entry.ip.outiface_mask if len(mask) == 0: return None @@ -1098,10 +1098,10 @@ def set_out_interface(self, intf): intf = intf[:-1] masklen -= 2 - self.entry.ip.outiface = ("".join([intf, '\x00' * (_IFNAMSIZ - - len(intf))])).encode() - self.entry.ip.outiface_mask = ("".join(['\xff' * masklen, '\x00' * - (_IFNAMSIZ - masklen)])).encode() + self.entry.ip.outiface = b"".join([intf.encode(), b'\x00' * (_IFNAMSIZ - + len(intf))]) + self.entry.ip.outiface_mask = b"".join([b'\xff' * masklen, b'\x00' * + (_IFNAMSIZ - masklen)]) out_interface = property(get_out_interface, set_out_interface) """This is the output network interface e.g. *eth0*. A wildcard match can diff --git a/iptc/ip6tc.py b/iptc/ip6tc.py index be27e54..96535d6 100644 --- a/iptc/ip6tc.py +++ b/iptc/ip6tc.py @@ -266,14 +266,14 @@ def _count_bits(self, n): return bits def _create_mask(self, plen): - mask = bytes() + mask = [] for i in range(16): if plen >= 8: - mask += bytes([0xff]) + mask.append(0xff) elif plen > 0: - mask += bytes([2 ** plen - 1]) + mask.append(2 ** plen - 1) else: - mask += bytes([0x00]) + mask.append(0x00) plen -= 8 return mask @@ -336,8 +336,7 @@ def set_src(self, src): plen = int(netm) if plen < 0 or plen > 128: raise ValueError("invalid prefix length %d" % (plen)) - self.entry.ipv6.smsk.s6_addr = arr.from_buffer_copy( - self._create_mask(plen)) + self.entry.ipv6.smsk.s6_addr = arr(*self._create_mask(plen)) return # nope, we got an IPv6 address-style prefix @@ -392,8 +391,7 @@ def set_dst(self, dst): plen = int(netm) if plen < 0 or plen > 128: raise ValueError("invalid prefix length %d" % (plen)) - self.entry.ipv6.dmsk.s6_addr = arr.from_buffer_copy( - self._create_mask(plen)) + self.entry.ipv6.dmsk.s6_addr = arr(*self._create_mask(plen)) return # nope, we got an IPv6 address-style prefix diff --git a/test.py b/test.py index 9397abe..c24c956 100755 --- a/test.py +++ b/test.py @@ -3,6 +3,11 @@ import sys +try: + input = raw_input +except NameError: + pass + print("WARNING: this test will manipulate iptables rules.") print("Don't do this on a production machine.") while True: From 5b37e0417fe1d01db2eb7b644eaf76ba4af7a97f Mon Sep 17 00:00:00 2001 From: Bastien Chatelard Date: Wed, 23 Jul 2014 15:58:48 +0200 Subject: [PATCH 073/287] Flush stdout to avoid getting buffered results. --- iptc/ip4tc.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index e1dd897..ca9e094 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -2,6 +2,7 @@ import os import re +import sys import ctypes as ct import socket import struct @@ -294,6 +295,8 @@ def _get_saved_buf(self, ip): # redirect C stdout to a pipe and read back the output of m->save + # Flush stdout to avoid getting buffered results + sys.stdout.flush() # Save the current C stdout. stdout = os.dup(1) try: From e233f6c804e56383847414031c5cf73f847a9f13 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Thu, 24 Jul 2014 18:12:00 +0200 Subject: [PATCH 074/287] Update .gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 807897e..051a899 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ /.project /MANIFEST /dist +/scripts From 20f46f670aa9971aba4531750c8b852bc9c0794d Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Thu, 24 Jul 2014 18:15:21 +0200 Subject: [PATCH 075/287] Document Rule. This fixes #77. --- doc/usage.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/usage.rst b/doc/usage.rst index 7534bcd..40c1584 100644 --- a/doc/usage.rst +++ b/doc/usage.rst @@ -76,6 +76,13 @@ Target :members: :inherited-members: +Rule +---- + +.. autoclass:: Rule + :members: + :inherited-members: + Rule6 ----- From 84397ccee913c286bd397dce77c4721a3eb076c7 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Thu, 24 Jul 2014 18:19:07 +0200 Subject: [PATCH 076/287] Release 0.4.0. --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index 5d776fd..d38e775 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.4.0-dev" +__version__ = "0.4.0" From fc1b4bf774fcd59419eb02fcb8a3c4fd204e8571 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Thu, 24 Jul 2014 18:28:59 +0200 Subject: [PATCH 077/287] Start 0.5.0-dev. --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index d38e775..6f91622 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.4.0" +__version__ = "0.5.0-dev" From 8f42142022855fd1593f7c28bebb22b2c78d0a80 Mon Sep 17 00:00:00 2001 From: Marek Majkowski Date: Mon, 28 Jul 2014 16:29:14 +0100 Subject: [PATCH 078/287] Fix #82 - don't run "strip" on a list --- iptc/ip4tc.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index ca9e094..8a9e33c 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -2,6 +2,7 @@ import os import re +import shlex import sys import ctypes as ct import socket @@ -263,20 +264,19 @@ def parse(self, parameter, value): raise AttributeError("%s: invalid parameter %s" % (self._module.name, parameter)) - parameter = parameter.rstrip().lstrip() - value = value.rstrip().lstrip() - if value.startswith("!"): + parameter = parameter.strip() + + inv = ct.c_int(0) + if value and value[0] == "!": inv = ct.c_int(1) value = value[1:] + if isinstance(value, str): + args = [value] else: - inv = ct.c_int(0) - - N = 1 - if isinstance(value, list): args = value - else: - args = [value] + N = len(args) + argv = (ct.c_char_p * (N + 1))() argv[0] = parameter for i in xrange(N): @@ -359,9 +359,11 @@ def get_all_parameters(self): ip = self.rule.get_ip() buf = self._get_saved_buf(ip) if buf is not None: - res = re.findall(IPTCModule.pattern, buf) - for x in res: - params[x[1]] = "%s%s" % ((x[0] or x[2]) and "!" or "", x[3]) + res = shlex.split(buf) + key, p = res[0], res[1:] + if len(p) == 1: + p = p[0] + params[key[2:]] = p return params def __setattr__(self, name, value): From 261d795c52f57136db875b39eb590a925cf35cf5 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 10 Aug 2014 11:11:08 +0200 Subject: [PATCH 079/287] Fix parameter value parsing. --- iptc/ip4tc.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 6f8f6dd..bb47832 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -262,8 +262,24 @@ def __init__(self): def parse(self, parameter, value): if isinstance(parameter, str): parameter = parameter.encode() + + # Check if we are dealing with an inverted parameter value. + inv = ct.c_int(0) + if len(value) > 0 and value[0] == "!": + inv = ct.c_int(1) + value = value[1:] + + # Value can be either a string, or a list of strings, e.g. "8888", + # "!0:65535" or ["!", "example_set", "dst"]. + args = [] if isinstance(value, str): - value = value.encode() + args = [value.encode()] + else: + try: + args = [val.encode() for val in value] + except: + raise TypeError("Invalid parameter value: " + "must be string or list of strings") if not self._module.extra_opts and not self._module.x6_options: raise AttributeError("%s: invalid parameter %s" % @@ -271,15 +287,6 @@ def parse(self, parameter, value): parameter = parameter.strip() - inv = ct.c_int(0) - if value and value[0] == b"!": - inv = ct.c_int(1) - value = value[1:] - if isinstance(value, str): - args = [value] - else: - args = value - N = len(args) argv = (ct.c_char_p * (N + 1))() From caef86dd06a5c43d5d72abd8c9d27c99e4f88cdf Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 10 Aug 2014 11:13:41 +0200 Subject: [PATCH 080/287] Parameter name is always a string. --- iptc/ip4tc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index bb47832..dc8db9f 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -260,8 +260,8 @@ def __init__(self): raise NotImplementedError() def parse(self, parameter, value): - if isinstance(parameter, str): - parameter = parameter.encode() + # Parameter name must always be a string. + parameter = parameter.encode() # Check if we are dealing with an inverted parameter value. inv = ct.c_int(0) From 8f7296d9d3ba2281d4355f277e54aa751584d06a Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 10 Aug 2014 11:25:56 +0200 Subject: [PATCH 081/287] Update setup.py. --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 2eb7ff5..5e02330 100644 --- a/setup.py +++ b/setup.py @@ -14,8 +14,7 @@ name=__pkgname__, version=__version__, description="Python bindings for iptables", - author="Nilvec", - author_email="nilvec@nilvec.com", + author="Vilmos Nebehaj", url="https://github.com/ldx/python-iptables", packages=["iptc"], package_dir={"iptc": "iptc"}, From b54bbb1ace4254ccecdc37056e6de4d74dd128ba Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Tue, 12 Aug 2014 09:56:43 +0200 Subject: [PATCH 082/287] Update documentation with autocommit info. --- README.md | 43 +++++++++++++++++++++++++++++++++++++++++++ doc/examples.rst | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/README.md b/README.md index 28a5fb6..692e8b2 100644 --- a/README.md +++ b/README.md @@ -439,3 +439,46 @@ counters: >>> for rule in chain.rules: >>> (packets, bytes) = rule.get_counters() >>> print packets, bytes + +Autocommit +---------- + +`Python-iptables` by default automatically performs an iptables commit +after each operation. That is, after you add a rule in +`python-iptables`, that will take effect immediately. + +It may happen that you want to batch together certain operations. A +typical use case is traversing a chain and removing rules matching a +specific criteria. If you do this with autocommit enabled, after the +first delete operation, your chain's state will chain and you have to +restart the traversal. You can do something like this: + + >>> import iptc + >>> table = iptc.Table(iptc.Table.FILTER) + >>> removed = True + >>> chain = iptc.Chain(table, "FORWARD") + >>> while removed == True: + >>> removed = False + >>> for rule in chain.rules: + >>> if rule.out_interface and "eth0" in rule.out_interface: + >>> chain.delete_rule(rule) + >>> removed = True + >>> break + +This is clearly not ideal and the code is not very readable. An +alternative is to disable autocommits, traverse the chain, removing one +or more rules, than commit it: + + >>> import iptc + >>> table = iptc.Table(iptc.Table.FILTER) + >>> table.autocommit = False + >>> chain = iptc.Chain(table, "FORWARD") + >>> for rule in chain.rules: + >>> if rule.out_interface and "eth0" in rule.out_interface: + >>> chain.delete_rule(rule) + >>> table.commit() + >>> table.autocommit = True + +The drawback is that Table is a singleton, and if you disable +autocommit, it will be disabled for all instances of that Table. + diff --git a/doc/examples.rst b/doc/examples.rst index 7c0c916..44ba460 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -340,3 +340,44 @@ your rules. You have to refresh your table to get update your counters:: >>> for rule in chain.rules: >>> (packets, bytes) = rule.get_counters() >>> print packets, bytes + +Autocommit +---------- +``Python-iptables`` by default automatically performs an iptables commit after +each operation. That is, after you add a rule in ``python-iptables``, that +will take effect immediately. + +It may happen that you want to batch together certain operations. A typical +use case is traversing a chain and removing rules matching a specific +criteria. If you do this with autocommit enabled, after the first delete +operation, your chain's state will chain and you have to restart the +traversal. You can do something like this:: + + >>> import iptc + >>> table = iptc.Table(iptc.Table.FILTER) + >>> removed = True + >>> chain = iptc.Chain(table, "FORWARD") + >>> while removed == True: + >>> removed = False + >>> for rule in chain.rules: + >>> if rule.out_interface and "eth0" in rule.out_interface: + >>> chain.delete_rule(rule) + >>> removed = True + >>> break + +This is clearly not ideal and the code is not very readable. An alternative is +to disable autocommits, traverse the chain, removing one or more rules, than +commit it:: + + >>> import iptc + >>> table = iptc.Table(iptc.Table.FILTER) + >>> table.autocommit = False + >>> chain = iptc.Chain(table, "FORWARD") + >>> for rule in chain.rules: + >>> if rule.out_interface and "eth0" in rule.out_interface: + >>> chain.delete_rule(rule) + >>> table.commit() + >>> table.autocommit = True + +The drawback is that `Table` is a singleton, and if you disable autocommit, it +will be disabled for all instances of that `Table`. From e7458c3269e316d7eef07d9e3a8868d833365689 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Wed, 13 Aug 2014 09:49:29 +0200 Subject: [PATCH 083/287] Use python 3.3 and 3.4 on Travis CCI. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 67d1392..0059e6f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,8 @@ language: python python: - "2.6" - "2.7" + - "3.3" + - "3.4" install: - python setup.py build - python setup.py install From bfb75ecc5ce2820b14e0fd0eff1e14bb3c190b8a Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Wed, 13 Aug 2014 09:52:14 +0200 Subject: [PATCH 084/287] Use only python 3.4. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0059e6f..c3f9c0e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: python python: - "2.6" - "2.7" - - "3.3" - "3.4" install: - python setup.py build From 88ee374a094d7bbb0c74896c742481c4641ba9cd Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Wed, 13 Aug 2014 10:00:37 +0200 Subject: [PATCH 085/287] Add note about Python compatibility to docs. --- README.md | 2 ++ doc/intro.rst | 2 ++ 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index 692e8b2..761e1e0 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,8 @@ output. It is meant primarily for dynamic and/or complex routers and firewalls, where rules are often updated or changed, or Python programs wish to interface with the Linux iptables framework.. +`Python-iptables` supports Python 2.6, 2.7 and 3.4. + ![buildstatus](https://travis-ci.org/ldx/python-iptables.png?branch=master) ![Bitdeli](https://d2weczhvl823v0.cloudfront.net/ldx/python-iptables/trend.png) diff --git a/doc/intro.rst b/doc/intro.rst index 89784af..10cbbce 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -27,6 +27,8 @@ meant primarily for dynamic and/or complex routers and firewalls, where rules are often updated or changed, or Python programs wish to interface with the Linux iptables framework.. +``Python-iptables`` supports Python 2.6, 2.7 and 3.4. + |buildstatus| .. |buildstatus| image:: https://travis-ci.org/ldx/python-iptables.png?branch=master From 54271134f1fb8598bb128168dc342d9e10433316 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Thu, 28 Aug 2014 15:45:34 +0200 Subject: [PATCH 086/287] Remove bitdeli badge. --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 761e1e0..9c330d8 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,6 @@ wish to interface with the Linux iptables framework.. ![buildstatus](https://travis-ci.org/ldx/python-iptables.png?branch=master) -![Bitdeli](https://d2weczhvl823v0.cloudfront.net/ldx/python-iptables/trend.png) - Installing via pip ------------------ From 2336059f28d04aca7589c74d34f2cee10c9e1259 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Thu, 28 Aug 2014 16:35:31 +0200 Subject: [PATCH 087/287] Add doc/Makefile target for updating README.md. --- doc/Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/Makefile b/doc/Makefile index 9ead21b..bd87a6b 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -99,3 +99,6 @@ gh-pages: git reset --hard git clean -f .. git checkout master + +markdown: intro.rst examples.rst + pandoc -o ../README.md -f rst -t markdown intro.rst examples.rst From 1c11c023f66d112ed9e638dc365c39ffa7f5398f Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 31 Aug 2014 15:05:18 +0200 Subject: [PATCH 088/287] Add Flattr button. --- README.md | 2 ++ doc/intro.rst | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9c330d8..79ee94f 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ wish to interface with the Linux iptables framework.. ![buildstatus](https://travis-ci.org/ldx/python-iptables.png?branch=master) +[![Flattr](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=ldx&url=https%3A%2F%2Fgithub.com%2Fldx%2Fpython-iptables) + Installing via pip ------------------ diff --git a/doc/intro.rst b/doc/intro.rst index 10cbbce..c7c4349 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -33,9 +33,10 @@ Linux iptables framework.. .. |buildstatus| image:: https://travis-ci.org/ldx/python-iptables.png?branch=master -|Bitdeli| +|Flattr| -.. |Bitdeli| image:: https://d2weczhvl823v0.cloudfront.net/ldx/python-iptables/trend.png +.. |Flattr| image:: http://api.flattr.com/button/flattr-badge-large.png + :target: https://flattr.com/submit/auto?user_id=ldx&url=https%3A%2F%2Fgithub.com%2Fldx%2Fpython-iptables Installing via pip ------------------ From ae1f7e4270528645301c7d79e281ac0a7fccaf69 Mon Sep 17 00:00:00 2001 From: Federico Ceratto Date: Thu, 4 Sep 2014 13:00:29 +0000 Subject: [PATCH 089/287] Add supported Python versions and interpreter --- setup.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/setup.py b/setup.py index 5e02330..1964c69 100644 --- a/setup.py +++ b/setup.py @@ -34,6 +34,12 @@ "Topic :: Software Development :: Libraries", "Topic :: System :: Networking :: Firewalls", "Topic :: System :: Systems Administration", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: Implementation :: CPython", ], license="Apache License, Version 2.0", ) From 325f2ed9b5ac4a4785da15c04e654df8ea299244 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 20 Sep 2014 23:35:38 +0200 Subject: [PATCH 090/287] Fix #91. Make sure ip6t_ip6.IP6T_F_PROTO is updated in Rule6.entry.ipv6.flags when the Rule6.protocol is changed. --- iptc/ip6tc.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/iptc/ip6tc.py b/iptc/ip6tc.py index 96535d6..0535b9e 100644 --- a/iptc/ip6tc.py +++ b/iptc/ip6tc.py @@ -507,10 +507,13 @@ def get_protocol(self): def set_protocol(self, proto): if proto[0] == "!": self.entry.ipv6.invflags |= ip6t_ip6.IP6T_INV_PROTO + self.entry.ipv6.flags &= (~ip6t_ip6.IP6T_F_PROTO & + ip6t_ip6.IP6T_F_MASK) proto = proto[1:] else: self.entry.ipv6.invflags &= (~ip6t_ip6.IP6T_INV_PROTO & ip6t_ip6.IP6T_INV_MASK) + self.entry.ipv6.flags |= ip6t_ip6.IP6T_F_PROTO for p in self.protocols.items(): if proto.lower() == p[1]: self.entry.ipv6.proto = p[0] From 248287ab9bb6ca4ea98bc84ab451309b8aba4222 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 20 Sep 2014 23:39:43 +0200 Subject: [PATCH 091/287] Fix empty buffer case in get_all_parameters(). Return empty dict if the iptables buffer is empty in get_all_parameters(). --- iptc/ip4tc.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index dc8db9f..1fdd40a 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -372,6 +372,8 @@ def get_all_parameters(self): buf = self._get_saved_buf(ip) if buf is not None: res = shlex.split(buf) + if len(res) == 0: + return params key, p = res[0], res[1:] if len(p) == 1: p = p[0] From 4a68eb32792d4887ef869fd80455606086dfa88b Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 21 Sep 2014 13:10:18 +0200 Subject: [PATCH 092/287] Fix parsing of multiple parameters from save(). --- iptc/ip4tc.py | 19 +++++++++++-------- iptc/test/test_iptc.py | 25 +++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 1fdd40a..42d63f9 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -370,14 +370,17 @@ def get_all_parameters(self): params = {} ip = self.rule.get_ip() buf = self._get_saved_buf(ip) - if buf is not None: - res = shlex.split(buf) - if len(res) == 0: - return params - key, p = res[0], res[1:] - if len(p) == 1: - p = p[0] - params[key[2:]] = p + if buf is None: + return params + res = shlex.split(buf) + res.reverse() + while len(res) > 0: + x = res.pop() + if x.startswith('--'): # This is a parameter name. + key = x[2:] + params[key] = [] + continue + params[key].append(x) # This is a parameter value. return params def __setattr__(self, name, value): diff --git a/iptc/test/test_iptc.py b/iptc/test/test_iptc.py index c2e32ba..f6bc6d7 100755 --- a/iptc/test/test_iptc.py +++ b/iptc/test/test_iptc.py @@ -770,6 +770,31 @@ def test_rule_insert(self): self.failUnless(rule in crules) crules.remove(rule) + def test_rule_multiple_parameters(self): + self.table.autocommit = False + self.table.refresh() + rule = iptc.Rule() + rule.dst = "127.0.0.1" + rule.protocol = "tcp" + match = rule.create_match('tcp') + match.sport = "1234" + match.dport = "8080" + target = rule.create_target("REJECT") + target.reject_with = "icmp-host-unreachable" + self.chain.insert_rule(rule) + self.table.commit() + self.table.refresh() + self.assertEquals(len(self.chain.rules), 1) + r = self.chain.rules[0] + self.assertEquals(r.src, '0.0.0.0/0.0.0.0') + self.assertEquals(r.dst, '127.0.0.1/255.255.255.255') + self.assertEquals(r.protocol, 'tcp') + self.assertEquals(len(r.matches), 1) + m = r.matches[0] + self.assertEquals(m.name, 'tcp') + self.assertEquals(m.sport, '1234') + self.assertEquals(m.dport, '8080') + def test_rule_delete(self): self.table.autocommit = False self.table.refresh() From f31e0760a31fcac0bb656d605c5e63e40d5c72f9 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 21 Sep 2014 13:11:56 +0200 Subject: [PATCH 093/287] Whitespace fix. --- iptc/test/test_iptc.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/iptc/test/test_iptc.py b/iptc/test/test_iptc.py index c2e32ba..105ef67 100755 --- a/iptc/test/test_iptc.py +++ b/iptc/test/test_iptc.py @@ -144,7 +144,7 @@ def test_flush_builtin(self): iptc.Chain(filter_table, "OUTPUT").append_rule(rule) self.assertEquals(len(iptc.Chain(filter_table, "OUTPUT").rules), output_rule_count + 1) - + filter_table.flush() self.assertEquals(len(iptc.Chain(filter_table, "OUTPUT").rules), 0) @@ -580,10 +580,10 @@ def tearDown(self): def test_rule_address(self): # valid addresses rule = iptc.Rule() - for addr in [("127.0.0.1/255.255.255.0", "127.0.0.1/255.255.255.0"), - ("!127.0.0.1/255.255.255.0", "!127.0.0.1/255.255.255.0"), + for addr in [("127.0.0.1/255.255.255.0", "127.0.0.1/255.255.255.0"), + ("!127.0.0.1/255.255.255.0", "!127.0.0.1/255.255.255.0"), ("127.0.0.1/255.255.128.0", "127.0.0.1/255.255.128.0"), - ("127.0.0.1/16", "127.0.0.1/255.255.0.0"), + ("127.0.0.1/16", "127.0.0.1/255.255.0.0"), ("127.0.0.1/24", "127.0.0.1/255.255.255.0"), ("127.0.0.1/17", "127.0.0.1/255.255.128.0"), ("!127.0.0.1/17", "!127.0.0.1/255.255.128.0")]: From 84406703bbbe24658a8328310b408f055ff61b2b Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 28 Sep 2014 13:15:03 +0200 Subject: [PATCH 094/287] Fix a typo in doc/examples.rst. This fixes #97. --- README.md | 2 +- doc/examples.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 79ee94f..cf32d73 100644 --- a/README.md +++ b/README.md @@ -359,7 +359,7 @@ matches: >>> match.dst_range = "172.22.33.106" >>> rule.add_match(match) >>> rule.target = iptc.Target(rule, "DROP") - >>> chain = iptc.Chain(iptc.Table.(iptc.Table.FILTER), "INPUT") + >>> chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), "INPUT") >>> chain.insert_rule(rule) This is the `python-iptables` equivalent of the following iptables diff --git a/doc/examples.rst b/doc/examples.rst index 44ba460..ba40725 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -261,7 +261,7 @@ matches:: >>> match.dst_range = "172.22.33.106" >>> rule.add_match(match) >>> rule.target = iptc.Target(rule, "DROP") - >>> chain = iptc.Chain(iptc.Table.(iptc.Table.FILTER), "INPUT") + >>> chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), "INPUT") >>> chain.insert_rule(rule) This is the ``python-iptables`` equivalent of the following iptables command:: From 5d06300c2d825da5d71e97fe3ac082fcc01ed6ce Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Mon, 6 Oct 2014 12:06:08 +0200 Subject: [PATCH 095/287] Allocate udata for extensions. This fixes #98. --- iptc/ip4tc.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 42d63f9..dc065da 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -571,6 +571,9 @@ def reset(self): if self._module.init: self._module.init(self._ptr) self._module.mflags = 0 + if self._module.udata_size > 0: + udata_buf = (ct.c_ubyte * self._module.udata_size)() + self._module.udata = ct.cast(ct.byref(udata_buf), ct.c_void_p) def _get_match(self): return ct.cast(ct.byref(self.match_buf), ct.POINTER(xt_entry_match))[0] @@ -759,6 +762,9 @@ def reset(self): if self._module.init: self._module.init(self._ptr) self._module.tflags = 0 + if self._module.udata_size > 0: + udata_buf = (ct.c_ubyte * self._module.udata_size)() + self._module.udata = ct.cast(ct.byref(udata_buf), ct.c_void_p) def _get_target(self): return self._ptr[0] From 77e0ce7db7934ce1c5bc6e38864f159425a9c854 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Mon, 6 Oct 2014 12:18:20 +0200 Subject: [PATCH 096/287] Add hashlimit match test. --- iptc/test/test_matches.py | 45 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/iptc/test/test_matches.py b/iptc/test/test_matches.py index 5fa7f40..067adf2 100755 --- a/iptc/test/test_matches.py +++ b/iptc/test/test_matches.py @@ -365,6 +365,46 @@ def test_state(self): self.assertEquals(m.ctstate, "NEW,RELATED") +class TestHashlimitMatch(unittest.TestCase): + def setUp(self): + self.rule = iptc.Rule() + self.rule.src = "127.0.0.1" + self.rule.protocol = "udp" + self.rule.target = iptc.Target(self.rule, "ACCEPT") + + self.match = iptc.Match(self.rule, "hashlimit") + + self.chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), + "iptc_test_hashlimit") + self.table = iptc.Table(iptc.Table.FILTER) + try: + self.chain.flush() + self.chain.delete() + except: + pass + self.table.create_chain(self.chain) + + def tearDown(self): + self.chain.flush() + self.chain.delete() + + def test_hashlimit(self): + self.match.hashlimit_name = 'foo' + self.match.hashlimit_mode = 'srcip' + self.match.hashlimit_upto = '200/sec' + self.match.hashlimit_burst = '5' + self.match.hashlimit_htable_expire = '1000' + self.rule.add_match(self.match) + self.chain.insert_rule(self.rule) + rule = self.chain.rules[0] + m = rule.matches[0] + self.assertTrue(m.name, ["hashlimit"]) + self.assertEquals(m.hashlimit_name, "foo") + self.assertEquals(m.hashlimit_mode, "srcip") + self.assertEquals(m.hashlimit_upto, "200/sec") + self.assertEquals(m.hashlimit_burst, "5") + + def suite(): suite_match = unittest.TestLoader().loadTestsFromTestCase(TestMatch) suite_udp = unittest.TestLoader().loadTestsFromTestCase(TestXTUdpMatch) @@ -377,6 +417,8 @@ def suite(): suite_state = unittest.TestLoader().loadTestsFromTestCase(TestXTStateMatch) suite_conntrack = unittest.TestLoader().loadTestsFromTestCase( TestXTConntrackMatch) + suite_hashlimit = unittest.TestLoader().loadTestsFromTestCase( + TestHashlimitMatch) extra_suites = [] if is_table6_available(iptc.Table6.FILTER): extra_suites += unittest.TestLoader().loadTestsFromTestCase( @@ -384,7 +426,8 @@ def suite(): return unittest.TestSuite([suite_match, suite_udp, suite_mark, suite_limit, suite_comment, suite_iprange, - suite_state, suite_conntrack] + extra_suites) + suite_state, suite_conntrack, + suite_hashlimit] + extra_suites) def run_tests(): From d4fc390f9d3fcc8d16db8fea7d5152936f6898be Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Wed, 8 Oct 2014 11:43:15 +0200 Subject: [PATCH 097/287] Update docs with ipset example. --- README.md | 22 ++++++++++++++++------ doc/examples.rst | 24 ++++++++++++++++++------ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index cf32d73..1c831a8 100644 --- a/README.md +++ b/README.md @@ -191,8 +191,8 @@ will work. However, if you change the state parameter: argv[1])) iptc.xtables.XTablesError: state: parameter error -2 (RELATED,ESTABLISHED,FOOBAR) -In certain cases you might need to use quoting inside the parameter -string, for example: +Certain parameters take a string that optionally consists of multiple +words. The comment match is a good example: >>> rule = iptc.Rule() >>> rule.src = "127.0.0.1" @@ -203,11 +203,21 @@ string, for example: >>> chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), "INPUT") >>> chain.insert_rule(rule) -will only add the comment this instead of the expected this is a test -comment. Use quoting inside the comment string itself: +Note that this is still just one parameter value. - >>> comment = "this is a test comment" - >>> match.comment = "\"%s\"" % (comment) +However, when a match or a target takes multiple parameter values, that +needs to be passed in as a list. Let's assume you have created and set +up an `ipset` called `blacklist` via the `ipset` command. To create a +rule with a match for this set: + + >>> rule = iptc.Rule() + >>> m = rule.create_match("set") + >>> m.match_set = ['blacklist', 'src'] + +Note how this time a list was used for the parameter value, since the +`set` match `match_set` parameter expects two values. See the `iptables` +manpages to find out what the extensions you use expect. See +[ipset](http://ipset.netfilter.org/) for more information. When you are ready constructing your rule, add them to the chain you want it to show up in: diff --git a/doc/examples.rst b/doc/examples.rst index ba40725..576b425 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -93,8 +93,8 @@ will work. However, if you change the `state` parameter:: argv[1])) iptc.xtables.XTablesError: state: parameter error -2 (RELATED,ESTABLISHED,FOOBAR) -In certain cases you might need to use quoting inside the parameter string, for -example:: +Certain parameters take a string that optionally consists of multiple words. +The comment match is a good example:: >>> rule = iptc.Rule() >>> rule.src = "127.0.0.1" @@ -105,11 +105,23 @@ example:: >>> chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), "INPUT") >>> chain.insert_rule(rule) -will only add the comment `this` instead of the expected `this is a test -comment`. Use quoting inside the comment string itself:: +Note that this is still just one parameter value. + +However, when a match or a target takes multiple parameter values, that needs +to be passed in as a list. Let's assume you have created and set up an +``ipset`` called ``blacklist`` via the ``ipset`` command. To create a rule +with a match for this set:: + + >>> rule = iptc.Rule() + >>> m = rule.create_match("set") + >>> m.match_set = ['blacklist', 'src'] + +Note how this time a list was used for the parameter value, since the ``set`` +match ``match_set`` parameter expects two values. See the ``iptables`` +manpages to find out what the extensions you use expect. See ipset_ for more +information. - >>> comment = "this is a test comment" - >>> match.comment = "\"%s\"" % (comment) +.. _ipset: http://ipset.netfilter.org/ When you are ready constructing your rule, add them to the chain you want it to show up in:: From 04597db472be4f445e8cffd3a453aa94c1e6200c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Wed, 1 Oct 2014 11:41:53 +0200 Subject: [PATCH 098/287] debian/ - update --- debian/changelog | 9 +++++++++ debian/control | 29 +++++++++++++++++++++++++--- debian/rules | 50 +++++++++++++++++++++++++++++++++++++----------- 3 files changed, 74 insertions(+), 14 deletions(-) diff --git a/debian/changelog b/debian/changelog index 9a73782..0d7c90f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +python-iptables (0.5-git-20140925) precise; urgency=low + + * update debian/ + remove cdbs + build python3 packages + build debug packages + + -- Markus Kötter Thu, 25 Sep 2014 10:48:41 +0200 + python-iptables (0.1.1) unstable; urgency=low * Initial Release. diff --git a/debian/control b/debian/control index b3582ab..468f1a5 100644 --- a/debian/control +++ b/debian/control @@ -2,17 +2,40 @@ Source: python-iptables Section: net Priority: extra Maintainer: Juliano Martinez -Build-Depends: cdbs (>= 0.4.49), debhelper (>= 7), python (>= 2.4), python-support, python-all-dev (>= 2.3.5-11) -XS-Python-Version: >= 2.6 +Build-depends: python-all-dev (>= 2.7), python-all-dbg (>= 2.7), python3-all-dev (>= 3.2), python3-all-dbg, debhelper (>= 7), python-support (>= 0.90) +X-Python-Version: >= 2.7 +X-Python3-Version: >= 3.2 Standards-Version: 3.8.4 Homepage: https://github.com/ldx/python-iptables Package: python-iptables Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends} +Depends: ${python:Depends}, ${shlibs:Depends}, ${misc:Depends} +Provides: ${python:Provides} Description: Python bindings for iptables Python-iptables is a Python project that provides bindings to the iptables C libraries in Linux. Interoperability with iptables is achieved using the iptables C libraries (libiptc, libxtables, and iptables extensions), not calling the iptables executable and parsing its output as most other iptables wrapper libraries do; this makes python-iptables faster and not prone to parsing errors, at the same time leveraging all available iptables match and target extensions without further work. + +Package: python-iptables-dbg +Section: debug +Priority: extra +Architecture: any +Depends: ${python:Depends}, ${shlibs:Depends}, ${misc:Depends}, python-iptables (= ${binary:Version}) +Provides: ${python:Provides} +Description: Python bindings for iptables + +Package: python3-iptables +Architecture: any +Depends: ${python3:Depends}, ${shlibs:Depends}, ${misc:Depends} +Description: Python3 bindings for iptables + +Package: python3-iptables-dbg +Section: debug +Priority: extra +Architecture: any +Depends: ${python3:Depends}, ${shlibs:Depends}, ${misc:Depends}, python3-iptables (= ${binary:Version}) +Description: Python3 bindings for iptables + diff --git a/debian/rules b/debian/rules index 4cd3ea9..b8ad1a9 100755 --- a/debian/rules +++ b/debian/rules @@ -1,15 +1,43 @@ #!/usr/bin/make -f -# -*- makefile -*- -# Sample debian/rules that uses debhelper. -# This file was originally written by Joey Hess and Craig Small. -# As a special exception, when this file is copied by dh-make into a -# dh-make output file, you may use that output file without restriction. -# This special exception was added by Craig Small in version 0.37 of dh-make. -# Uncomment this to turn on verbose mode. -export DH_VERBOSE=1 +# This file was automatically generated by stdeb 0.6.0+git at +# Thu, 14 Nov 2013 16:04:50 +0100 -DEB_PYTHON_SYSTEM=pysupport +PY3VERS := $(shell py3versions -r) +PY2VERS := $(shell pyversions -r) + +%: + dh $@ --with python2,python3 --buildsystem=python_distutils + +.PHONY: override_dh_clean +override_dh_clean: + rm -rf build/* + dh_clean + +.PHONY: override_dh_auto_install +override_dh_auto_install: + set -e; \ + for py in $(PY3VERS); do \ + $$py -B setup.py install --root debian/python3-iptables --install-layout deb; \ + $$py-dbg -B setup.py install --root debian/python3-iptables-dbg --install-layout deb; \ + done + set -e; \ + for py in $(PY2VERS); do \ + $$py -B setup.py install --root debian/python-iptables --install-layout deb; \ + $$py-dbg -B setup.py install --root debian/python-iptables-dbg --install-layout deb; \ + done + +.PHONY: override_dh_strip +override_dh_strip: + set -e; \ + dh_strip -ppython-ev --dbg-package=python-iptables-dbg; \ + dh_strip -ppython3-ev --dbg-package=python3-iptables-dbg; + +override_dh_python2: + dh_python2 -ppython-iptables + dh_python2 -ppython-iptables-dbg + +override_dh_python3: + dh_python3 -ppython3-iptables + dh_python3 -ppython3-iptables-dbg -include /usr/share/cdbs/1/rules/debhelper.mk -include /usr/share/cdbs/1/class/python-distutils.mk From 7c20dd3ff489d3b80878590808d5576dd6ac8d89 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 12 Oct 2014 21:40:34 +0200 Subject: [PATCH 099/287] Update docs. --- README.md | 6 ++++++ doc/intro.rst | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/README.md b/README.md index 1c831a8..fd1d322 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,12 @@ wish to interface with the Linux iptables framework.. ![buildstatus](https://travis-ci.org/ldx/python-iptables.png?branch=master) +![latest\_release](https://pypip.in/v/python-iptables/badge.png) + +![number\_of\_downloads](https://pypip.in/d/python-iptables/badge.png) + +![license](https://pypip.in/license/python-iptables/badge.png) + [![Flattr](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=ldx&url=https%3A%2F%2Fgithub.com%2Fldx%2Fpython-iptables) Installing via pip diff --git a/doc/intro.rst b/doc/intro.rst index c7c4349..60c95e4 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -33,6 +33,18 @@ Linux iptables framework.. .. |buildstatus| image:: https://travis-ci.org/ldx/python-iptables.png?branch=master +|latest_release| + +.. |latest_release| image:: https://pypip.in/v/python-iptables/badge.png + +|number_of_downloads| + +.. |number_of_downloads| image:: https://pypip.in/d/python-iptables/badge.png + +|license| + +.. |license| image:: https://pypip.in/license/python-iptables/badge.png + |Flattr| .. |Flattr| image:: http://api.flattr.com/button/flattr-badge-large.png From 6a77b20b3a2b2e654dbc9a99cc33c822d2aeae01 Mon Sep 17 00:00:00 2001 From: Michael Weber Date: Mon, 13 Oct 2014 09:01:45 +0200 Subject: [PATCH 100/287] Fix library lookup for python3 < 3.4, https://github.com/ldx/python-iptables/issues/104 --- iptc/util.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/iptc/util.py b/iptc/util.py index ec1f505..d2750f3 100644 --- a/iptc/util.py +++ b/iptc/util.py @@ -2,6 +2,7 @@ import ctypes import ctypes.util from subprocess import Popen, PIPE +from sys import version_info try: from sysconfig import get_config_var except ImportError: @@ -66,6 +67,8 @@ def _do_find_library(name): def _find_library(*names): ext = get_config_var('SO') + if version_info > (3, ) and version_info < (3, 4): + ext = '.cpython-%i%i' % (version_info.major, version_info.minor) + ext for name in names: for n in (name, "lib" + name, name + ext, "lib" + name + ext): lib = _do_find_library(n) From de5d27f14879961a5358b2e8b416af5b56d55a8c Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Mon, 13 Oct 2014 10:38:58 +0200 Subject: [PATCH 101/287] Fix get_all_parameters(). Take negated parameters into account when parsing parameters. This fixes #103. --- iptc/ip4tc.py | 11 ++++++++++- iptc/test/test_matches.py | 9 +++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index dc065da..9c35a76 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -374,11 +374,20 @@ def get_all_parameters(self): return params res = shlex.split(buf) res.reverse() + inv = False while len(res) > 0: x = res.pop() + if x == '!': + # Next parameter is negated. + inv = True + continue if x.startswith('--'): # This is a parameter name. key = x[2:] - params[key] = [] + if inv: + params[key] = ['!'] + else: + params[key] = [] + inv = False continue params[key].append(x) # This is a parameter value. return params diff --git a/iptc/test/test_matches.py b/iptc/test/test_matches.py index 067adf2..8a9897d 100755 --- a/iptc/test/test_matches.py +++ b/iptc/test/test_matches.py @@ -63,6 +63,15 @@ def test_match_parameters(self): m.reset() self.failUnless(len(m.parameters) == 0) + def test_get_all_parameters(self): + m = iptc.Match(iptc.Rule(), "udp") + m.sport = "12345:55555" + m.dport = "!33333" + + params = m.get_all_parameters() + self.assertEquals(set(params['sport']), set(['12345:55555'])) + self.assertEquals(set(params['dport']), set(['!', '33333'])) + class TestXTUdpMatch(unittest.TestCase): def setUp(self): From 237e6612ca880f34c413e495a049e15edbfba626 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Mon, 13 Oct 2014 14:15:46 +0200 Subject: [PATCH 102/287] Convert bytes to string in get_all_parameters(). --- iptc/ip4tc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 9c35a76..a266fbe 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -372,7 +372,7 @@ def get_all_parameters(self): buf = self._get_saved_buf(ip) if buf is None: return params - res = shlex.split(buf) + res = shlex.split(buf.decode()) res.reverse() inv = False while len(res) > 0: From 0ea199314abeb336945d3648e915530bff6d3d40 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Thu, 16 Oct 2014 00:48:18 +0200 Subject: [PATCH 103/287] Fix get_all_parameters() in Python 2.6. Shlex does not support unicode string input. Encode input and decode output when using it. --- iptc/ip4tc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index a266fbe..dcf5871 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -372,7 +372,7 @@ def get_all_parameters(self): buf = self._get_saved_buf(ip) if buf is None: return params - res = shlex.split(buf.decode()) + res = [s.decode() for s in shlex.split(buf.encode())] res.reverse() inv = False while len(res) > 0: From cac8903a1fa98ef22fadffd217bd21beaf1dd1bb Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Thu, 16 Oct 2014 11:47:19 +0200 Subject: [PATCH 104/287] Convert bytes to string in get_all_parameters(). --- iptc/ip4tc.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index dcf5871..f975c2c 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -372,7 +372,10 @@ def get_all_parameters(self): buf = self._get_saved_buf(ip) if buf is None: return params - res = [s.decode() for s in shlex.split(buf.encode())] + if type(buf) != str: + # In Python3, string and bytes are different types. + buf = buf.decode() + res = shlex.split(buf) res.reverse() inv = False while len(res) > 0: From 5b273e360f372b2450381734f3869a3e10526bca Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Thu, 16 Oct 2014 11:53:05 +0200 Subject: [PATCH 105/287] Release 0.5.0. --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index 6f91622..e7b8bc5 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.5.0-dev" +__version__ = "0.5.0" From 3b794e620f041b514547dd00b03ca2752aacc060 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Thu, 16 Oct 2014 12:06:22 +0200 Subject: [PATCH 106/287] Add author_email to setup.py. --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 1964c69..ae3a46c 100644 --- a/setup.py +++ b/setup.py @@ -15,6 +15,7 @@ version=__version__, description="Python bindings for iptables", author="Vilmos Nebehaj", + author_email="v.nebehaj@gmail.com", url="https://github.com/ldx/python-iptables", packages=["iptc"], package_dir={"iptc": "iptc"}, From b7f1fb7e988d7f267909d7403bdee426ed2a2937 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Thu, 16 Oct 2014 12:12:29 +0200 Subject: [PATCH 107/287] Bump version to 0.6.0-dev. --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index e7b8bc5..a907981 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.5.0" +__version__ = "0.6.0-dev" From 7b63f9fa07c1b15494feedf32a05cafbfb0c0b52 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 19 Oct 2014 18:21:56 +0200 Subject: [PATCH 108/287] Update badges in docs. --- README.md | 10 +++++----- doc/intro.rst | 31 +++++++++++++++---------------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index fd1d322..0bb1a24 100644 --- a/README.md +++ b/README.md @@ -29,15 +29,15 @@ wish to interface with the Linux iptables framework.. `Python-iptables` supports Python 2.6, 2.7 and 3.4. -![buildstatus](https://travis-ci.org/ldx/python-iptables.png?branch=master) +[![Build Status](https://travis-ci.org/ldx/python-iptables.png?branch=master)](https://travis-ci.org/ldx/python-iptables) -![latest\_release](https://pypip.in/v/python-iptables/badge.png) +[![Flattr](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=ldx&url=https%3A%2F%2Fgithub.com%2Fldx%2Fpython-iptables) -![number\_of\_downloads](https://pypip.in/d/python-iptables/badge.png) +[![Latest Release](https://pypip.in/v/python-iptables/badge.png)](https://pypi.python.org/pypi/python-iptables) -![license](https://pypip.in/license/python-iptables/badge.png) +[![Number of Downloads](https://pypip.in/d/python-iptables/badge.png)](https://pypi.python.org/pypi/python-iptables) -[![Flattr](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=ldx&url=https%3A%2F%2Fgithub.com%2Fldx%2Fpython-iptables) +[![License](https://pypip.in/license/python-iptables/badge.png)](https://pypi.python.org/pypi/python-iptables) Installing via pip ------------------ diff --git a/doc/intro.rst b/doc/intro.rst index 60c95e4..4e10442 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -29,26 +29,25 @@ Linux iptables framework.. ``Python-iptables`` supports Python 2.6, 2.7 and 3.4. -|buildstatus| +.. image:: https://travis-ci.org/ldx/python-iptables.png?branch=master + :target: https://travis-ci.org/ldx/python-iptables + :alt: Build Status -.. |buildstatus| image:: https://travis-ci.org/ldx/python-iptables.png?branch=master - -|latest_release| - -.. |latest_release| image:: https://pypip.in/v/python-iptables/badge.png - -|number_of_downloads| - -.. |number_of_downloads| image:: https://pypip.in/d/python-iptables/badge.png - -|license| +.. image:: http://api.flattr.com/button/flattr-badge-large.png + :target: https://flattr.com/submit/auto?user_id=ldx&url=https%3A%2F%2Fgithub.com%2Fldx%2Fpython-iptables + :alt: Flattr -.. |license| image:: https://pypip.in/license/python-iptables/badge.png +.. image:: https://pypip.in/v/python-iptables/badge.png + :target: https://pypi.python.org/pypi/python-iptables + :alt: Latest Release -|Flattr| +.. image:: https://pypip.in/d/python-iptables/badge.png + :target: https://pypi.python.org/pypi/python-iptables + :alt: Number of Downloads -.. |Flattr| image:: http://api.flattr.com/button/flattr-badge-large.png - :target: https://flattr.com/submit/auto?user_id=ldx&url=https%3A%2F%2Fgithub.com%2Fldx%2Fpython-iptables +.. image:: https://pypip.in/license/python-iptables/badge.png + :target: https://pypi.python.org/pypi/python-iptables + :alt: License Installing via pip ------------------ From 034580be566315bd507d756aad41dae22f5250ae Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Wed, 29 Oct 2014 00:24:02 +0100 Subject: [PATCH 109/287] Fix #109. Check if udata_size is present in _module before resetting the udata buffer. --- iptc/ip4tc.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index f975c2c..226b917 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -583,8 +583,9 @@ def reset(self): if self._module.init: self._module.init(self._ptr) self._module.mflags = 0 - if self._module.udata_size > 0: - udata_buf = (ct.c_ubyte * self._module.udata_size)() + udata_size = getattr(self._module, 'udata_size', 0) + if udata_size > 0: + udata_buf = (ct.c_ubyte * udata_size)() self._module.udata = ct.cast(ct.byref(udata_buf), ct.c_void_p) def _get_match(self): @@ -774,8 +775,9 @@ def reset(self): if self._module.init: self._module.init(self._ptr) self._module.tflags = 0 - if self._module.udata_size > 0: - udata_buf = (ct.c_ubyte * self._module.udata_size)() + udata_size = getattr(self._module, 'udata_size', 0) + if udata_size > 0: + udata_buf = (ct.c_ubyte * udata_size)() self._module.udata = ct.cast(ct.byref(udata_buf), ct.c_void_p) def _get_target(self): From 41df51f0b3a01cfb1bba76273898f94502bfdfb2 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Wed, 29 Oct 2014 01:05:51 +0100 Subject: [PATCH 110/287] Fix #111. When comparing targets, check if the target is a standard target. --- iptc/ip4tc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index f975c2c..e8e2b20 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -663,7 +663,8 @@ def __eq__(self, targ): self.target.u.user.name == b"ACCEPT" or self.target.u.user.name == b"DROP" or self.target.u.user.name == b"RETURN" or - self.target.u.user.name == b"ERROR"): + self.target.u.user.name == b"ERROR" or + self._is_standard_target()): return True if (self._target_buf[basesz:self.usersize] == targ._target_buf[basesz:targ.usersize]): From ecffa76f0f4230513da82c71a3b97e826b40254a Mon Sep 17 00:00:00 2001 From: loli10K Date: Thu, 30 Oct 2014 21:58:22 +0100 Subject: [PATCH 111/287] Implement chain.replace_rule() --- iptc/ip4tc.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 61f19ea..ccf86fb 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -1375,6 +1375,13 @@ def insert_rule(self, rule, position=0): raise ValueError("invalid rule") self.table.insert_entry(self.name, rbuf, position) + def replace_rule(self, rule, position=0): + """Replace existing rule in the chain at *position* with given *rule*""" + rbuf = rule.rule + if not rbuf: + raise ValueError("invalid rule") + self.table.replace_entry(self.name, rbuf, position) + def delete_rule(self, rule): """Removes *rule* from the chain.""" rbuf = rule.rule @@ -1638,6 +1645,15 @@ def insert_entry(self, chain, entry, position): raise IPTCError("can't insert entry into chain %s: %s)" % (chain, self.strerror())) + @autocommit + def replace_entry(self, chain, entry, position): + """Replace existing rule in *chain* at *position* with given *rule*.""" + rv = self._iptc.iptc_replace_entry(chain.encode(), ct.cast(entry, ct.c_void_p), + position, self._handle) + if rv != 1: + raise IPTCError("can't replace entry in chain %s: %s)" % + (chain, self.strerror())) + @autocommit def delete_entry(self, chain, entry, mask): """Removes rule *entry* with *mask* from *chain*.""" From 25f1b6df822798571e1580d6f55084c64254af99 Mon Sep 17 00:00:00 2001 From: loli10K Date: Sat, 1 Nov 2014 08:45:10 +0100 Subject: [PATCH 112/287] Add test for chain.replace_rule() --- iptc/test/test_iptc.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/iptc/test/test_iptc.py b/iptc/test/test_iptc.py index 7c38729..56e7e50 100755 --- a/iptc/test/test_iptc.py +++ b/iptc/test/test_iptc.py @@ -770,6 +770,23 @@ def test_rule_insert(self): self.failUnless(rule in crules) crules.remove(rule) + def test_rule_replace(self): + rule = iptc.Rule() + rule.protocol = "tcp" + rule.src = "127.0.0.1" + target = iptc.Target(rule, "ACCEPT") + rule.target = target + self.chain.insert_rule(rule, 0) + + rule = iptc.Rule() + rule.protocol = "udp" + rule.src = "127.0.0.1" + target = iptc.Target(rule, "ACCEPT") + rule.target = target + + self.chain.replace_rule(rule, 0) + self.failUnless(self.chain.rules[0] == rule) + def test_rule_multiple_parameters(self): self.table.autocommit = False self.table.refresh() From 578d1f832f7226ac97a33bf16c86ec1280d781dc Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 15 Nov 2014 20:33:29 +0100 Subject: [PATCH 113/287] Fix string check in IPTCModule.parse(). In Python2, there are two string types: str and unicode. Check for both when testing if parameter value is a list or not. This fixes #119. --- iptc/ip4tc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index ccf86fb..214cb3c 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -272,7 +272,7 @@ def parse(self, parameter, value): # Value can be either a string, or a list of strings, e.g. "8888", # "!0:65535" or ["!", "example_set", "dst"]. args = [] - if isinstance(value, str): + if isinstance(value, str) or isinstance(value, unicode): args = [value.encode()] else: try: From 37847558089101fbd2116b6692fb08f63d89b96f Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 15 Nov 2014 20:36:58 +0100 Subject: [PATCH 114/287] Add test for #119. --- iptc/test/test_matches.py | 44 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/iptc/test/test_matches.py b/iptc/test/test_matches.py index 8a9897d..b4d7f61 100755 --- a/iptc/test/test_matches.py +++ b/iptc/test/test_matches.py @@ -73,6 +73,44 @@ def test_get_all_parameters(self): self.assertEquals(set(params['dport']), set(['!', '33333'])) +class TestMultiportMatch(unittest.TestCase): + def setUp(self): + self.rule = iptc.Rule() + self.rule.src = "127.0.0.1" + self.rule.protocol = "udp" + self.rule.create_target("ACCEPT") + + self.match = self.rule.create_match("multiport") + + table = iptc.Table(iptc.Table.FILTER) + self.chain = iptc.Chain(table, "iptc_test_udp") + try: + self.chain.flush() + self.chain.delete() + except: + pass + + iptc.Table(iptc.Table.FILTER).create_chain(self.chain) + + def tearDown(self): + self.chain.flush() + self.chain.delete() + + def test_multiport(self): + self.match.dports = '1111,2222' + self.chain.insert_rule(self.rule) + rule = self.chain.rules[0] + match = rule.matches[0] + self.assertEquals(match.dports, '1111,2222') + + def test_unicode_multiport(self): + self.match.dports = u'1111,2222' + self.chain.insert_rule(self.rule) + rule = self.chain.rules[0] + match = rule.matches[0] + self.assertEquals(match.dports, '1111,2222') + + class TestXTUdpMatch(unittest.TestCase): def setUp(self): self.rule = iptc.Rule() @@ -419,6 +457,8 @@ def suite(): suite_udp = unittest.TestLoader().loadTestsFromTestCase(TestXTUdpMatch) suite_mark = unittest.TestLoader().loadTestsFromTestCase(TestXTMarkMatch) suite_limit = unittest.TestLoader().loadTestsFromTestCase(TestXTLimitMatch) + suite_mport = unittest.TestLoader().loadTestsFromTestCase( + TestMultiportMatch) suite_comment = unittest.TestLoader().loadTestsFromTestCase( TestCommentMatch) suite_iprange = unittest.TestLoader().loadTestsFromTestCase( @@ -434,8 +474,8 @@ def suite(): TestIcmpv6Match) return unittest.TestSuite([suite_match, suite_udp, suite_mark, - suite_limit, suite_comment, suite_iprange, - suite_state, suite_conntrack, + suite_limit, suite_mport, suite_comment, + suite_iprange, suite_state, suite_conntrack, suite_hashlimit] + extra_suites) From c8d241ff15cd224bb8a560c7b2d47c62bd57d0b1 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 22 Nov 2014 22:47:46 +0100 Subject: [PATCH 115/287] Update copyright notices. --- NOTICE | 2 +- debian/copyright | 4 ++-- doc/conf.py | 4 ++-- iptc/__init__.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/NOTICE b/NOTICE index 5cdb2d3..1746fec 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ - Copyright (c) 2010-, Nilvec nilvec-(at)-nilvec.com + Copyright (c) 2010-, Vilmos Nebehaj and others Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/debian/copyright b/debian/copyright index 9ab11ee..34a2451 100644 --- a/debian/copyright +++ b/debian/copyright @@ -8,11 +8,11 @@ It was downloaded from: Upstream Author(s): - ldx -> https://github.com/ldx + Vilmos Nebehaj -> https://github.com/ldx Copyright: - Nilvec -> http://nilvec.com + Vilmos Nebehaj License: diff --git a/doc/conf.py b/doc/conf.py index 303b453..6abeedf 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -39,7 +39,7 @@ # General information about the project. project = u'python-iptables' -copyright = u'2010-2013, Nilvec' +copyright = u'2010-2014, Vilmos Nebehaj' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -174,7 +174,7 @@ # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'python-iptables.tex', u'python-iptables Documentation', - u'Nilvec', 'manual'), + u'Vilmos Nebehaj', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of diff --git a/iptc/__init__.py b/iptc/__init__.py index 2a02d4b..9818014 100644 --- a/iptc/__init__.py +++ b/iptc/__init__.py @@ -4,7 +4,7 @@ .. module:: iptc :synopsis: Python bindings for libiptc. -.. moduleauthor:: Nilvec +.. moduleauthor:: Vilmos Nebehaj """ from iptc.ip4tc import (is_table_available, Table, Chain, Rule, Match, Target, From 8fbf0d6508e11bcf9c3a811af34b3d798c9bbd31 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 23 Nov 2014 11:47:32 +0100 Subject: [PATCH 116/287] Update .gitignore. --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 051a899..eeb8a34 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ /MANIFEST /dist /scripts +/Vagrantfile +/.vagrant +/tags From 34b7ce429a132cfd3f101510f475cb96c0479b9d Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 23 Nov 2014 11:47:49 +0100 Subject: [PATCH 117/287] Revert "Disable option checks." This reverts commit 405feb2aa483b7c686b66f78ea82dc675fccc11a. --- iptc/ip4tc.py | 39 +++++++++++++++++++++ iptc/xtables.py | 93 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 214cb3c..6b522c7 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -301,6 +301,13 @@ def parse(self, parameter, value): def _parse(self, argv, inv, entry): raise NotImplementedError() + def final_check(self): + if self._module: + self._final_check() # subclasses override this + + def _final_check(self): + raise NotImplementedError() + def _get_saved_buf(self, ip): if not self._module or not self._module.save: return None @@ -395,6 +402,12 @@ def get_all_parameters(self): params[key].append(x) # This is a parameter value. return params + def _update_parameters(self): + params = self.get_all_parameters().iteritems() + self.reset() + for k, v in params: + self.__setattr__(k, v) + def __setattr__(self, name, value): if not name.startswith('_') and name not in dir(self): self.parse(name.replace("_", "-"), value) @@ -495,6 +508,7 @@ def __init__(self, rule, name=None, match=None, revision=None): if match: ct.memmove(ct.byref(self._match_buf), ct.byref(match), self.size) self._update_pointers() + self._update_parameters() else: self.reset() @@ -534,6 +548,13 @@ def _store_buffer(self, module): self._buffer = _Buffer() self._buffer.buffer = ct.cast(module, ct.POINTER(ct.c_ubyte)) + def _final_check(self): + if self._alias is not None: + module = self._alias + else: + module = self._module + self._xt.final_check_match(module) + def _parse(self, argv, inv, entry): if self._alias is not None: module = self._alias @@ -696,6 +717,7 @@ def _create_buffer(self, target): if target: ct.memmove(self._target_buf, ct.byref(target), self.size) self._update_pointers() + self._update_parameters() else: self.reset() @@ -705,6 +727,13 @@ def _is_standard_target(self): return True return False + def _final_check(self): + if self._alias is not None: + module = self._alias + else: + module = self._module + self._xt.final_check_target(module) + def _parse(self, argv, inv, entry): if self._alias is not None: module = self._alias @@ -900,6 +929,13 @@ def _get_tables(self): tables = property(_get_tables) """This is the list of tables for our protocol.""" + def final_check(self): + """Do a final check on the target and the matches.""" + if self.target: + self.target.final_check() + for match in self.matches: + match.final_check() + def create_match(self, name, revision=None): """Create a *match*, and add it to the list of matches in this rule. *name* is the name of the match extension, *revision* is the revision @@ -1362,6 +1398,7 @@ def is_builtin(self): def append_rule(self, rule): """Append *rule* to the end of the chain.""" + rule.final_check() rbuf = rule.rule if not rbuf: raise ValueError("invalid rule") @@ -1370,6 +1407,7 @@ def append_rule(self, rule): def insert_rule(self, rule, position=0): """Insert *rule* as the first entry in the chain if *position* is 0 or not specified, else *rule* is inserted in the given position.""" + rule.final_check() rbuf = rule.rule if not rbuf: raise ValueError("invalid rule") @@ -1384,6 +1422,7 @@ def replace_rule(self, rule, position=0): def delete_rule(self, rule): """Removes *rule* from the chain.""" + rule.final_check() rbuf = rule.rule if not rbuf: raise ValueError("invalid rule") diff --git a/iptc/xtables.py b/iptc/xtables.py index 045a712..78ecb40 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -1042,3 +1042,96 @@ def parse_match(self, argv, invert, m, fw, ptr): flags = ct.pointer(ct.c_uint(0)) self._parse(m, argv, invert, flags, fw, ptr) m.mflags |= flags[0] + + # Check that all option constraints have been met. This effectively + # replaces ->final_check of the older API. + def _options_fcheck(self, name, xflags, table): + for entry in table: + if entry.name is None: + break + if entry.flags & XTOPT_MAND and not xflags & (1 << entry.id): + raise XTablesError("%s: --%s must be specified" % (name, + entry.name)) + if not xflags & (1 << entry.id): + continue + # XXX: check for conflicting options + + def _fcheck_target_old(self, target): + # old API + if not target.final_check: + return + rv = _wrap_uintfn(target.final_check, target.tflags) + if rv: + raise XTablesError("%s.final_check() has failed" % + (target.name)) + + def _fcheck_target_new(self, target): + # new API + cb = xt_fcheck_call() + cb.ext_name = target.name + cb.data = ct.cast(target.t[0].data, ct.c_void_p) + cb.xflags = target.tflags + cb.udata = target.udata + rv = _wrap_x6fn(target.x6_fcheck, ct.pointer(cb)) + if rv: + raise XTablesError("%s.x6_fcheck has failed" % (target.name)) + if target.x6_options: + self._options_fcheck(target.name, target.tflags, + target.x6_options) + + # Dispatch arguments to the appropriate final_check function, based upon + # the extension's choice of API. + @preserve_globals + def final_check_target(self, target): + x6_fcheck = None + try: + # new API? + x6_fcheck = target.x6_fcheck + except AttributeError: + # old API + pass + + if x6_fcheck: + self._fcheck_target_new(target) + else: + self._fcheck_target_old(target) + + def _fcheck_match_old(self, match): + # old API + if not match.final_check: + return + rv = _wrap_uintfn(match.final_check, match.mflags) + if rv: + raise XTablesError("%s.final_check() has failed" % + (match.name)) + + def _fcheck_match_new(self, match): + # new API + cb = xt_fcheck_call() + cb.ext_name = match.name + cb.data = ct.cast(match.m[0].data, ct.c_void_p) + cb.xflags = match.mflags + cb.udata = match.udata + rv = _wrap_x6fn(match.x6_fcheck, ct.pointer(cb)) + if rv: + raise XTablesError("%s.x6_fcheck has failed" % (match.name)) + if match.x6_options: + self._options_fcheck(match.name, match.mflags, + match.x6_options) + + # Dispatch arguments to the appropriate final_check function, based upon + # the extension's choice of API. + @preserve_globals + def final_check_match(self, match): + x6_fcheck = None + try: + # new API? + x6_fcheck = match.x6_fcheck + except AttributeError: + # old API + pass + + if x6_fcheck: + self._fcheck_match_new(match) + else: + self._fcheck_match_old(match) From ceb8dd61a0734bfb8649820e77bcfa1bae7aecc5 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 23 Nov 2014 22:12:18 +0100 Subject: [PATCH 118/287] Release 0.6.0. --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index a907981..ebdb06c 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.6.0-dev" +__version__ = "0.6.0" From 389e269e5eb4419729abb3511e953ae94e9cb0a2 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 23 Nov 2014 22:13:28 +0100 Subject: [PATCH 119/287] Start 0.7.0-dev. --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index ebdb06c..302d1ca 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.6.0" +__version__ = "0.7.0-dev" From 2691804b7ef77712335447ff194f6043d3ca3c23 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Thu, 25 Dec 2014 18:29:03 +0100 Subject: [PATCH 120/287] Implement better alias handling. When an extension has a 'real_name' attribute, look up that module and use it as self._module except the 'x6_parse' and 'x6_options' attributes from the original module (if they are not NULL). The logic in iptables is that alias modules only use these two for parsing input parameters, everything else is implemented in the 'real' module. --- iptc/ip4tc.py | 99 +++++++++++++++---------------------------------- iptc/xtables.py | 40 ++++++++++---------- 2 files changed, 50 insertions(+), 89 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 6b522c7..4c23143 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -487,14 +487,22 @@ def __init__(self, rule, name=None, match=None, revision=None): name = match.u.user.name.decode() self._name = name self._rule = rule - self._alias = None - self._real_name = None + self._orig_parse = None + self._orig_options = None self._xt = xtables(rule.nfproto) module = self._xt.find_match(name) + real_name = module and getattr(module[0], 'real_name', None) or None + if real_name: + # Alias name, look up real module. + self._name = real_name.decode() + self._orig_parse = getattr(module[0], 'x6_parse', None) + self._orig_options = getattr(module[0], 'x6_options', None) + module = self._xt.find_match(real_name) if not module: raise XTablesError("can't find match %s" % (name)) + self._module = module[0] self._module.mflags = 0 if revision is not None: @@ -529,39 +537,17 @@ def __hash__(self): def __ne__(self, match): return not self.__eq__(match) - def _check_alias(self, module, match): - # This is ugly, but there are extensions using an alias name. Check if - # that's the case, and load that extension as well if necessary. It - # will be used to parse parameters, since the 'real' extension - # probably won't understand them. - if getattr(module, "real_name", None) is not None and module.real_name: - self._real_name = module.real_name.decode() - if getattr(module, "alias", None) is not None and module.alias: - self._alias_name = module.alias(match) - alias = self._xt.find_match(self._alias_name) - if not alias: - raise XTablesError("can't find alias match %s" % - (self._alias_name)) - self._alias = alias[0] - def _store_buffer(self, module): self._buffer = _Buffer() self._buffer.buffer = ct.cast(module, ct.POINTER(ct.c_ubyte)) def _final_check(self): - if self._alias is not None: - module = self._alias - else: - module = self._module - self._xt.final_check_match(module) + self._xt.final_check_match(self._module) def _parse(self, argv, inv, entry): - if self._alias is not None: - module = self._alias - else: - module = self._module - self._xt.parse_match(argv, inv, module, entry, - ct.cast(self._ptrptr, ct.POINTER(ct.c_void_p))) + self._xt.parse_match(argv, inv, self._module, entry, + ct.cast(self._ptrptr, ct.POINTER(ct.c_void_p)), + self._orig_parse, self._orig_options) def _get_size(self): return xt_align(self._module.size + ct.sizeof(xt_entry_match)) @@ -580,17 +566,11 @@ def _update_pointers(self): self._ptrptr = ct.cast(ct.pointer(self._ptr), ct.POINTER(ct.POINTER(xt_entry_match))) self._module.m = self._ptr - self._check_alias(self._module, self._module.m) - if self._alias is not None: - self._alias.m = self._ptr self._update_name() def _update_name(self): m = self._ptr[0] - if self._real_name is not None: - m.u.user.name = self._real_name.encode() - else: - m.u.user.name = self.name.encode() + m.u.user.name = self.name.encode() def reset(self): """Reset the match. @@ -653,15 +633,24 @@ def __init__(self, rule, name=None, target=None, revision=None): name = target.u.user.name.decode() self._name = name self._rule = rule + self._orig_parse = None + self._orig_options = None self._xt = xtables(rule.nfproto) module = (self._is_standard_target() and self._xt.find_target('standard') or self._xt.find_target(name)) - + real_name = module and getattr(module[0], 'real_name', None) or None + if real_name: + # Alias name, look up real module. + self._name = real_name.decode() + self._orig_parse = getattr(module, 'x6_parse', None) + self._orig_options = getattr(module, 'x6_options', None) + module = self._xt.find_target(real_name) if not module: raise XTablesError("can't find target %s" % (name)) + self._module = module[0] self._module.tflags = 0 if revision is not None: @@ -696,21 +685,6 @@ def __eq__(self, targ): def __ne__(self, target): return not self.__eq__(target) - def _check_alias(self, module, target): - # This is ugly, but there are extensions using an alias name. Check if - # that's the case, and load that extension as well if necessary. It - # will be used to parse parameters, since the 'real' extension - # probably won't understand them. - if getattr(module, "real_name", None) is not None and module.real_name: - self._real_name = module.real_name.decode() - if getattr(module, "alias", None) is not None and module.alias: - self._alias_name = module.alias(target) - alias = self._xt.find_target(self._alias_name) - if not alias: - raise XTablesError("can't find alias target %s" % - (self._alias_name)) - self._alias = alias[0] - def _create_buffer(self, target): self._buffer = _Buffer(self.size) self._target_buf = self._buffer.buffer @@ -728,19 +702,12 @@ def _is_standard_target(self): return False def _final_check(self): - if self._alias is not None: - module = self._alias - else: - module = self._module - self._xt.final_check_target(module) + self._xt.final_check_target(self._module) def _parse(self, argv, inv, entry): - if self._alias is not None: - module = self._alias - else: - module = self._module - self._xt.parse_target(argv, inv, module, entry, - ct.cast(self._ptrptr, ct.POINTER(ct.c_void_p))) + self._xt.parse_target(argv, inv, self._module, entry, + ct.cast(self._ptrptr, ct.POINTER(ct.c_void_p)), + self._orig_parse, self._orig_options) self._target_buf = ct.cast(self._module.t, ct.POINTER(ct.c_ubyte)) if self._buffer.buffer != self._target_buf: if self._buffer.buffer is not None: @@ -782,17 +749,11 @@ def _update_pointers(self): self._ptrptr = ct.cast(ct.pointer(self._ptr), ct.POINTER(ct.POINTER(xt_entry_target))) self._module.t = self._ptr - self._check_alias(self._module, self._module.t) - if self._alias is not None: - self._alias.t = self._ptr self._update_name() def _update_name(self): m = self._ptr[0] - if self._real_name is not None: - m.u.user.name = self._real_name.encode() - else: - m.u.user.name = self.name.encode() + m.u.user.name = self.name.encode() def reset(self): """Reset the target. Parameters are set to their default values, any diff --git a/iptc/xtables.py b/iptc/xtables.py index 78ecb40..2592efe 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -956,22 +956,22 @@ def _parse(self, module, argv, inv, flags, entry, ptr): # Dispatch arguments to the appropriate parse function, based upon the # extension's choice of API. @preserve_globals - def parse_target(self, argv, invert, t, fw, ptr): - _optarg.value = argv[1] - _optind.value = 2 + def parse_target(self, argv, invert, t, fw, ptr, x6_parse, x6_options): + _optarg.value = len(argv) > 1 and argv[1] or None + _optind.value = len(argv) - 1 - x6_options = None - x6_parse = None try: # new API? - x6_options = t.x6_options - x6_parse = t.x6_parse + if x6_options is None: + x6_options = t.x6_options + if x6_parse is None: + x6_parse = t.x6_parse except AttributeError: pass if x6_options and x6_parse: # new API - entry = self._option_lookup(t.x6_options, argv[0]) + entry = self._option_lookup(x6_options, argv[0]) if not entry: raise XTablesError("%s: no such parameter %s" % (t.name, argv[0])) @@ -986,7 +986,7 @@ def parse_target(self, argv, invert, t, fw, ptr): cb.target = ct.pointer(t.t) cb.xt_entry = ct.cast(fw, ct.c_void_p) cb.udata = t.udata - rv = _wrap_x6fn(t.x6_parse, ct.pointer(cb)) + rv = _wrap_x6fn(x6_parse, ct.pointer(cb)) if rv != 0: raise XTablesError("%s: parameter error %d (%s)" % (t.name, rv, argv[1])) @@ -1001,22 +1001,22 @@ def parse_target(self, argv, invert, t, fw, ptr): # Dispatch arguments to the appropriate parse function, based upon the # extension's choice of API. @preserve_globals - def parse_match(self, argv, invert, m, fw, ptr): - _optarg.value = argv[1] - _optind.value = 2 + def parse_match(self, argv, invert, m, fw, ptr, x6_parse, x6_options): + _optarg.value = len(argv) > 1 and argv[1] or None + _optind.value = len(argv) - 1 - x6_options = None - x6_parse = None try: # new API? - x6_options = m.x6_options - x6_parse = m.x6_parse + if x6_options is None: + x6_options = m.x6_options + if x6_parse is None: + x6_parse = m.x6_parse except AttributeError: pass if x6_options and x6_parse: # new API - entry = self._option_lookup(m.x6_options, argv[0]) + entry = self._option_lookup(x6_options, argv[0]) if not entry: raise XTablesError("%s: no such parameter %s" % (m.name, argv[0])) @@ -1031,10 +1031,10 @@ def parse_match(self, argv, invert, m, fw, ptr): cb.match = ct.pointer(m.m) cb.xt_entry = ct.cast(fw, ct.c_void_p) cb.udata = m.udata - rv = _wrap_x6fn(m.x6_parse, ct.pointer(cb)) + rv = _wrap_x6fn(x6_parse, ct.pointer(cb)) if rv != 0: - raise XTablesError("%s: parameter error %d (%s)" % (m.name, rv, - argv[1])) + raise XTablesError("%s: parameter '%s' error %d" % ( + m.name, len(argv) > 1 and argv[1] or "", rv)) m.mflags |= cb.xflags return From 5a9ab96c9763cca3f9f5627e5fef223e5cf03d18 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Thu, 25 Dec 2014 18:32:26 +0100 Subject: [PATCH 121/287] Only call _update_parameters() in final_check(). --- iptc/ip4tc.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 4c23143..83512b7 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -303,6 +303,7 @@ def _parse(self, argv, inv, entry): def final_check(self): if self._module: + self._update_parameters() self._final_check() # subclasses override this def _final_check(self): @@ -516,7 +517,6 @@ def __init__(self, rule, name=None, match=None, revision=None): if match: ct.memmove(ct.byref(self._match_buf), ct.byref(match), self.size) self._update_pointers() - self._update_parameters() else: self.reset() @@ -691,7 +691,6 @@ def _create_buffer(self, target): if target: ct.memmove(self._target_buf, ct.byref(target), self.size) self._update_pointers() - self._update_parameters() else: self.reset() From ac840e4f1d776c0e7a621ec86c45adda0dcc0ba2 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Thu, 25 Dec 2014 18:32:55 +0100 Subject: [PATCH 122/287] Use correct parameters for hashlimit test. --- iptc/test/test_matches.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iptc/test/test_matches.py b/iptc/test/test_matches.py index b4d7f61..0b55524 100755 --- a/iptc/test/test_matches.py +++ b/iptc/test/test_matches.py @@ -439,8 +439,8 @@ def test_hashlimit(self): self.match.hashlimit_name = 'foo' self.match.hashlimit_mode = 'srcip' self.match.hashlimit_upto = '200/sec' - self.match.hashlimit_burst = '5' - self.match.hashlimit_htable_expire = '1000' + self.match.hashlimit = '200' + self.match.hashlimit_htable_expire = '100' self.rule.add_match(self.match) self.chain.insert_rule(self.rule) rule = self.chain.rules[0] From 62063632592fc190faa1299eb343e2cb5aade665 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Thu, 25 Dec 2014 18:37:14 +0100 Subject: [PATCH 123/287] Fix python3 compatibility. Use dict.items() instead of dict.iteritems(). --- iptc/ip4tc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 83512b7..16bd0ef 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -404,7 +404,7 @@ def get_all_parameters(self): return params def _update_parameters(self): - params = self.get_all_parameters().iteritems() + params = self.get_all_parameters().items() self.reset() for k, v in params: self.__setattr__(k, v) From 2b895f6767b05731d8d6c228a8c0f64f225dd549 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Thu, 25 Dec 2014 18:45:38 +0100 Subject: [PATCH 124/287] Add python3 type check workaround. --- iptc/ip4tc.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 16bd0ef..9456890 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -272,7 +272,15 @@ def parse(self, parameter, value): # Value can be either a string, or a list of strings, e.g. "8888", # "!0:65535" or ["!", "example_set", "dst"]. args = [] - if isinstance(value, str) or isinstance(value, unicode): + + is_str = isinstance(value, str) + try: + if not is_str: + is_str = isinstance(value, unicode) + except: + pass + + if is_str: args = [value.encode()] else: try: From 868ec2ee90c69da642abb0e6659803ac2315666a Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Thu, 25 Dec 2014 19:09:52 +0100 Subject: [PATCH 125/287] Fix real module attribute check for Target. --- iptc/ip4tc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 9456890..d3c82c9 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -653,8 +653,8 @@ def __init__(self, rule, name=None, target=None, revision=None): if real_name: # Alias name, look up real module. self._name = real_name.decode() - self._orig_parse = getattr(module, 'x6_parse', None) - self._orig_options = getattr(module, 'x6_options', None) + self._orig_parse = getattr(module[0], 'x6_parse', None) + self._orig_options = getattr(module[0], 'x6_options', None) module = self._xt.find_target(real_name) if not module: raise XTablesError("can't find target %s" % (name)) From ab277c7e29c95c3ad74a1b2bf5f8fd45119827c1 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Fri, 26 Dec 2014 22:37:56 +0100 Subject: [PATCH 126/287] Try to look up real extension name if init fails. When we are looking for a match/target and can't seem to find/register it, try to check for its "real" name (assuming it is an alias). E.g. the "state" match is now an alias only in the "conntrack" module, and the init function for registering it is called "libxt_conntrack_init". This fixes #112. --- iptc/xtables.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index 2592efe..5cfbefc 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -773,6 +773,11 @@ class xtables(object): except ValueError: _xtables_pending_targets = ct.POINTER(None) + _real_name = { + 'state': 'conntrack', + 'NOTRACK': 'CT' + } + _cache = weakref.WeakValueDictionary() def __new__(cls, proto): @@ -783,8 +788,9 @@ def __new__(cls, proto): obj._xtinit(proto) return obj - def _xtinit(self, proto): + def _xtinit(self, proto, no_alias_check=False): self.proto = proto + self.no_alias_check = no_alias_check self._xt_globals = xtables_globals() self._xt_globals.option_offset = 0 self._xt_globals.program_name = version.__pkgname__.encode() @@ -865,6 +871,10 @@ def _get_initfn_from_lib(self, name, lib): except AttributeError: prefix = self._get_prefix() initfn = getattr(lib, "%s%s_init" % (prefix, name), None) + if initfn is None and not self.no_alias_check: + if name in xtables._real_name: + name = xtables._real_name[name] + initfn = self._get_initfn_from_lib(name, lib) return initfn def _try_extinit(self, name, lib): From 5d27a36b2bed206e2abdb6438b68f7a4aae5f383 Mon Sep 17 00:00:00 2001 From: Bin He Date: Sat, 27 Dec 2014 12:26:15 +0800 Subject: [PATCH 127/287] Add to README:query the counters of the rule together with the proto and port --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 0bb1a24..239547e 100644 --- a/README.md +++ b/README.md @@ -458,6 +458,22 @@ counters: >>> (packets, bytes) = rule.get_counters() >>> print packets, bytes +What is more, if you add + + iptables -A OUTPUT -p tcp --sport 80 + iptables -A OUTPUT -p tcp --sport 22 + +you can query rule and chain counters together with the protocol and sport(or dport), e.g.: + + >>> import iptc + >>> table = iptc.Table(iptc.Table.FILTER) + >>> chain = iptc.Chain(table, 'OUTPUT') + >>> for rule in chain.rules: + >>> for match in rule.matches: + >>> (packets, bytes) = rule.get_counters() + >>> print packets, bytes, match.name, match.sport + + Autocommit ---------- From a48f5b589078c94d85858a8b1e7f2033ed84003f Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 10 Jan 2015 23:43:08 +0100 Subject: [PATCH 128/287] Move doc update to rst. --- doc/examples.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/examples.rst b/doc/examples.rst index 576b425..0345fe0 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -353,6 +353,22 @@ your rules. You have to refresh your table to get update your counters:: >>> (packets, bytes) = rule.get_counters() >>> print packets, bytes +What is more, if you add:: + + iptables -A OUTPUT -p tcp --sport 80 + iptables -A OUTPUT -p tcp --sport 22 + +you can query rule and chain counters together with the protocol and sport(or +dport), e.g.:: + + >>> import iptc + >>> table = iptc.Table(iptc.Table.FILTER) + >>> chain = iptc.Chain(table, 'OUTPUT') + >>> for rule in chain.rules: + >>> for match in rule.matches: + >>> (packets, bytes) = rule.get_counters() + >>> print packets, bytes, match.name, match.sport + Autocommit ---------- ``Python-iptables`` by default automatically performs an iptables commit after From fa411d9d86e84f9b498ff51875d6f8d3d3b02869 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 10 Jan 2015 23:43:31 +0100 Subject: [PATCH 129/287] Documentation cosmetics. --- doc/intro.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/intro.rst b/doc/intro.rst index 4e10442..d593386 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -29,10 +29,6 @@ Linux iptables framework.. ``Python-iptables`` supports Python 2.6, 2.7 and 3.4. -.. image:: https://travis-ci.org/ldx/python-iptables.png?branch=master - :target: https://travis-ci.org/ldx/python-iptables - :alt: Build Status - .. image:: http://api.flattr.com/button/flattr-badge-large.png :target: https://flattr.com/submit/auto?user_id=ldx&url=https%3A%2F%2Fgithub.com%2Fldx%2Fpython-iptables :alt: Flattr @@ -41,6 +37,10 @@ Linux iptables framework.. :target: https://pypi.python.org/pypi/python-iptables :alt: Latest Release +.. image:: https://travis-ci.org/ldx/python-iptables.png?branch=master + :target: https://travis-ci.org/ldx/python-iptables + :alt: Build Status + .. image:: https://pypip.in/d/python-iptables/badge.png :target: https://pypi.python.org/pypi/python-iptables :alt: Number of Downloads From 6183287dd2d25124ea8dde7c85aed72cedec51ba Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 10 Jan 2015 23:43:45 +0100 Subject: [PATCH 130/287] Regenerate README.md. --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 239547e..1e20c44 100644 --- a/README.md +++ b/README.md @@ -29,12 +29,12 @@ wish to interface with the Linux iptables framework.. `Python-iptables` supports Python 2.6, 2.7 and 3.4. -[![Build Status](https://travis-ci.org/ldx/python-iptables.png?branch=master)](https://travis-ci.org/ldx/python-iptables) - [![Flattr](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=ldx&url=https%3A%2F%2Fgithub.com%2Fldx%2Fpython-iptables) [![Latest Release](https://pypip.in/v/python-iptables/badge.png)](https://pypi.python.org/pypi/python-iptables) +[![Build Status](https://travis-ci.org/ldx/python-iptables.png?branch=master)](https://travis-ci.org/ldx/python-iptables) + [![Number of Downloads](https://pypip.in/d/python-iptables/badge.png)](https://pypi.python.org/pypi/python-iptables) [![License](https://pypip.in/license/python-iptables/badge.png)](https://pypi.python.org/pypi/python-iptables) @@ -458,12 +458,13 @@ counters: >>> (packets, bytes) = rule.get_counters() >>> print packets, bytes -What is more, if you add - +What is more, if you add: + iptables -A OUTPUT -p tcp --sport 80 iptables -A OUTPUT -p tcp --sport 22 -you can query rule and chain counters together with the protocol and sport(or dport), e.g.: +you can query rule and chain counters together with the protocol and +sport(or dport), e.g.: >>> import iptc >>> table = iptc.Table(iptc.Table.FILTER) @@ -473,7 +474,6 @@ you can query rule and chain counters together with the protocol and sport(or dp >>> (packets, bytes) = rule.get_counters() >>> print packets, bytes, match.name, match.sport - Autocommit ---------- From c92093f89775e3679bdf6fb5c7554e3328d0cdb1 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 10 Jan 2015 23:45:57 +0100 Subject: [PATCH 131/287] Release 0.7.0. --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index 302d1ca..43aabc2 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.7.0-dev" +__version__ = "0.7.0" From 838c2a71b8c7883ec21989434fcaac0d8c67de52 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 10 Jan 2015 23:55:16 +0100 Subject: [PATCH 132/287] Start 0.8.0-dev. --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index 43aabc2..a00f4fb 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.7.0" +__version__ = "0.8.0-dev" From cacb878e07b771d9ce2bf7a3306f052a0f7d5e4e Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 10 Jan 2015 23:55:31 +0100 Subject: [PATCH 133/287] Add link to python-ebtables. --- doc/intro.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/intro.rst b/doc/intro.rst index d593386..b54b26f 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -27,6 +27,9 @@ meant primarily for dynamic and/or complex routers and firewalls, where rules are often updated or changed, or Python programs wish to interface with the Linux iptables framework.. +If you are looking for ``ebtables`` python bindings, check out +`python-ebtables `_. + ``Python-iptables`` supports Python 2.6, 2.7 and 3.4. .. image:: http://api.flattr.com/button/flattr-badge-large.png From 8eba9859e058cca04b5841521c364a8310af452f Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 10 Jan 2015 23:55:40 +0100 Subject: [PATCH 134/287] Regenerate README.md. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 1e20c44..9e7c5bf 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,9 @@ output. It is meant primarily for dynamic and/or complex routers and firewalls, where rules are often updated or changed, or Python programs wish to interface with the Linux iptables framework.. +If you are looking for `ebtables` python bindings, check out +[python-ebtables](https://github.com/ldx/python-ebtables/). + `Python-iptables` supports Python 2.6, 2.7 and 3.4. [![Flattr](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=ldx&url=https%3A%2F%2Fgithub.com%2Fldx%2Fpython-iptables) From 8a0af8ae0d4c3dbdad3bafeedfbf1c83b00355dc Mon Sep 17 00:00:00 2001 From: YangZheng Date: Wed, 21 Jan 2015 04:21:40 +0800 Subject: [PATCH 135/287] Modify the README Word spelling mistake --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9e7c5bf..244dfd1 100644 --- a/README.md +++ b/README.md @@ -487,7 +487,7 @@ after each operation. That is, after you add a rule in It may happen that you want to batch together certain operations. A typical use case is traversing a chain and removing rules matching a specific criteria. If you do this with autocommit enabled, after the -first delete operation, your chain's state will chain and you have to +first delete operation, your chain's state will change and you have to restart the traversal. You can do something like this: >>> import iptc From 6ef4a3f5f0e911677df732e77237b0a1433f6ab7 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Wed, 21 Jan 2015 11:06:14 +0100 Subject: [PATCH 136/287] Add default modprobe path. --- iptc/util.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/iptc/util.py b/iptc/util.py index d2750f3..fc8cfe6 100644 --- a/iptc/util.py +++ b/iptc/util.py @@ -20,8 +20,12 @@ def _insert_ko(modprobe, modname): def _load_ko(modname): # this will return the full path for the modprobe binary - proc = open("/proc/sys/kernel/modprobe") - modprobe = proc.read(1024) + modprobe = "/sbin/modprobe" + try: + proc = open("/proc/sys/kernel/modprobe") + modprobe = proc.read(1024) + except: + pass if modprobe[-1] == '\n': modprobe = modprobe[:-1] return _insert_ko(modprobe, modname) From 7790f99302203b7af7eece6353408df18c8305bf Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Wed, 21 Jan 2015 11:07:27 +0100 Subject: [PATCH 137/287] Swallow exception if modprobe fails. --- iptc/ip4tc.py | 5 ++++- iptc/ip6tc.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index d3c82c9..d1931b4 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -15,7 +15,10 @@ __all__ = ["Table", "Chain", "Rule", "Match", "Target", "Policy", "IPTCError"] -load_kernel("ip_tables") +try: + load_kernel("ip_tables") +except: + pass _IFNAMSIZ = 16 diff --git a/iptc/ip6tc.py b/iptc/ip6tc.py index 0535b9e..c186845 100644 --- a/iptc/ip6tc.py +++ b/iptc/ip6tc.py @@ -10,7 +10,10 @@ __all__ = ["Table6", "Rule6"] -load_kernel("ip6_tables") +try: + load_kernel("ip6_tables") +except: + pass _IFNAMSIZ = 16 From d93c05f5707090614beaade475f9e63ea0004a1f Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Wed, 21 Jan 2015 11:36:58 +0100 Subject: [PATCH 138/287] Fix typo in examples.rst too. --- doc/examples.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/examples.rst b/doc/examples.rst index 0345fe0..5efbf42 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -378,7 +378,7 @@ will take effect immediately. It may happen that you want to batch together certain operations. A typical use case is traversing a chain and removing rules matching a specific criteria. If you do this with autocommit enabled, after the first delete -operation, your chain's state will chain and you have to restart the +operation, your chain's state will change and you have to restart the traversal. You can do something like this:: >>> import iptc From 94556218d9c567ce47591806b8fa1445db3e214c Mon Sep 17 00:00:00 2001 From: jazzmes Date: Thu, 19 Feb 2015 15:51:19 -0800 Subject: [PATCH 139/287] Fix: retrieve library extension --- iptc/util.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/iptc/util.py b/iptc/util.py index fc8cfe6..6f1cbd4 100644 --- a/iptc/util.py +++ b/iptc/util.py @@ -70,9 +70,10 @@ def _do_find_library(name): def _find_library(*names): - ext = get_config_var('SO') - if version_info > (3, ) and version_info < (3, 4): - ext = '.cpython-%i%i' % (version_info.major, version_info.minor) + ext + if version_info > (3, ): + ext = get_config_var("EXT_SUFFIX") + else: + ext = get_config_var('SO') for name in names: for n in (name, "lib" + name, name + ext, "lib" + name + ext): lib = _do_find_library(n) From a46dc0bff84815ba578721f95d95da68d4334fac Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 28 Feb 2015 17:06:57 +0100 Subject: [PATCH 140/287] Load standard target as ''. --- iptc/ip4tc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index d1931b4..45d047c 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -650,7 +650,7 @@ def __init__(self, rule, name=None, target=None, revision=None): self._xt = xtables(rule.nfproto) module = (self._is_standard_target() and - self._xt.find_target('standard') or + self._xt.find_target('') or self._xt.find_target(name)) real_name = module and getattr(module[0], 'real_name', None) or None if real_name: From 09cb2f4c42be4a39483d9657255635b5b8840a7b Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 28 Feb 2015 17:07:27 +0100 Subject: [PATCH 141/287] Use a set for saving loaded extensions. --- iptc/xtables.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index 5cfbefc..4b44f73 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -809,7 +809,7 @@ def _xtinit(self, proto, no_alias_check=False): raise XTablesError("unknown xtables version %d" % (_xtables_version)) - self._loaded_exts = [] + self._loaded_exts = set() # make sure we're initializing with clean state self._xt_params = ct.c_void_p(None).value @@ -857,7 +857,7 @@ def _check_extname(self, name): return name def _loaded(self, name): - self._loaded_exts.append(name) + self._loaded_exts.add(name) def _is_loaded(self, name): if name in self._loaded_exts: From 3729dd3b5f39029d41de5f4b49fae46c5b9b1b1b Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 28 Feb 2015 17:08:39 +0100 Subject: [PATCH 142/287] Check if extension can be loaded in _try_register(). --- iptc/xtables.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/iptc/xtables.py b/iptc/xtables.py index 4b44f73..9a81203 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -898,6 +898,8 @@ def _get_prefix(self): raise XTablesError("Unknown protocol %d" % (self.proto)) def _try_register(self, name): + if self._is_loaded(name): + return if isinstance(name, bytes): name = name.decode() if self._try_extinit(name, _lib_xtables): From eaecb80be1d2c8077bf2f07fba86b327d215a478 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 28 Feb 2015 17:09:30 +0100 Subject: [PATCH 143/287] Use XTF_DONT_LOAD when finding extension the second time. --- iptc/xtables.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index 9a81203..56341ee 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -919,7 +919,7 @@ def find_match(self, name): match = xtables._xtables_find_match(name, XTF_TRY_LOAD, None) if not match: self._try_register(name) - match = xtables._xtables_find_match(name, XTF_TRY_LOAD, None) + match = xtables._xtables_find_match(name, XTF_DONT_LOAD, None) if not match: return match self._loaded(name) @@ -934,7 +934,7 @@ def find_target(self, name): target = xtables._xtables_find_target(name, XTF_TRY_LOAD) if not target: self._try_register(name) - target = xtables._xtables_find_target(name, XTF_TRY_LOAD) + target = xtables._xtables_find_target(name, XTF_DONT_LOAD) if not target: return target self._loaded(name) From c64b23fe8615f863705f9c6dee492e7a48207cde Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 28 Feb 2015 17:10:08 +0100 Subject: [PATCH 144/287] Use extension's name when saving loaded state. --- iptc/xtables.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index 56341ee..fa41e66 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -922,9 +922,10 @@ def find_match(self, name): match = xtables._xtables_find_match(name, XTF_DONT_LOAD, None) if not match: return match - self._loaded(name) - return ct.cast(match, ct.POINTER(self._match_struct)) + m = ct.cast(match, ct.POINTER(self._match_struct)) + self._loaded(m[0].name) + return m @preserve_globals def find_target(self, name): @@ -937,9 +938,10 @@ def find_target(self, name): target = xtables._xtables_find_target(name, XTF_DONT_LOAD) if not target: return target - self._loaded(name) - return ct.cast(target, ct.POINTER(self._target_struct)) + t = ct.cast(target, ct.POINTER(self._target_struct)) + self._loaded(t[0].name) + return t @preserve_globals def save(self, module, ip, ptr): From f8758f9f8391e06a8347aba560c0fb6539ce8cbb Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 28 Feb 2015 20:03:32 +0100 Subject: [PATCH 145/287] Move imports to top of file. --- iptc/util.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/iptc/util.py b/iptc/util.py index 6f1cbd4..5f21373 100644 --- a/iptc/util.py +++ b/iptc/util.py @@ -1,6 +1,9 @@ import re +import os +import sys import ctypes import ctypes.util +from distutils.sysconfig import get_python_lib from subprocess import Popen, PIPE from sys import version_info try: @@ -50,8 +53,6 @@ def _do_find_library(name): return lib # probably we have been installed in a virtualenv - import os - from distutils.sysconfig import get_python_lib try: lib = ctypes.CDLL(os.path.join(get_python_lib(), name), mode=ctypes.RTLD_GLOBAL) @@ -59,7 +60,6 @@ def _do_find_library(name): except: pass - import sys for p in sys.path: try: lib = ctypes.CDLL(os.path.join(p, name), mode=ctypes.RTLD_GLOBAL) From b7f78cde8165d75cd642ee7e0db79d56ebe919ae Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 28 Feb 2015 20:04:25 +0100 Subject: [PATCH 146/287] Check IPTABLES_LIBDIR first when loading libraries. This fixes #138. --- iptc/util.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/iptc/util.py b/iptc/util.py index 5f21373..ec69a8b 100644 --- a/iptc/util.py +++ b/iptc/util.py @@ -4,6 +4,7 @@ import ctypes import ctypes.util from distutils.sysconfig import get_python_lib +from itertools import product from subprocess import Popen, PIPE from sys import version_info try: @@ -75,7 +76,15 @@ def _find_library(*names): else: ext = get_config_var('SO') for name in names: - for n in (name, "lib" + name, name + ext, "lib" + name + ext): + libnames = [name, "lib" + name, name + ext, "lib" + name + ext] + libdir = os.environ.get('IPTABLES_LIBDIR', None) + if libdir is not None: + libdirs = libdir.split(':') + libs = [os.path.join(*p) for p in product(libdirs, libnames)] + libs.extend(libnames) + else: + libs = libnames + for n in libs: lib = _do_find_library(n) if lib is not None: yield lib From 3b7936e55fa0fa480f14ba741329b17f6c7b6b48 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 28 Feb 2015 20:35:41 +0100 Subject: [PATCH 147/287] Resolve symlinks when search for libraries. --- iptc/util.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/iptc/util.py b/iptc/util.py index ec69a8b..00fe20e 100644 --- a/iptc/util.py +++ b/iptc/util.py @@ -85,6 +85,8 @@ def _find_library(*names): else: libs = libnames for n in libs: + while os.path.islink(n): + n = os.path.realpath(n) lib = _do_find_library(n) if lib is not None: yield lib @@ -93,7 +95,7 @@ def _find_library(*names): def find_library(*names): for lib in _find_library(*names): major = 0 - m = re.search(r"\.so\.(\d+)", lib._name) + m = re.search(r"\.so\.(\d+).?", lib._name) if m: major = int(m.group(1)) return lib, major From 4627e94ee9b997827abcfa875c0cdd27909e5a2c Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 28 Feb 2015 21:42:25 +0100 Subject: [PATCH 148/287] Make iptc.xtables.xtables_version public. --- iptc/xtables.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index fa41e66..da481aa 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -677,7 +677,7 @@ class XTablesError(Exception): _optind = ct.c_long.in_dll(_libc, "optind") _optarg = ct.c_char_p.in_dll(_libc, "optarg") -_lib_xtables, _xtables_version = find_library("xtables") +_lib_xtables, xtables_version = find_library("xtables") _xtables_libdir = os.getenv("XTABLES_LIBDIR") if _xtables_libdir is None: import os.path @@ -800,14 +800,14 @@ def _xtinit(self, proto, no_alias_check=False): self._xt_globals.exit_err = _xt_exit thismodule = sys.modules[__name__] - matchname = "_xtables_match_v%d" % (_xtables_version) - targetname = "_xtables_target_v%d" % (_xtables_version) + matchname = "_xtables_match_v%d" % (xtables_version) + targetname = "_xtables_target_v%d" % (xtables_version) try: self._match_struct = getattr(thismodule, matchname) self._target_struct = getattr(thismodule, targetname) except: raise XTablesError("unknown xtables version %d" % - (_xtables_version)) + (xtables_version)) self._loaded_exts = set() From 201657b60f2461bb5e9ae618b3aeb944188e15d2 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 28 Feb 2015 21:42:40 +0100 Subject: [PATCH 149/287] Check xtables_version for CT test. --- iptc/test/test_targets.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/iptc/test/test_targets.py b/iptc/test/test_targets.py index a28f655..2262dff 100755 --- a/iptc/test/test_targets.py +++ b/iptc/test/test_targets.py @@ -2,6 +2,7 @@ import unittest import iptc +from iptc.xtables import xtables_version is_table_available = iptc.is_table_available @@ -420,7 +421,7 @@ def suite(): suites.extend([suite_target, suite_cluster, suite_tos]) if is_table_available(iptc.Table.NAT): suites.extend([suite_redir, suite_masq, suite_dnat]) - if is_table_available(iptc.Table.RAW): + if is_table_available(iptc.Table.RAW) and xtables_version >= 10: suites.extend([suite_notrack, suite_ct]) return unittest.TestSuite(suites) From b1b0f3bc46cedeb8ad6154bb1f1e659f8f8f9289 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 28 Feb 2015 22:36:19 +0100 Subject: [PATCH 150/287] Add info on custom iptables install to docs. --- README.md | 23 +++++++++++++++++++++++ doc/intro.rst | 23 +++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/README.md b/README.md index 244dfd1..1f4fc28 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,29 @@ package can be imported: Of course you need to be root to be able to use iptables. +Using a custom iptables install +------------------------------- + +If you are stuck on a system with an old version of `iptables`, you can +install a more up to date version to a custom location, and ask +`python-iptables` to use libraries at that location. + +To install `iptables` to `/tmp/iptables`: + + % git clone git://git.netfilter.org/iptables && cd iptables + % ./autogen.sh + % ./configure --prefix=/tmp/iptables + % make + % make install + +Make sure the dependencies `iptables` needs are installed. + +Now you can point `python-iptables` to this install path via: + + % sudo PATH=$PATH IPTABLES_LIBDIR=/tmp/iptables/lib XTABLES_LIBDIR=/tmp/iptables/lib/xtables python + >>> import iptc + >>> + What is supported ----------------- diff --git a/doc/intro.rst b/doc/intro.rst index b54b26f..eab00f2 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -104,6 +104,29 @@ package can be imported:: Of course you need to be root to be able to use iptables. +Using a custom iptables install +------------------------------- + +If you are stuck on a system with an old version of ``iptables``, you can +install a more up to date version to a custom location, and ask +``python-iptables`` to use libraries at that location. + +To install ``iptables`` to ``/tmp/iptables``:: + + % git clone git://git.netfilter.org/iptables && cd iptables + % ./autogen.sh + % ./configure --prefix=/tmp/iptables + % make + % make install + +Make sure the dependencies ``iptables`` needs are installed. + +Now you can point ``python-iptables`` to this install path via:: + + % sudo PATH=$PATH IPTABLES_LIBDIR=/tmp/iptables/lib XTABLES_LIBDIR=/tmp/iptables/lib/xtables python + >>> import iptc + >>> + What is supported ----------------- From 04fce7dfdef03b22e08dbb5626cbe9234d3688f5 Mon Sep 17 00:00:00 2001 From: Patrick McLean Date: Thu, 12 Mar 2015 18:44:20 -0700 Subject: [PATCH 151/287] Don't try to load kernel modules unless they are supported --- iptc/util.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/iptc/util.py b/iptc/util.py index 00fe20e..a587e7e 100644 --- a/iptc/util.py +++ b/iptc/util.py @@ -23,6 +23,10 @@ def _insert_ko(modprobe, modname): def _load_ko(modname): + # only try to load modules on kernels that support them + if not os.path.exists("/proc/modules"): + return (0, None) + # this will return the full path for the modprobe binary modprobe = "/sbin/modprobe" try: From 9db40fe89b5598a75f7f56885934f8a841dbc5b4 Mon Sep 17 00:00:00 2001 From: matteo brancaleoni Date: Thu, 19 Mar 2015 11:24:08 +0100 Subject: [PATCH 152/287] Add a set_parameter method to explicitly set Match or Target parameters, useful to set special params like "name" which are protected in iptc classes. --- iptc/ip4tc.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 45d047c..b733ba8 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -262,6 +262,21 @@ def __init__(self): self._ptrptr = None raise NotImplementedError() + def set_parameter(self, parameter, value=None): + """ + Set a parameter for target or match extension, with an optional value. + + @param parameter: name of the parameter to set + @type parameter: C{str} + + @param value: optional value of the parameter to set, defaults to C{None} + @type value: C{str} or a C{list} of C{str} + """ + if value is None: + value = "" + + return self.parse(parameter.replace("_", "-"), value) + def parse(self, parameter, value): # Parameter name must always be a string. parameter = parameter.encode() From 3ebe9d9d573bdfa9fc9b761e6cad1c9a7bac4cd2 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 22 Mar 2015 19:14:49 +0100 Subject: [PATCH 153/287] Make sure xt_params is set up after `import iptc`. This fixes #145. --- iptc/ip4tc.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 45d047c..4ec21c1 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -32,6 +32,9 @@ _free.restype = None _free.argtypes = [ct.POINTER(ct.c_ubyte)] +# Make sure xt_params is set up. +xtables(NFPROTO_IPV4) + def is_table_available(name): try: From 1488a4131023755358882a0112e0c6899119f06b Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 22 Mar 2015 19:52:10 +0100 Subject: [PATCH 154/287] Remove pyflakes warnings in ip4tc.py. --- iptc/ip4tc.py | 83 +++++++++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 36 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 45d047c..7ce0e6b 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -217,9 +217,9 @@ class iptc(object): # Check the packet `e' on chain `chain'. Returns the verdict, or # NULL and sets errno. - #iptc_check_packet = _libiptc.iptc_check_packet - #iptc_check_packet.restype = ct.c_char_p - #iptc_check_packet.argstype = [ct.c_char_p, ct.POINTER(ipt), ct.c_void_p] + # iptc_check_packet = _libiptc.iptc_check_packet + # iptc_check_packet.restype = ct.c_char_p + # iptc_check_packet.argstype = [ct.c_char_p, ct.POINTER(ipt), ct.c_void_p] # Get the number of references to this chain iptc_get_references = _libiptc.iptc_get_references @@ -537,13 +537,15 @@ def __eq__(self, match): self.match.u.user.name == match.match.u.user.name and self.match.u.user.revision == match.match.u.user.revision and self.match_buf[basesz:self.usersize] == - match.match_buf[basesz:match.usersize]): + match.match_buf[basesz:match.usersize]): return True return False def __hash__(self): - basesz = ct.sizeof(xt_entry_match) - return hash(self.match.u.match_size) ^ hash(self.match.u.user.name) ^ hash(self.match.u.user.revision) ^ hash(bytes(self.match_buf)) + return (hash(self.match.u.match_size) ^ + hash(self.match.u.user.name) ^ + hash(self.match.u.user.revision) ^ + hash(bytes(self.match_buf))) def __ne__(self, match): return not self.__eq__(match) @@ -677,19 +679,19 @@ def __init__(self, rule, name=None, target=None, revision=None): def __eq__(self, targ): basesz = ct.sizeof(xt_entry_target) if (self.target.u.target_size != targ.target.u.target_size or - self.target.u.user.name != targ.target.u.user.name or - self.target.u.user.revision != targ.target.u.user.revision): + self.target.u.user.name != targ.target.u.user.name or + self.target.u.user.revision != targ.target.u.user.revision): return False if (self.target.u.user.name == b"" or - self.target.u.user.name == b"standard" or - self.target.u.user.name == b"ACCEPT" or - self.target.u.user.name == b"DROP" or - self.target.u.user.name == b"RETURN" or - self.target.u.user.name == b"ERROR" or - self._is_standard_target()): + self.target.u.user.name == b"standard" or + self.target.u.user.name == b"ACCEPT" or + self.target.u.user.name == b"DROP" or + self.target.u.user.name == b"RETURN" or + self.target.u.user.name == b"ERROR" or + self._is_standard_target()): return True if (self._target_buf[basesz:self.usersize] == - targ._target_buf[basesz:targ.usersize]): + targ._target_buf[basesz:targ.usersize]): return True return False @@ -885,10 +887,10 @@ def __eq__(self, rule): self._matches]): return False if (self.src == rule.src and self.dst == rule.dst and - self.protocol == rule.protocol and - self.fragment == rule.fragment and - self.in_interface == rule.in_interface and - self.out_interface == rule.out_interface): + self.protocol == rule.protocol and + self.fragment == rule.fragment and + self.in_interface == rule.in_interface and + self.out_interface == rule.out_interface): return True return False @@ -1102,10 +1104,11 @@ def set_in_interface(self, intf): intf = intf[:-1] masklen -= 2 - self.entry.ip.iniface = b"".join([intf.encode(), b'\x00' * (_IFNAMSIZ - - len(intf))]) - self.entry.ip.iniface_mask = b"".join([b'\xff' * masklen, b'\x00' * - (_IFNAMSIZ - masklen)]) + self.entry.ip.iniface = b"".join([intf.encode(), + b'\x00' * (_IFNAMSIZ - len(intf))]) + self.entry.ip.iniface_mask = b"".join([b'\xff' * masklen, + b'\x00' * (_IFNAMSIZ - + masklen)]) in_interface = property(get_in_interface, set_in_interface) """This is the input network interface e.g. *eth0*. A wildcard match can @@ -1143,10 +1146,11 @@ def set_out_interface(self, intf): intf = intf[:-1] masklen -= 2 - self.entry.ip.outiface = b"".join([intf.encode(), b'\x00' * (_IFNAMSIZ - - len(intf))]) - self.entry.ip.outiface_mask = b"".join([b'\xff' * masklen, b'\x00' * - (_IFNAMSIZ - masklen)]) + self.entry.ip.outiface = b"".join([intf.encode(), + b'\x00' * (_IFNAMSIZ - len(intf))]) + self.entry.ip.outiface_mask = b"".join([b'\xff' * masklen, + b'\x00' * (_IFNAMSIZ - + masklen)]) out_interface = property(get_out_interface, set_out_interface) """This is the output network interface e.g. *eth0*. A wildcard match can @@ -1252,7 +1256,7 @@ def _set_rule(self, entry): entrysz = self._entry_size() matchsz = entry.target_offset - entrysz - #targetsz = entry.next_offset - entry.target_offset + # targetsz = entry.next_offset - entry.target_offset # iterate over matches to create blob if matchsz: @@ -1385,7 +1389,8 @@ def insert_rule(self, rule, position=0): self.table.insert_entry(self.name, rbuf, position) def replace_rule(self, rule, position=0): - """Replace existing rule in the chain at *position* with given *rule*""" + """Replace existing rule in the chain at *position* with given + *rule*""" rbuf = rule.rule if not rbuf: raise ValueError("invalid rule") @@ -1577,7 +1582,8 @@ def rename_chain(self, chain, new_name): """Rename chain *chain* to *new_name*.""" if isinstance(chain, Chain): chain = chain.name - rv = self._iptc.iptc_rename_chain(chain.encode(), new_name.encode(), self._handle) + rv = self._iptc.iptc_rename_chain(chain.encode(), new_name.encode(), + self._handle) if rv != 1: raise IPTCError("can't rename chain %s: %s" % (chain, self.strerror())) @@ -1617,7 +1623,8 @@ def set_policy(self, chain, policy, counters=None): cntrs = ct.pointer(cntrs) else: cntrs = None - rv = self._iptc.iptc_set_policy(chain.encode(), policy.encode(), cntrs, self._handle) + rv = self._iptc.iptc_set_policy(chain.encode(), policy.encode(), + cntrs, self._handle) if rv != 1: raise IPTCError("can't set policy %s on chain %s: %s)" % (policy, chain, self.strerror())) @@ -1640,7 +1647,8 @@ def get_policy(self, chain): @autocommit def append_entry(self, chain, entry): """Appends rule *entry* to *chain*.""" - rv = self._iptc.iptc_append_entry(chain.encode(), ct.cast(entry, ct.c_void_p), + rv = self._iptc.iptc_append_entry(chain.encode(), + ct.cast(entry, ct.c_void_p), self._handle) if rv != 1: raise IPTCError("can't append entry to chain %s: %s)" % @@ -1649,7 +1657,8 @@ def append_entry(self, chain, entry): @autocommit def insert_entry(self, chain, entry, position): """Inserts rule *entry* into *chain* at position *position*.""" - rv = self._iptc.iptc_insert_entry(chain.encode(), ct.cast(entry, ct.c_void_p), + rv = self._iptc.iptc_insert_entry(chain.encode(), + ct.cast(entry, ct.c_void_p), position, self._handle) if rv != 1: raise IPTCError("can't insert entry into chain %s: %s)" % @@ -1658,8 +1667,9 @@ def insert_entry(self, chain, entry, position): @autocommit def replace_entry(self, chain, entry, position): """Replace existing rule in *chain* at *position* with given *rule*.""" - rv = self._iptc.iptc_replace_entry(chain.encode(), ct.cast(entry, ct.c_void_p), - position, self._handle) + rv = self._iptc.iptc_replace_entry(chain.encode(), + ct.cast(entry, ct.c_void_p), + position, self._handle) if rv != 1: raise IPTCError("can't replace entry in chain %s: %s)" % (chain, self.strerror())) @@ -1667,7 +1677,8 @@ def replace_entry(self, chain, entry, position): @autocommit def delete_entry(self, chain, entry, mask): """Removes rule *entry* with *mask* from *chain*.""" - rv = self._iptc.iptc_delete_entry(chain.encode(), ct.cast(entry, ct.c_void_p), + rv = self._iptc.iptc_delete_entry(chain.encode(), + ct.cast(entry, ct.c_void_p), mask, self._handle) if rv != 1: raise IPTCError("can't delete entry from chain %s: %s)" % From 2e65c4ab0b4a16593bafc3f399ec499da88e93d1 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 22 Mar 2015 19:55:38 +0100 Subject: [PATCH 155/287] Remove pyflakes warnings in ip6tc.py. --- iptc/ip6tc.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/iptc/ip6tc.py b/iptc/ip6tc.py index c186845..0f9906f 100644 --- a/iptc/ip6tc.py +++ b/iptc/ip6tc.py @@ -2,7 +2,6 @@ import ctypes as ct import socket -import weakref from .ip4tc import Rule, Table, IPTCError from .util import find_library, load_kernel @@ -196,9 +195,9 @@ class ip6tc(object): # Check the packet `e' on chain `chain'. Returns the verdict, or # NULL and sets errno. - #iptc_check_packet = _libiptc.ip6tc_check_packet - #iptc_check_packet.restype = ct.c_char_p - #iptc_check_packet.argstype = [ct.c_char_p, ct.POINTER(ipt), ct.c_void_p] + # iptc_check_packet = _libiptc.ip6tc_check_packet + # iptc_check_packet.restype = ct.c_char_p + # iptc_check_packet.argstype = [ct.c_char_p, ct.POINTER(ipt), ct.c_void_p] # Get the number of references to this chain iptc_get_references = _libiptc.ip6tc_get_references @@ -246,9 +245,9 @@ def __eq__(self, rule): if x in self._matches]): return False if (self.src == rule.src and self.dst == rule.dst and - self.protocol == rule.protocol and - self.in_interface == rule.in_interface and - self.out_interface == rule.out_interface): + self.protocol == rule.protocol and + self.in_interface == rule.in_interface and + self.out_interface == rule.out_interface): return True return False @@ -445,10 +444,10 @@ def set_in_interface(self, intf): intf = intf[:-1] masklen -= 2 - self.entry.ipv6.iniface = ("".join([intf, '\x00' * (_IFNAMSIZ - - len(intf))])).encode() - self.entry.ipv6.iniface_mask = ("".join(['\x01' * masklen, '\x00' * - (_IFNAMSIZ - masklen)])).encode() + self.entry.ipv6.iniface = ("".join( + [intf, '\x00' * (_IFNAMSIZ - len(intf))])).encode() + self.entry.ipv6.iniface_mask = ("".join( + ['\x01' * masklen, '\x00' * (_IFNAMSIZ - masklen)])).encode() in_interface = property(get_in_interface, set_in_interface) """This is the input network interface e.g. *eth0*. A wildcard match can @@ -490,10 +489,10 @@ def set_out_interface(self, intf): intf = intf[:-1] masklen -= 2 - self.entry.ipv6.outiface = ("".join([intf, '\x00' * (_IFNAMSIZ - - len(intf))])).encode() - self.entry.ipv6.outiface_mask = ("".join(['\x01' * masklen, '\x00' * - (_IFNAMSIZ - masklen)])).encode() + self.entry.ipv6.outiface = ("".join( + [intf, '\x00' * (_IFNAMSIZ - len(intf))])).encode() + self.entry.ipv6.outiface_mask = ("".join( + ['\x01' * masklen, '\x00' * (_IFNAMSIZ - masklen)])).encode() out_interface = property(get_out_interface, set_out_interface) """This is the output network interface e.g. *eth0*. A wildcard match can From 072baae726f2f6642c05f04cea6a47e16d733424 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 22 Mar 2015 19:55:48 +0100 Subject: [PATCH 156/287] Remove pyflakes warnings in xtables.py. --- iptc/xtables.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index da481aa..ce58d7b 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -23,6 +23,7 @@ XTF_TRY_LOAD = 0x02 XTF_LOAD_MUST_SUCCEED = 0x03 + XTOPT_INVERT = 1 << 0 XTOPT_MAND = 1 << 1 XTOPT_MULTI = 1 << 2 @@ -386,7 +387,8 @@ class _xtables_match_v10(ct.Structure): ("save", ct.CFUNCTYPE(None, ct.c_void_p, ct.POINTER(xt_entry_match))), # Print match name or alias - ("alias", ct.CFUNCTYPE(ct.c_char_p, ct.POINTER(xt_entry_match))), + ("alias", ct.CFUNCTYPE(ct.c_char_p, + ct.POINTER(xt_entry_match))), # pointer to list of extra command-line options ("extra_opts", ct.POINTER(option)), @@ -635,7 +637,8 @@ class _xtables_target_v10(ct.Structure): ("save", ct.CFUNCTYPE(None, ct.c_void_p, ct.POINTER(xt_entry_target))), # Print target name or alias - ("alias", ct.CFUNCTYPE(ct.c_char_p, ct.POINTER(xt_entry_target))), + ("alias", ct.CFUNCTYPE(ct.c_char_p, + ct.POINTER(xt_entry_target))), # pointer to list of extra command-line options ("extra_opts", ct.POINTER(option)), @@ -762,14 +765,14 @@ class xtables(object): _xtables_xt_params = ct.c_void_p.in_dll(_lib_xtables, "xt_params") _xtables_matches = (ct.c_void_p.in_dll(_lib_xtables, "xtables_matches")) try: - _xtables_pending_matches = (ct.c_void_p.in_dll(_lib_xtables, - "xtables_pending_matches")) + _xtables_pending_matches = (ct.c_void_p.in_dll( + _lib_xtables, "xtables_pending_matches")) except ValueError: _xtables_pending_matches = ct.POINTER(None) _xtables_targets = (ct.c_void_p.in_dll(_lib_xtables, "xtables_targets")) try: - _xtables_pending_targets = (ct.c_void_p.in_dll(_lib_xtables, - "xtables_pending_targets")) + _xtables_pending_targets = (ct.c_void_p.in_dll( + _lib_xtables, "xtables_pending_targets")) except ValueError: _xtables_pending_targets = ct.POINTER(None) From f782efaaf61fcbb2282e0b8c54255ef0b7af0c7d Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 22 Mar 2015 19:57:15 +0100 Subject: [PATCH 157/287] Remove pyflakes warnings from test_iptc.py. --- iptc/test/test_iptc.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/iptc/test/test_iptc.py b/iptc/test/test_iptc.py index 56e7e50..03597bc 100755 --- a/iptc/test/test_iptc.py +++ b/iptc/test/test_iptc.py @@ -111,9 +111,9 @@ def test_refresh(self): def test_flush_user_chains(self): chain1 = iptc.Chain(iptc.Table(iptc.Table.FILTER), - "iptc_test_flush_chain1") + "iptc_test_flush_chain1") chain2 = iptc.Chain(iptc.Table(iptc.Table.FILTER), - "iptc_test_flush_chain2") + "iptc_test_flush_chain2") iptc.Table(iptc.Table.FILTER).create_chain(chain1) iptc.Table(iptc.Table.FILTER).create_chain(chain2) @@ -143,7 +143,8 @@ def test_flush_builtin(self): iptc.Chain(filter_table, "OUTPUT").append_rule(rule) - self.assertEquals(len(iptc.Chain(filter_table, "OUTPUT").rules), output_rule_count + 1) + self.assertEquals(len(iptc.Chain(filter_table, "OUTPUT").rules), + output_rule_count + 1) filter_table.flush() @@ -581,12 +582,12 @@ def test_rule_address(self): # valid addresses rule = iptc.Rule() for addr in [("127.0.0.1/255.255.255.0", "127.0.0.1/255.255.255.0"), - ("!127.0.0.1/255.255.255.0", "!127.0.0.1/255.255.255.0"), - ("127.0.0.1/255.255.128.0", "127.0.0.1/255.255.128.0"), - ("127.0.0.1/16", "127.0.0.1/255.255.0.0"), - ("127.0.0.1/24", "127.0.0.1/255.255.255.0"), - ("127.0.0.1/17", "127.0.0.1/255.255.128.0"), - ("!127.0.0.1/17", "!127.0.0.1/255.255.128.0")]: + ("!127.0.0.1/255.255.255.0", "!127.0.0.1/255.255.255.0"), + ("127.0.0.1/255.255.128.0", "127.0.0.1/255.255.128.0"), + ("127.0.0.1/16", "127.0.0.1/255.255.0.0"), + ("127.0.0.1/24", "127.0.0.1/255.255.255.0"), + ("127.0.0.1/17", "127.0.0.1/255.255.128.0"), + ("!127.0.0.1/17", "!127.0.0.1/255.255.128.0")]: rule.src = addr[0] self.assertEquals(rule.src, addr[1]) rule.dst = addr[0] @@ -614,7 +615,6 @@ def test_rule_address(self): else: self.fail("rule accepted invalid address %s" % (addr)) - def test_rule_interface(self): # valid interfaces rule = iptc.Rule() From e4a0ad3c7081081d6eac264c6ae2082a21d9b7e5 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Mon, 25 May 2015 15:53:22 +0200 Subject: [PATCH 158/287] Release 0.8.0. --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index a00f4fb..7e90c7a 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.8.0-dev" +__version__ = "0.8.0" From 80f052c06b6faf42235f03fcc9fad5e7c76a263f Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Mon, 25 May 2015 16:00:53 +0200 Subject: [PATCH 159/287] Start 0.9.0-dev. --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index 7e90c7a..7969d4c 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.8.0" +__version__ = "0.9.0-dev" From ee5cca728fe1989ebc6be2ef2a388b326392204e Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 21 Jun 2015 23:48:34 +0200 Subject: [PATCH 160/287] Move _xt_globals to module scope. --- iptc/xtables.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index ce58d7b..7d4cbee 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -745,6 +745,15 @@ def new(*args): return new +_xt_globals = xtables_globals() +_xt_globals.option_offset = 0 +_xt_globals.program_name = version.__pkgname__.encode() +_xt_globals.program_version = version.__version__.encode() +_xt_globals.orig_opts = None +_xt_globals.opts = None +_xt_globals.exit_err = _xt_exit + + class xtables(object): _xtables_init_all = _lib_xtables.xtables_init_all _xtables_init_all.restype = ct.c_int @@ -794,13 +803,6 @@ def __new__(cls, proto): def _xtinit(self, proto, no_alias_check=False): self.proto = proto self.no_alias_check = no_alias_check - self._xt_globals = xtables_globals() - self._xt_globals.option_offset = 0 - self._xt_globals.program_name = version.__pkgname__.encode() - self._xt_globals.program_version = version.__version__.encode() - self._xt_globals.orig_opts = None - self._xt_globals.opts = None - self._xt_globals.exit_err = _xt_exit thismodule = sys.modules[__name__] matchname = "_xtables_match_v%d" % (xtables_version) @@ -821,7 +823,7 @@ def _xtinit(self, proto, no_alias_check=False): self._targets = ct.c_void_p(None).value self._pending_targets = ct.c_void_p(None).value - rv = xtables._xtables_init_all(ct.pointer(self._xt_globals), proto) + rv = xtables._xtables_init_all(ct.pointer(_xt_globals), proto) if rv: raise XTablesError("xtables_init_all() failed: %d" % (rv)) self._save_globals() From 7f52d2cf1c8b3f711f486d81176d629ad7ee087d Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 21 Jun 2015 23:50:55 +0200 Subject: [PATCH 161/287] Use module level extension cache. --- iptc/xtables.py | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index 7d4cbee..fa756e1 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -754,6 +754,9 @@ def new(*args): _xt_globals.exit_err = _xt_exit +_loaded_exts = {} + + class xtables(object): _xtables_init_all = _lib_xtables.xtables_init_all _xtables_init_all.restype = ct.c_int @@ -814,8 +817,6 @@ def _xtinit(self, proto, no_alias_check=False): raise XTablesError("unknown xtables version %d" % (xtables_version)) - self._loaded_exts = set() - # make sure we're initializing with clean state self._xt_params = ct.c_void_p(None).value self._matches = ct.c_void_p(None).value @@ -861,14 +862,8 @@ def _check_extname(self, name): name = b"standard" return name - def _loaded(self, name): - self._loaded_exts.add(name) - - def _is_loaded(self, name): - if name in self._loaded_exts: - return True - else: - return False + def _loaded(self, name, ext): + _loaded_exts['%s___%s' % (self.proto, name)] = ext def _get_initfn_from_lib(self, name, lib): try: @@ -903,8 +898,6 @@ def _get_prefix(self): raise XTablesError("Unknown protocol %d" % (self.proto)) def _try_register(self, name): - if self._is_loaded(name): - return if isinstance(name, bytes): name = name.decode() if self._try_extinit(name, _lib_xtables): @@ -916,11 +909,20 @@ def _try_register(self, name): if self._try_extinit(name, lib): return + def _get_loaded_ext(self, name): + ext = _loaded_exts.get('%s___%s' % (self.proto, name), None) + return ext + @preserve_globals def find_match(self, name): if isinstance(name, str): name = name.encode() name = self._check_extname(name) + + ext = self._get_loaded_ext(name) + if ext is not None: + return ext + match = xtables._xtables_find_match(name, XTF_TRY_LOAD, None) if not match: self._try_register(name) @@ -929,7 +931,7 @@ def find_match(self, name): return match m = ct.cast(match, ct.POINTER(self._match_struct)) - self._loaded(m[0].name) + self._loaded(m[0].name, m) return m @preserve_globals @@ -937,6 +939,11 @@ def find_target(self, name): if isinstance(name, str): name = name.encode() name = self._check_extname(name) + + ext = self._get_loaded_ext(name) + if ext is not None: + return ext + target = xtables._xtables_find_target(name, XTF_TRY_LOAD) if not target: self._try_register(name) @@ -945,7 +952,7 @@ def find_target(self, name): return target t = ct.cast(target, ct.POINTER(self._target_struct)) - self._loaded(t[0].name) + self._loaded(t[0].name, t) return t @preserve_globals From f210f1416782a43304b2694af972e6ae77dada35 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 21 Jun 2015 23:51:28 +0200 Subject: [PATCH 162/287] Set xtables_{matches,targets} to NULL. When trying to find a match or target, explicitly set `xtables_matches` and `xtables_targets` to NULL. At the same time, cache matches and targets we have found. This way we can use protocol-independent extensions for both IPv4 and IPv6. When a match or target is not in the cache, libxtables will find it, and it will be added to the cache. This fixes #150. --- iptc/xtables.py | 65 ++++++++++++------------------------------------- 1 file changed, 15 insertions(+), 50 deletions(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index fa756e1..fe046ef 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -731,17 +731,11 @@ def _xt_exit(status, *args): _xt_exit = _EXIT_FN(_xt_exit) -def preserve_globals(fn): +def set_nfproto(fn): def new(*args): - obj = args[0] - obj._restore_globals() - try: - ret = fn(*args) - except Exception: - obj._save_globals() - raise - obj._save_globals() - return ret + xtobj = args[0] + xtables._xtables_set_nfproto(xtobj.proto) + return fn(*args) return new @@ -817,46 +811,13 @@ def _xtinit(self, proto, no_alias_check=False): raise XTablesError("unknown xtables version %d" % (xtables_version)) - # make sure we're initializing with clean state - self._xt_params = ct.c_void_p(None).value - self._matches = ct.c_void_p(None).value - self._pending_matches = ct.c_void_p(None).value - self._targets = ct.c_void_p(None).value - self._pending_targets = ct.c_void_p(None).value - rv = xtables._xtables_init_all(ct.pointer(_xt_globals), proto) if rv: raise XTablesError("xtables_init_all() failed: %d" % (rv)) - self._save_globals() def __repr__(self): return "XTables for protocol %d" % (self.proto) - def _save_globals(self): - # Save our per-protocol libxtables global variables, and set them to - # NULL so that we don't interfere with other protocols. - null = ct.c_void_p(None) - self._xt_params = xtables._xtables_xt_params.value - xtables._xtables_xt_params.value = null.value - self._matches = xtables._xtables_matches.value - xtables._xtables_matches.value = null.value - self._pending_matches = xtables._xtables_pending_matches.value - xtables._xtables_pending_matches.value = null.value - self._targets = xtables._xtables_targets.value - xtables._xtables_targets.value = null.value - self._pending_targets = xtables._xtables_pending_targets.value - xtables._xtables_pending_targets.value = null.value - - def _restore_globals(self): - # Restore per-protocol libxtables global variables saved in - # _save_globals(). - xtables._xtables_set_nfproto(self.proto) - xtables._xtables_xt_params.value = self._xt_params - xtables._xtables_matches.value = self._matches - xtables._xtables_pending_matches.value = self._pending_matches - xtables._xtables_targets.value = self._targets - xtables._xtables_pending_targets.value = self._pending_targets - def _check_extname(self, name): if name in [b"", b"ACCEPT", b"DROP", b"QUEUE", b"RETURN"]: name = b"standard" @@ -913,7 +874,7 @@ def _get_loaded_ext(self, name): ext = _loaded_exts.get('%s___%s' % (self.proto, name), None) return ext - @preserve_globals + @set_nfproto def find_match(self, name): if isinstance(name, str): name = name.encode() @@ -923,6 +884,8 @@ def find_match(self, name): if ext is not None: return ext + xtables._xtables_matches.value = ct.c_void_p(None).value + match = xtables._xtables_find_match(name, XTF_TRY_LOAD, None) if not match: self._try_register(name) @@ -934,7 +897,7 @@ def find_match(self, name): self._loaded(m[0].name, m) return m - @preserve_globals + @set_nfproto def find_target(self, name): if isinstance(name, str): name = name.encode() @@ -944,6 +907,8 @@ def find_target(self, name): if ext is not None: return ext + xtables._xtables_targets.value = ct.c_void_p(None).value + target = xtables._xtables_find_target(name, XTF_TRY_LOAD) if not target: self._try_register(name) @@ -955,7 +920,7 @@ def find_target(self, name): self._loaded(t[0].name, t) return t - @preserve_globals + @set_nfproto def save(self, module, ip, ptr): _wrap_save(module.save, ct.cast(ct.pointer(ip), ct.c_void_p), ptr) @@ -981,7 +946,7 @@ def _parse(self, module, argv, inv, flags, entry, ptr): # Dispatch arguments to the appropriate parse function, based upon the # extension's choice of API. - @preserve_globals + @set_nfproto def parse_target(self, argv, invert, t, fw, ptr, x6_parse, x6_options): _optarg.value = len(argv) > 1 and argv[1] or None _optind.value = len(argv) - 1 @@ -1026,7 +991,7 @@ def parse_target(self, argv, invert, t, fw, ptr, x6_parse, x6_options): # Dispatch arguments to the appropriate parse function, based upon the # extension's choice of API. - @preserve_globals + @set_nfproto def parse_match(self, argv, invert, m, fw, ptr, x6_parse, x6_options): _optarg.value = len(argv) > 1 and argv[1] or None _optind.value = len(argv) - 1 @@ -1107,7 +1072,7 @@ def _fcheck_target_new(self, target): # Dispatch arguments to the appropriate final_check function, based upon # the extension's choice of API. - @preserve_globals + @set_nfproto def final_check_target(self, target): x6_fcheck = None try: @@ -1147,7 +1112,7 @@ def _fcheck_match_new(self, match): # Dispatch arguments to the appropriate final_check function, based upon # the extension's choice of API. - @preserve_globals + @set_nfproto def final_check_match(self, match): x6_fcheck = None try: From 32e7be45fd2761f517287904bb5d5bffa0862d20 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Tue, 30 Jun 2015 10:29:41 +0200 Subject: [PATCH 163/287] Set pending matches/targets to NULL. Before looking up a match/target via libxtables, set pending match/target list head to NULL. This fixes #154. --- iptc/xtables.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/iptc/xtables.py b/iptc/xtables.py index fe046ef..d21e62d 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -885,6 +885,8 @@ def find_match(self, name): return ext xtables._xtables_matches.value = ct.c_void_p(None).value + if xtables._xtables_pending_matches: + xtables._xtables_pending_matches.value = ct.c_void_p(None).value match = xtables._xtables_find_match(name, XTF_TRY_LOAD, None) if not match: @@ -908,6 +910,8 @@ def find_target(self, name): return ext xtables._xtables_targets.value = ct.c_void_p(None).value + if xtables._xtables_pending_targets: + xtables._xtables_pending_targets.value = ct.c_void_p(None).value target = xtables._xtables_find_target(name, XTF_TRY_LOAD) if not target: From 942a50dbb0e21800455d9a4853f08a1f4eecd641 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 4 Jul 2015 12:44:07 +0200 Subject: [PATCH 164/287] Fix #153. --- iptc/ip4tc.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 7e1479e..9a4efd0 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -546,9 +546,21 @@ def __init__(self, rule, name=None, match=None, revision=None): if match: ct.memmove(ct.byref(self._match_buf), ct.byref(match), self.size) self._update_pointers() + alias = getattr(self._module, 'alias', None) + if alias: + self._check_alias(alias) else: self.reset() + def _check_alias(self, alias): + name = self._module.alias(self._ptr).decode() + alias_module = self._xt.find_match(name) + if alias_module is None: + return + self._alias_module = alias_module[0] + self._orig_parse = getattr(self._alias_module, 'x6_parse', None) + self._orig_options = getattr(self._alias_module, 'x6_options', None) + def __eq__(self, match): basesz = ct.sizeof(xt_entry_match) if (self.match.u.match_size == match.match.u.match_size and From 1f5a1e5a69123da26ebb47704a4cce05e40853d1 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 4 Jul 2015 12:49:41 +0200 Subject: [PATCH 165/287] Move full logic to _check_alias(). --- iptc/ip4tc.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 9a4efd0..41239fd 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -546,13 +546,14 @@ def __init__(self, rule, name=None, match=None, revision=None): if match: ct.memmove(ct.byref(self._match_buf), ct.byref(match), self.size) self._update_pointers() - alias = getattr(self._module, 'alias', None) - if alias: - self._check_alias(alias) + self._check_alias() else: self.reset() - def _check_alias(self, alias): + def _check_alias(self): + alias = getattr(self._module, 'alias', None) + if not alias: + return name = self._module.alias(self._ptr).decode() alias_module = self._xt.find_match(name) if alias_module is None: From 2fe7a57e3fc9b70d0c7728cadf466063037a2c48 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 4 Jul 2015 12:49:58 +0200 Subject: [PATCH 166/287] Implement _check_alias() for targets too. --- iptc/ip4tc.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 41239fd..d6dddaa 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -706,6 +706,20 @@ def __init__(self, rule, name=None, target=None, revision=None): if self._is_standard_target(): self.standard_target = name + elif target: + self._check_alias() + + def _check_alias(self): + alias = getattr(self._module, 'alias', None) + if not alias: + return + name = self._module.alias(self._ptr).decode() + alias_module = self._xt.find_target(name) + if alias_module is None: + return + self._alias_module = alias_module[0] + self._orig_parse = getattr(self._alias_module, 'x6_parse', None) + self._orig_options = getattr(self._alias_module, 'x6_options', None) def __eq__(self, targ): basesz = ct.sizeof(xt_entry_target) From 61a3fd59adf121c71a43d3e5d40bf3762a4a30fe Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 4 Jul 2015 13:04:51 +0200 Subject: [PATCH 167/287] Return alias name if present. --- iptc/ip4tc.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index d6dddaa..0495ae6 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -438,6 +438,14 @@ def _update_parameters(self): for k, v in params: self.__setattr__(k, v) + def _get_alias_name(self): + if not self._module or not self._ptr: + return None + alias = getattr(self._module, 'alias', None) + if not alias: + return None + return self._module.alias(self._ptr).decode() + def __setattr__(self, name, value): if not name.startswith('_') and name not in dir(self): self.parse(name.replace("_", "-"), value) @@ -456,7 +464,8 @@ def _get_parameters(self): contain those set by the module by default too.""" def _get_name(self): - return self._name + alias = self._get_alias_name() + return alias and alias or self._name name = property(_get_name) """Name of this target or match.""" From 54a392c565b8a4537d52822ac39106270e08f955 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 4 Jul 2015 13:05:10 +0200 Subject: [PATCH 168/287] Use _get_alias_name() helper in _check_alias(). --- iptc/ip4tc.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 0495ae6..2d56d4b 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -560,10 +560,9 @@ def __init__(self, rule, name=None, match=None, revision=None): self.reset() def _check_alias(self): - alias = getattr(self._module, 'alias', None) - if not alias: + name = self._get_alias_name() + if name is None: return - name = self._module.alias(self._ptr).decode() alias_module = self._xt.find_match(name) if alias_module is None: return @@ -719,10 +718,9 @@ def __init__(self, rule, name=None, target=None, revision=None): self._check_alias() def _check_alias(self): - alias = getattr(self._module, 'alias', None) - if not alias: + name = self._get_alias_name() + if name is None: return - name = self._module.alias(self._ptr).decode() alias_module = self._xt.find_target(name) if alias_module is None: return From 143fffb24c1de3254bbb5bf50f6f8cac172e52f7 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 4 Jul 2015 13:05:40 +0200 Subject: [PATCH 169/287] Remove superfluous pass statements from test_matches.py. --- iptc/test/test_matches.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/iptc/test/test_matches.py b/iptc/test/test_matches.py index 0b55524..b99d9d0 100755 --- a/iptc/test/test_matches.py +++ b/iptc/test/test_matches.py @@ -366,7 +366,6 @@ def setUp(self): def tearDown(self): self.chain.flush() self.chain.delete() - pass def test_state(self): self.match.state = "RELATED,ESTABLISHED" @@ -400,7 +399,6 @@ def setUp(self): def tearDown(self): self.chain.flush() self.chain.delete() - pass def test_state(self): self.match.ctstate = "NEW,RELATED" From 88ef009e753457e08ee5154ff9c44a2e30f2ff87 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 4 Jul 2015 13:06:02 +0200 Subject: [PATCH 170/287] Test if removing rule succeeds in match teardowns. --- iptc/test/test_matches.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/iptc/test/test_matches.py b/iptc/test/test_matches.py index b99d9d0..1bf86dd 100755 --- a/iptc/test/test_matches.py +++ b/iptc/test/test_matches.py @@ -93,6 +93,8 @@ def setUp(self): iptc.Table(iptc.Table.FILTER).create_chain(self.chain) def tearDown(self): + for r in self.chain.rules: + self.chain.delete_rule(r) self.chain.flush() self.chain.delete() @@ -123,6 +125,8 @@ def setUp(self): iptc.Table(iptc.Table.FILTER).create_chain(self.chain) def tearDown(self): + for r in self.chain.rules: + self.chain.delete_rule(r) self.chain.flush() self.chain.delete() @@ -176,6 +180,8 @@ def setUp(self): iptc.Table(iptc.Table.FILTER).create_chain(self.chain) def tearDown(self): + for r in self.chain.rules: + self.chain.delete_rule(r) self.chain.flush() self.chain.delete() @@ -218,6 +224,8 @@ def setUp(self): iptc.Table(iptc.Table.FILTER).create_chain(self.chain) def tearDown(self): + for r in self.chain.rules: + self.chain.delete_rule(r) self.chain.flush() self.chain.delete() @@ -268,6 +276,8 @@ def setUp(self): self.table.create_chain(self.chain) def tearDown(self): + for r in self.chain.rules: + self.chain.delete_rule(r) self.chain.flush() self.chain.delete() @@ -290,6 +300,8 @@ def setUp(self): iptc.Table(iptc.Table.FILTER).create_chain(self.chain) def tearDown(self): + for r in self.chain.rules: + self.chain.delete_rule(r) self.chain.flush() self.chain.delete() @@ -314,6 +326,8 @@ def setUp(self): iptc.Table(iptc.Table.FILTER).create_chain(self.chain) def tearDown(self): + for r in self.chain.rules: + self.chain.delete_rule(r) self.chain.flush() self.chain.delete() @@ -364,6 +378,8 @@ def setUp(self): self.table.create_chain(self.chain) def tearDown(self): + for r in self.chain.rules: + self.chain.delete_rule(r) self.chain.flush() self.chain.delete() @@ -373,7 +389,7 @@ def test_state(self): self.chain.insert_rule(self.rule) rule = self.chain.rules[0] m = rule.matches[0] - self.assertTrue(m.name, ["state", "conntrack"]) + self.assertEquals(m.name, "state") self.assertEquals(m.state, "RELATED,ESTABLISHED") @@ -397,6 +413,8 @@ def setUp(self): self.table.create_chain(self.chain) def tearDown(self): + for r in self.chain.rules: + self.chain.delete_rule(r) self.chain.flush() self.chain.delete() @@ -430,6 +448,8 @@ def setUp(self): self.table.create_chain(self.chain) def tearDown(self): + for r in self.chain.rules: + self.chain.delete_rule(r) self.chain.flush() self.chain.delete() From 58b97458838cd4db40073674bbad4d30780cb1fc Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 4 Jul 2015 13:08:40 +0200 Subject: [PATCH 171/287] Test if removing rule succeeds in target teardowns. --- iptc/test/test_targets.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/iptc/test/test_targets.py b/iptc/test/test_targets.py index 2262dff..3587acc 100755 --- a/iptc/test/test_targets.py +++ b/iptc/test/test_targets.py @@ -79,6 +79,8 @@ def setUp(self): iptc.Table(iptc.Table.FILTER).create_chain(self.chain) def tearDown(self): + for r in self.chain.rules: + self.chain.delete_rule(r) self.chain.flush() self.chain.delete() @@ -133,6 +135,8 @@ def setUp(self): iptc.Table(iptc.Table.NAT).create_chain(self.chain) def tearDown(self): + for r in self.chain.rules: + self.chain.delete_rule(r) self.chain.flush() self.chain.delete() @@ -181,6 +185,8 @@ def setUp(self): iptc.Table(iptc.Table.MANGLE).create_chain(self.chain) def tearDown(self): + for r in self.chain.rules: + self.chain.delete_rule(r) self.chain.flush() self.chain.delete() @@ -262,6 +268,8 @@ def setUp(self): iptc.Table(iptc.Table.MANGLE).create_chain(self.chain) def tearDown(self): + for r in self.chain.rules: + self.chain.delete_rule(r) self.chain.flush() self.chain.delete() @@ -307,6 +315,8 @@ def setUp(self): iptc.Table(iptc.Table.NAT).create_chain(self.chain) def tearDown(self): + for r in self.chain.rules: + self.chain.delete_rule(r) self.chain.flush() self.chain.delete() @@ -363,6 +373,8 @@ def setUp(self): iptc.Table(iptc.Table.RAW).create_chain(self.chain) def tearDown(self): + for r in self.chain.rules: + self.chain.delete_rule(r) self.chain.flush() self.chain.delete() @@ -393,6 +405,8 @@ def setUp(self): iptc.Table(iptc.Table.RAW).create_chain(self.chain) def tearDown(self): + for r in self.chain.rules: + self.chain.delete_rule(r) self.chain.flush() self.chain.delete() From ac68a5c945a85c54dd9b6637c443279d8e19cd7b Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 11 Jul 2015 19:53:49 +0200 Subject: [PATCH 172/287] Release 0.9.0. --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index 7969d4c..6cadff1 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.9.0-dev" +__version__ = "0.9.0" From a29d91c856a583faf90ff775dc357c6b6dba03ee Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 11 Jul 2015 20:00:01 +0200 Subject: [PATCH 173/287] Start 0.10.0-dev. --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index 6cadff1..68a6b1d 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.9.0" +__version__ = "0.10.0-dev" From c3dcc54a86e676cddfc927566f1dfde44008391b Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 30 Aug 2015 01:23:31 +0200 Subject: [PATCH 174/287] Cosmetics: reduce line length --- iptc/ip4tc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 2d56d4b..9738a9c 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -272,7 +272,7 @@ def set_parameter(self, parameter, value=None): @param parameter: name of the parameter to set @type parameter: C{str} - @param value: optional value of the parameter to set, defaults to C{None} + @param value: optional value of the parameter, defaults to C{None} @type value: C{str} or a C{list} of C{str} """ if value is None: From 255eac75731fce92a7eaf419b95cadc0af4dae51 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 30 Aug 2015 11:03:18 +0200 Subject: [PATCH 175/287] Provide more info when invalid rule is used --- iptc/ip4tc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 9738a9c..5cca152 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -1304,7 +1304,8 @@ def _set_rule(self, entry): ct.POINTER(self._entry_type()))[0] if not isinstance(entry, self._entry_type()): - raise TypeError() + raise TypeError("Invalid rule type %s; expected %s" % + (entry, self._entry_type())) entrysz = self._entry_size() matchsz = entry.target_offset - entrysz From 78967a67250421e7f185a9879df57b20806e8e46 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 30 Aug 2015 21:19:09 +0200 Subject: [PATCH 176/287] Simplify Target._parse(). --- iptc/ip4tc.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 5cca152..62b2046 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -774,9 +774,6 @@ def _parse(self, argv, inv, entry): self._orig_parse, self._orig_options) self._target_buf = ct.cast(self._module.t, ct.POINTER(ct.c_ubyte)) if self._buffer.buffer != self._target_buf: - if self._buffer.buffer is not None: - self._buffer.buffer = None # Buffer was freed by iptables. - self._buffer = _Buffer() self._buffer.buffer = self._target_buf self._update_pointers() From 03f894ad4482afcb084f1a10c871cffc96506214 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 30 Aug 2015 21:20:52 +0200 Subject: [PATCH 177/287] Fix #157 --- iptc/ip4tc.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 62b2046..a8400de 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -548,8 +548,6 @@ def __init__(self, rule, name=None, match=None, revision=None): self._revision = revision else: self._revision = self._module.revision - if self._module.next is not None: - self._store_buffer(module) self._match_buf = (ct.c_ubyte * self.size)() if match: @@ -589,10 +587,6 @@ def __hash__(self): def __ne__(self, match): return not self.__eq__(match) - def _store_buffer(self, module): - self._buffer = _Buffer() - self._buffer.buffer = ct.cast(module, ct.POINTER(ct.c_ubyte)) - def _final_check(self): self._xt.final_check_match(self._module) From 4f4dea955dcfd754ace1718b290cdf9493647d75 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 30 Aug 2015 21:42:23 +0200 Subject: [PATCH 178/287] Use set_parameter() instead of __setattr__() In IPTCModule, certain parameters may overlap with Target/Match attribute or method names. Use set_parameter() to work around this problem. This fixes #158. --- iptc/ip4tc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index a8400de..3b6ee8a 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -436,7 +436,7 @@ def _update_parameters(self): params = self.get_all_parameters().items() self.reset() for k, v in params: - self.__setattr__(k, v) + self.set_parameter(k, v) def _get_alias_name(self): if not self._module or not self._ptr: From 69242efe794321351d5b2e9b550f78a7773569c9 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 31 Oct 2015 22:59:32 -0700 Subject: [PATCH 179/287] Release v0.10.0 --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index 68a6b1d..1050830 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.10.0-dev" +__version__ = "0.10.0" From e3de898a3667b8170849956b428b612b6f9d11f9 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 31 Oct 2015 23:02:38 -0700 Subject: [PATCH 180/287] Rebuild README.md --- README.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 1f4fc28..66e128c 100644 --- a/README.md +++ b/README.md @@ -283,17 +283,17 @@ in/out inteface etc is. To print out all rules in the FILTER table: As you see in the code snippet above, rules are organized into chains, and chains are in tables. You have a fixed set of tables; for IPv4: -- FILTER, -- NAT, -- MANGLE and -- RAW. + * FILTER, + * NAT, + * MANGLE and + * RAW. For IPv6 the tables are: -- FILTER, -- MANGLE, -- RAW and -- SECURITY. + * FILTER, + * MANGLE, + * RAW and + * SECURITY. To access a table: @@ -541,4 +541,3 @@ or more rules, than commit it: The drawback is that Table is a singleton, and if you disable autocommit, it will be disabled for all instances of that Table. - From 432eee01a329a44a2deae0b579337e7dd5067c3d Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 31 Oct 2015 23:03:31 -0700 Subject: [PATCH 181/287] Start 0.11.0-dev --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index 1050830..273e3fd 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.10.0" +__version__ = "0.11.0-dev" From 4c18f94eab93e30b941439f1ed8b5fc5b69844d3 Mon Sep 17 00:00:00 2001 From: tomislacker Date: Mon, 2 Nov 2015 14:52:56 -0600 Subject: [PATCH 182/287] Add documentation to describe 1- vs. 0-indexed lists IPTables enumerates rules with a 1-indexed list whereas Python's `list` structure is 0-indexed **Note:** This makes the assumption that libiptc will *always* return rules in an ordered fashion --- iptc/ip4tc.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 3b6ee8a..ede18cb 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -1465,7 +1465,12 @@ def _get_rules(self): return [self.table.create_rule(e, self) for e in entries] rules = property(_get_rules) - """This is the list of rules currently in the chain.""" + """This is the list of rules currently in the chain. + + The indexes of the Rule items produced from this list *should* correspond + to the IPTables --line-numbers value minus one. Keeping in mind that + iptables rules are 1-indexed whereas the Python list is 0-indexed + """ def autocommit(fn): From 01ec240cce3546353e6d8c470d1d05a0039064c2 Mon Sep 17 00:00:00 2001 From: tomislacker Date: Mon, 2 Nov 2015 14:53:15 -0600 Subject: [PATCH 183/287] Add test for ensuring rule enumeration from libiptc is ordered If this test passes, it allows the safe assumption that the rule index in the list returned from Chain.rules is the IPTables rule number (minus one) --- iptc/test/test_iptc.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/iptc/test/test_iptc.py b/iptc/test/test_iptc.py index 03597bc..1e33e14 100755 --- a/iptc/test/test_iptc.py +++ b/iptc/test/test_iptc.py @@ -736,6 +736,31 @@ def test_rule_iterate_mangle(self): for rule in chain.rules if rule): pass + def test_rule_iterate_rulenum(self): + """Ensure rule numbers are always returned in order""" + insert_rule_count = 3 + append_rule_count = 3 + for rule_num in range(insert_rule_count, 0, -1): + rule = iptc.Rule() + match = rule.create_match("comment") + match.comment = "rule{}".format(rule_num) + rule.create_target("ACCEPT") + self.chain.insert_rule(rule) + + for rule_num in range(append_rule_count): + rule = iptc.Rule() + match = rule.create_match("comment") + match.comment = "rule{}".format(rule_num) + rule.create_target("ACCEPT") + self.chain.append_rule(rule) + + rules = self.chain.rules + assert len(rules) == (insert_rule_count + append_rule_count) + for rule_num, rule in enumerate(rules, start=1): + assert rule.target == "ACCEPT" + assert len(rule.matches) == 1 + assert rule.matches[0].comment == "rule{}".format(rule_num) + def test_rule_insert(self): rules = [] From 605aba22e37a838180f1f4d403414cc34cab412f Mon Sep 17 00:00:00 2001 From: tomislacker Date: Mon, 2 Nov 2015 15:01:51 -0600 Subject: [PATCH 184/287] TestFix: ValueError: zero length field name in format --- iptc/test/test_iptc.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/iptc/test/test_iptc.py b/iptc/test/test_iptc.py index 1e33e14..2d4418c 100755 --- a/iptc/test/test_iptc.py +++ b/iptc/test/test_iptc.py @@ -743,14 +743,14 @@ def test_rule_iterate_rulenum(self): for rule_num in range(insert_rule_count, 0, -1): rule = iptc.Rule() match = rule.create_match("comment") - match.comment = "rule{}".format(rule_num) + match.comment = "rule{rule_num}".format(rule_num=rule_num) rule.create_target("ACCEPT") self.chain.insert_rule(rule) for rule_num in range(append_rule_count): rule = iptc.Rule() match = rule.create_match("comment") - match.comment = "rule{}".format(rule_num) + match.comment = "rule{rule_num}".format(rule_num=rule_num) rule.create_target("ACCEPT") self.chain.append_rule(rule) @@ -759,7 +759,8 @@ def test_rule_iterate_rulenum(self): for rule_num, rule in enumerate(rules, start=1): assert rule.target == "ACCEPT" assert len(rule.matches) == 1 - assert rule.matches[0].comment == "rule{}".format(rule_num) + assert rule.matches[0].comment == "rule{rule_num}".format( + rule_num=rule_num) def test_rule_insert(self): rules = [] From 491b7044ab4e708611f1fd6c7eee6da97f763245 Mon Sep 17 00:00:00 2001 From: tomislacker Date: Mon, 2 Nov 2015 15:03:59 -0600 Subject: [PATCH 185/287] BugFix: Do not check rule targets, test apparatus should have a clear chain --- iptc/test/test_iptc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/iptc/test/test_iptc.py b/iptc/test/test_iptc.py index 2d4418c..4807e63 100755 --- a/iptc/test/test_iptc.py +++ b/iptc/test/test_iptc.py @@ -757,7 +757,6 @@ def test_rule_iterate_rulenum(self): rules = self.chain.rules assert len(rules) == (insert_rule_count + append_rule_count) for rule_num, rule in enumerate(rules, start=1): - assert rule.target == "ACCEPT" assert len(rule.matches) == 1 assert rule.matches[0].comment == "rule{rule_num}".format( rule_num=rule_num) From d73d28da8a72dbae4bbfc9fa83b2ef5f4c6a28d3 Mon Sep 17 00:00:00 2001 From: tomislacker Date: Mon, 2 Nov 2015 15:11:25 -0600 Subject: [PATCH 186/287] Bugfix: Bad range on rule appends --- iptc/test/test_iptc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/iptc/test/test_iptc.py b/iptc/test/test_iptc.py index 4807e63..e039859 100755 --- a/iptc/test/test_iptc.py +++ b/iptc/test/test_iptc.py @@ -747,7 +747,9 @@ def test_rule_iterate_rulenum(self): rule.create_target("ACCEPT") self.chain.insert_rule(rule) - for rule_num in range(append_rule_count): + append_rulenum_start = insert_rule_count + 1 + append_rulenum_end = append_rulenum_start + 3 + for rule_num in range(append_rulenum_start, append_rulenum_end): rule = iptc.Rule() match = rule.create_match("comment") match.comment = "rule{rule_num}".format(rule_num=rule_num) From f3183d099ee28d23e4c880aa07875731b9fb6548 Mon Sep 17 00:00:00 2001 From: tomislacker Date: Mon, 2 Nov 2015 15:11:38 -0600 Subject: [PATCH 187/287] Add message for assertion failure --- iptc/test/test_iptc.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/iptc/test/test_iptc.py b/iptc/test/test_iptc.py index e039859..4f73a79 100755 --- a/iptc/test/test_iptc.py +++ b/iptc/test/test_iptc.py @@ -761,7 +761,11 @@ def test_rule_iterate_rulenum(self): for rule_num, rule in enumerate(rules, start=1): assert len(rule.matches) == 1 assert rule.matches[0].comment == "rule{rule_num}".format( - rule_num=rule_num) + rule_num=rule_num), \ + "rule[{left_num}] is not new {right_num}".format( + left_num=rule_num, + right_num=rule.matches[0].comment + ) def test_rule_insert(self): rules = [] From 8529dd824b5add261ac4eb49a7245f81dc93e6d6 Mon Sep 17 00:00:00 2001 From: evilaliv3 Date: Fri, 1 Jan 2016 15:54:51 +0100 Subject: [PATCH 188/287] Properly package unit tests the python way --- .travis.yml | 2 +- README.md | 6 +----- setup.py | 4 +++- test.py | 27 --------------------------- {iptc/test => tests}/__init__.py | 0 {iptc/test => tests}/test_iptc.py | 0 {iptc/test => tests}/test_matches.py | 0 {iptc/test => tests}/test_targets.py | 0 8 files changed, 5 insertions(+), 34 deletions(-) delete mode 100755 test.py rename {iptc/test => tests}/__init__.py (100%) rename {iptc/test => tests}/test_iptc.py (100%) rename {iptc/test => tests}/test_matches.py (100%) rename {iptc/test => tests}/test_targets.py (100%) diff --git a/.travis.yml b/.travis.yml index c3f9c0e..b878ab7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,4 +7,4 @@ install: - python setup.py build - python setup.py install script: - - echo "y"|sudo PATH=$PATH ./test.py + - sudo PATH=$PATH python setup.py test diff --git a/README.md b/README.md index 66e128c..39a4412 100644 --- a/README.md +++ b/README.md @@ -76,11 +76,7 @@ installs into `/usr/local/lib`. Now you can run the tests: - % sudo PATH=$PATH ./test.py - WARNING: this test will manipulate iptables rules. - Don't do this on a production machine. - Would you like to continue? y/n y - [...] + % sudo PATH=$PATH python setup.py test The `PATH=$PATH` part is necessary after `sudo` if you have installed into a `virtualenv`, since `sudo` will reset your environment to a diff --git a/setup.py b/setup.py index ae3a46c..145f0c3 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,8 @@ """python-iptables setup script""" -from distutils.core import setup, Extension +from setuptools import setup, Extension +#from distutils.core import setup, Extension # make pyflakes happy __pkgname__ = None @@ -21,6 +22,7 @@ package_dir={"iptc": "iptc"}, ext_modules=[Extension("libxtwrapper", ["libxtwrapper/wrapper.c"])], + test_suite="tests", classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Console", diff --git a/test.py b/test.py deleted file mode 100755 index c24c956..0000000 --- a/test.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import sys - -try: - input = raw_input -except NameError: - pass - -print("WARNING: this test will manipulate iptables rules.") -print("Don't do this on a production machine.") -while True: - print("Would you like to continue? y/n") - answer = input() - if answer in "yYnN" and len(answer) == 1: - break -if answer in "nN": - sys.exit(0) - -from iptc.test import test_iptc, test_matches, test_targets - -results = [rv for rv in [test_iptc.run_tests(), test_matches.run_tests(), - test_targets.run_tests()]] -for res in results: - if res: - sys.exit(1) diff --git a/iptc/test/__init__.py b/tests/__init__.py similarity index 100% rename from iptc/test/__init__.py rename to tests/__init__.py diff --git a/iptc/test/test_iptc.py b/tests/test_iptc.py similarity index 100% rename from iptc/test/test_iptc.py rename to tests/test_iptc.py diff --git a/iptc/test/test_matches.py b/tests/test_matches.py similarity index 100% rename from iptc/test/test_matches.py rename to tests/test_matches.py diff --git a/iptc/test/test_targets.py b/tests/test_targets.py similarity index 100% rename from iptc/test/test_targets.py rename to tests/test_targets.py From 89985ce9d474661d11331241a8003c1c2cc82cf1 Mon Sep 17 00:00:00 2001 From: evilaliv3 Date: Fri, 1 Jan 2016 16:10:06 +0100 Subject: [PATCH 189/287] Add coverage tracking during unit-tests --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b878ab7..23e2a11 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,5 +6,8 @@ python: install: - python setup.py build - python setup.py install + - sudo pip install coveralls script: - - sudo PATH=$PATH python setup.py test + - sudo PATH=$PATH coverage run setup.py test +after_success: + coveralls From 4ed8ba8c6f79a44475c97a4147b7b04250eae20e Mon Sep 17 00:00:00 2001 From: evilaliv3 Date: Fri, 1 Jan 2016 16:13:34 +0100 Subject: [PATCH 190/287] Add coveralls badge to README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 39a4412..df46172 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ If you are looking for `ebtables` python bindings, check out [![Latest Release](https://pypip.in/v/python-iptables/badge.png)](https://pypi.python.org/pypi/python-iptables) -[![Build Status](https://travis-ci.org/ldx/python-iptables.png?branch=master)](https://travis-ci.org/ldx/python-iptables) +[![Build Status](https://travis-ci.org/ldx/python-iptables.png?branch=master)](https://travis-ci.org/ldx/python-iptables) [![Coverage Status](https://coveralls.io/repos/ldx/python-iptables/badge.svg?branch=codecoverage)](https://coveralls.io/r/ldx/python-iptables?branch=codecoverage) [![Code Health](https://landscape.io/github/ldx/python-iptables/codecoverage/landscape.svg)](https://landscape.io/github/ldx/python-iptables/codecoverage) [![Number of Downloads](https://pypip.in/d/python-iptables/badge.png)](https://pypi.python.org/pypi/python-iptables) From ae129da30ec282d33463863f6199f3ae175a630f Mon Sep 17 00:00:00 2001 From: James Oakley Date: Tue, 5 Jan 2016 08:02:12 -0800 Subject: [PATCH 191/287] Handle numeric protocols to allow for rules with protocols not explicitly defined in the module. --- iptc/ip4tc.py | 6 +++++- iptc/ip6tc.py | 6 +++++- tests/test_iptc.py | 14 ++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index ede18cb..3c6d42a 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -1218,16 +1218,20 @@ def get_protocol(self): proto = "!" else: proto = "" - proto = "".join([proto, self.protocols[self.entry.ip.proto]]) + proto = "".join([proto, self.protocols.get(self.entry.ip.proto, str(self.entry.ip.proto))]) return proto def set_protocol(self, proto): + proto = str(proto) if proto[0] == "!": self.entry.ip.invflags |= ipt_ip.IPT_INV_PROTO proto = proto[1:] else: self.entry.ip.invflags &= (~ipt_ip.IPT_INV_PROTO & ipt_ip.IPT_INV_MASK) + if proto.isdigit(): + self.entry.ip.proto = int(proto) + return for p in self.protocols.items(): if proto.lower() == p[1]: self.entry.ip.proto = p[0] diff --git a/iptc/ip6tc.py b/iptc/ip6tc.py index 0f9906f..1611525 100644 --- a/iptc/ip6tc.py +++ b/iptc/ip6tc.py @@ -503,10 +503,11 @@ def get_protocol(self): proto = "!" else: proto = "" - proto = "".join([proto, self.protocols[self.entry.ipv6.proto]]) + proto = "".join([proto, self.protocols.get(self.entry.ipv6.proto, str(self.entry.ipv6.proto))]) return proto def set_protocol(self, proto): + proto = str(proto) if proto[0] == "!": self.entry.ipv6.invflags |= ip6t_ip6.IP6T_INV_PROTO self.entry.ipv6.flags &= (~ip6t_ip6.IP6T_F_PROTO & @@ -516,6 +517,9 @@ def set_protocol(self, proto): self.entry.ipv6.invflags &= (~ip6t_ip6.IP6T_INV_PROTO & ip6t_ip6.IP6T_INV_MASK) self.entry.ipv6.flags |= ip6t_ip6.IP6T_F_PROTO + if proto.isdigit(): + self.entry.ipv6.proto = int(proto) + return for p in self.protocols.items(): if proto.lower() == p[1]: self.entry.ipv6.proto = p[0] diff --git a/tests/test_iptc.py b/tests/test_iptc.py index 4f73a79..6317d20 100755 --- a/tests/test_iptc.py +++ b/tests/test_iptc.py @@ -445,6 +445,13 @@ def test_rule_protocol(self): else: self.fail("rule accepted invalid protocol %s" % (proto)) + def test_rule_protocol_numeric(self): + rule = iptc.Rule6() + rule.protocol = 132 + self.assertEquals(rule.protocol, '132') + rule.protocol = '!132' + self.assertEquals(rule.protocol, '!132') + def test_rule_compare(self): r1 = iptc.Rule6() r1.src = "::1/128" @@ -668,6 +675,13 @@ def test_rule_protocol(self): else: self.fail("rule accepted invalid protocol %s" % (proto)) + def test_rule_protocol_numeric(self): + rule = iptc.Rule() + rule.protocol = 132 + self.assertEquals(rule.protocol, '132') + rule.protocol = '!132' + self.assertEquals(rule.protocol, '!132') + def test_rule_compare(self): r1 = iptc.Rule() r1.src = "127.0.0.2/255.255.255.0" From c2ccf4af77369fbd6f69d3fc32d7a4a7fbbcceb9 Mon Sep 17 00:00:00 2001 From: Jacob Cook Date: Thu, 21 Apr 2016 13:04:47 -0400 Subject: [PATCH 192/287] add xtables v11 match and target --- iptc/xtables.py | 124 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 122 insertions(+), 2 deletions(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index d21e62d..b3b5be4 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -108,6 +108,10 @@ class xt_option_entry(ct.Structure): ("max", ct.c_uint)] +class xt_xlate(ct.Structure): + _fields_ = [] + + class _U1(ct.Union): _fields_ = [("match", ct.POINTER(ct.POINTER(xt_entry_match))), ("target", ct.POINTER(ct.POINTER(xt_entry_target)))] @@ -408,6 +412,62 @@ class _xtables_match_v10(ct.Structure): ("loaded", ct.c_uint)] +class _xtables_match_v11(ct.Structure): + _fields_ = [("version", ct.c_char_p), + ("next", ct.c_void_p), + ("name", ct.c_char_p), + ("real_name", ct.c_char_p), + ("revision", ct.c_uint8), + ("ext_flags", ct.c_uint8), + ("family", ct.c_uint16), + ("size", ct.c_size_t), + ("userspacesize", ct.c_size_t), + ("help", ct.CFUNCTYPE(None)), + ("init", ct.CFUNCTYPE(None, ct.POINTER(xt_entry_match))), + # fourth parameter entry is struct ipt_entry for example + # int (*parse)(int c, char **argv, int invert, unsigned int + # *flags, const void *entry, struct xt_entry_match **match) + ("parse", ct.CFUNCTYPE(ct.c_int, ct.c_int, + ct.POINTER(ct.c_char_p), ct.c_int, + ct.POINTER(ct.c_uint), ct.c_void_p, + ct.POINTER(ct.POINTER( + xt_entry_match)))), + ("final_check", ct.CFUNCTYPE(None, ct.c_uint)), + # prints out the match iff non-NULL: put space at end + # first parameter ip is struct ipt_ip * for example + ("print", ct.CFUNCTYPE(None, ct.c_void_p, + ct.POINTER(xt_entry_match), ct.c_int)), + # saves the match info in parsable form to stdout. + # first parameter ip is struct ipt_ip * for example + ("save", ct.CFUNCTYPE(None, ct.c_void_p, + ct.POINTER(xt_entry_match))), + # Print match name or alias + ("alias", ct.CFUNCTYPE(ct.c_char_p, + ct.POINTER(xt_entry_match))), + # pointer to list of extra command-line options + ("extra_opts", ct.POINTER(option)), + + # introduced with the new iptables API + ("x6_parse", ct.CFUNCTYPE(None, ct.POINTER(xt_option_call))), + ("x6_fcheck", ct.CFUNCTYPE(None, ct.POINTER(xt_fcheck_call))), + ("x6_options", ct.POINTER(xt_option_entry)), + + # Translate iptables to nft + ("xlate", ct.CFUNCTYPE(None, ct.c_void_p, + ct.POINTER(xt_entry_match), + ct.POINTER(xt_xlate), ct.c_int)), + + # size of per-extension instance extra "global" scratch space + ("udata_size", ct.c_size_t), + + # ignore these men behind the curtain: + ("udata", ct.c_void_p), + ("option_offset", ct.c_uint), + ("m", ct.POINTER(xt_entry_match)), + ("mflags", ct.c_uint), + ("loaded", ct.c_uint)] + + class xtables_match(ct.Union): _fields_ = [("v1", _xtables_match_v1), ("v2", _xtables_match_v2), @@ -418,7 +478,8 @@ class xtables_match(ct.Union): ("v7", _xtables_match_v7), # Apparently v8 was skipped ("v9", _xtables_match_v9), - ("v10", _xtables_match_v10)] + ("v10", _xtables_match_v10), + ("v11", _xtables_match_v11)] class _xtables_target_v1(ct.Structure): @@ -659,6 +720,64 @@ class _xtables_target_v10(ct.Structure): ("loaded", ct.c_uint)] +class _xtables_target_v11(ct.Structure): + _fields_ = [("version", ct.c_char_p), + ("next", ct.c_void_p), + ("name", ct.c_char_p), + ("real_name", ct.c_char_p), + ("revision", ct.c_uint8), + ("ext_flags", ct.c_uint8), + ("family", ct.c_uint16), + ("size", ct.c_size_t), + ("userspacesize", ct.c_size_t), + ("help", ct.CFUNCTYPE(None)), + ("init", ct.CFUNCTYPE(None, ct.POINTER(xt_entry_target))), + # fourth parameter entry is struct ipt_entry for example + # int (*parse)(int c, char **argv, int invert, + # unsigned int *flags, const void *entry, + # struct xt_entry_target **target) + ("parse", ct.CFUNCTYPE(ct.c_int, + ct.POINTER(ct.c_char_p), ct.c_int, + ct.POINTER(ct.c_uint), ct.c_void_p, + ct.POINTER(ct.POINTER( + xt_entry_target)))), + ("final_check", ct.CFUNCTYPE(None, ct.c_uint)), + # prints out the target iff non-NULL: put space at end + # first parameter ip is struct ipt_ip * for example + ("print", ct.CFUNCTYPE(None, ct.c_void_p, + ct.POINTER(xt_entry_target), ct.c_int)), + # saves the target info in parsable form to stdout. + # first parameter ip is struct ipt_ip * for example + ("save", ct.CFUNCTYPE(None, ct.c_void_p, + ct.POINTER(xt_entry_target))), + # Print target name or alias + ("alias", ct.CFUNCTYPE(ct.c_char_p, + ct.POINTER(xt_entry_target))), + # pointer to list of extra command-line options + ("extra_opts", ct.POINTER(option)), + + # introduced with the new iptables API + ("x6_parse", ct.CFUNCTYPE(None, ct.POINTER(xt_option_call))), + ("x6_fcheck", ct.CFUNCTYPE(None, ct.POINTER(xt_fcheck_call))), + ("x6_options", ct.POINTER(xt_option_entry)), + + # Translate iptables to nft + ("xlate", ct.CFUNCTYPE(None, ct.c_void_p, + ct.POINTER(xt_entry_match), + ct.POINTER(xt_xlate), ct.c_int)), + + # size of per-extension instance extra "global" scratch space + ("udata_size", ct.c_size_t), + + # ignore these men behind the curtain: + ("udata", ct.c_void_p), + ("option_offset", ct.c_uint), + ("t", ct.POINTER(xt_entry_target)), + ("tflags", ct.c_uint), + ("used", ct.c_uint), + ("loaded", ct.c_uint)] + + class xtables_target(ct.Union): _fields_ = [("v1", _xtables_target_v1), ("v2", _xtables_target_v2), @@ -669,7 +788,8 @@ class xtables_target(ct.Union): ("v7", _xtables_target_v7), # Apparently v8 was skipped ("v9", _xtables_target_v9), - ("v10", _xtables_target_v10)] + ("v10", _xtables_target_v10), + ("v11", _xtables_target_v11)] class XTablesError(Exception): From fdae2efb101e03ff347e33ccffe073432b3f2703 Mon Sep 17 00:00:00 2001 From: Matthew Ellison Date: Fri, 29 Apr 2016 07:44:05 -0400 Subject: [PATCH 193/287] Prevent Segfault on v11 xtables This builds on top of proposed #173 but fixes the segfault issue when interacting with xtables. The structure of the tables did not change. Closes #172. --- iptc/xtables.py | 112 +----------------------------------------------- 1 file changed, 2 insertions(+), 110 deletions(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index b3b5be4..f80f4bd 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -412,60 +412,7 @@ class _xtables_match_v10(ct.Structure): ("loaded", ct.c_uint)] -class _xtables_match_v11(ct.Structure): - _fields_ = [("version", ct.c_char_p), - ("next", ct.c_void_p), - ("name", ct.c_char_p), - ("real_name", ct.c_char_p), - ("revision", ct.c_uint8), - ("ext_flags", ct.c_uint8), - ("family", ct.c_uint16), - ("size", ct.c_size_t), - ("userspacesize", ct.c_size_t), - ("help", ct.CFUNCTYPE(None)), - ("init", ct.CFUNCTYPE(None, ct.POINTER(xt_entry_match))), - # fourth parameter entry is struct ipt_entry for example - # int (*parse)(int c, char **argv, int invert, unsigned int - # *flags, const void *entry, struct xt_entry_match **match) - ("parse", ct.CFUNCTYPE(ct.c_int, ct.c_int, - ct.POINTER(ct.c_char_p), ct.c_int, - ct.POINTER(ct.c_uint), ct.c_void_p, - ct.POINTER(ct.POINTER( - xt_entry_match)))), - ("final_check", ct.CFUNCTYPE(None, ct.c_uint)), - # prints out the match iff non-NULL: put space at end - # first parameter ip is struct ipt_ip * for example - ("print", ct.CFUNCTYPE(None, ct.c_void_p, - ct.POINTER(xt_entry_match), ct.c_int)), - # saves the match info in parsable form to stdout. - # first parameter ip is struct ipt_ip * for example - ("save", ct.CFUNCTYPE(None, ct.c_void_p, - ct.POINTER(xt_entry_match))), - # Print match name or alias - ("alias", ct.CFUNCTYPE(ct.c_char_p, - ct.POINTER(xt_entry_match))), - # pointer to list of extra command-line options - ("extra_opts", ct.POINTER(option)), - - # introduced with the new iptables API - ("x6_parse", ct.CFUNCTYPE(None, ct.POINTER(xt_option_call))), - ("x6_fcheck", ct.CFUNCTYPE(None, ct.POINTER(xt_fcheck_call))), - ("x6_options", ct.POINTER(xt_option_entry)), - - # Translate iptables to nft - ("xlate", ct.CFUNCTYPE(None, ct.c_void_p, - ct.POINTER(xt_entry_match), - ct.POINTER(xt_xlate), ct.c_int)), - - # size of per-extension instance extra "global" scratch space - ("udata_size", ct.c_size_t), - - # ignore these men behind the curtain: - ("udata", ct.c_void_p), - ("option_offset", ct.c_uint), - ("m", ct.POINTER(xt_entry_match)), - ("mflags", ct.c_uint), - ("loaded", ct.c_uint)] +_xtables_match_v11 = _xtables_match_v10 class xtables_match(ct.Union): @@ -720,62 +667,7 @@ class _xtables_target_v10(ct.Structure): ("loaded", ct.c_uint)] -class _xtables_target_v11(ct.Structure): - _fields_ = [("version", ct.c_char_p), - ("next", ct.c_void_p), - ("name", ct.c_char_p), - ("real_name", ct.c_char_p), - ("revision", ct.c_uint8), - ("ext_flags", ct.c_uint8), - ("family", ct.c_uint16), - ("size", ct.c_size_t), - ("userspacesize", ct.c_size_t), - ("help", ct.CFUNCTYPE(None)), - ("init", ct.CFUNCTYPE(None, ct.POINTER(xt_entry_target))), - # fourth parameter entry is struct ipt_entry for example - # int (*parse)(int c, char **argv, int invert, - # unsigned int *flags, const void *entry, - # struct xt_entry_target **target) - ("parse", ct.CFUNCTYPE(ct.c_int, - ct.POINTER(ct.c_char_p), ct.c_int, - ct.POINTER(ct.c_uint), ct.c_void_p, - ct.POINTER(ct.POINTER( - xt_entry_target)))), - ("final_check", ct.CFUNCTYPE(None, ct.c_uint)), - # prints out the target iff non-NULL: put space at end - # first parameter ip is struct ipt_ip * for example - ("print", ct.CFUNCTYPE(None, ct.c_void_p, - ct.POINTER(xt_entry_target), ct.c_int)), - # saves the target info in parsable form to stdout. - # first parameter ip is struct ipt_ip * for example - ("save", ct.CFUNCTYPE(None, ct.c_void_p, - ct.POINTER(xt_entry_target))), - # Print target name or alias - ("alias", ct.CFUNCTYPE(ct.c_char_p, - ct.POINTER(xt_entry_target))), - # pointer to list of extra command-line options - ("extra_opts", ct.POINTER(option)), - - # introduced with the new iptables API - ("x6_parse", ct.CFUNCTYPE(None, ct.POINTER(xt_option_call))), - ("x6_fcheck", ct.CFUNCTYPE(None, ct.POINTER(xt_fcheck_call))), - ("x6_options", ct.POINTER(xt_option_entry)), - - # Translate iptables to nft - ("xlate", ct.CFUNCTYPE(None, ct.c_void_p, - ct.POINTER(xt_entry_match), - ct.POINTER(xt_xlate), ct.c_int)), - - # size of per-extension instance extra "global" scratch space - ("udata_size", ct.c_size_t), - - # ignore these men behind the curtain: - ("udata", ct.c_void_p), - ("option_offset", ct.c_uint), - ("t", ct.POINTER(xt_entry_target)), - ("tflags", ct.c_uint), - ("used", ct.c_uint), - ("loaded", ct.c_uint)] +_xtables_target_v11 = _xtables_target_v10 class xtables_target(ct.Union): From 942a6b955cb0eaca74878562d15ab8b8e61428bf Mon Sep 17 00:00:00 2001 From: Matthew Ellison Date: Thu, 12 May 2016 14:48:06 -0400 Subject: [PATCH 194/287] Add New 'compat_rev' Member to 'xtables_globals' Library verisons greater than 10 have an added member in the xtables_global struct. --- iptc/xtables.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index f80f4bd..8537338 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -84,7 +84,9 @@ class xtables_globals(ct.Structure): ("program_version", ct.c_char_p), ("orig_opts", ct.c_void_p), ("opts", ct.c_void_p), - ("exit_err", ct.CFUNCTYPE(None, ct.c_int, ct.c_char_p))] + ("exit_err", ct.CFUNCTYPE(None, ct.c_int, ct.c_char_p)), + ("compat_rev", ct.CFUNCTYPE(ct.c_int, ct.c_char_p, ct.c_uint8, + ct.c_int))] # struct used by getopt() @@ -759,6 +761,11 @@ def new(*args): _xt_globals.opts = None _xt_globals.exit_err = _xt_exit +if xtables_version > 10: + _COMPAT_REV_FN = ct.CFUNCTYPE(ct.c_int, ct.c_char_p, ct.c_uint8, ct.c_int) + _xt_compat_rev = _COMPAT_REV_FN(_lib_xtables.xtables_compatible_revision) + _xt_globals.compat_rev = _xt_compat_rev + _loaded_exts = {} From 4fd349494bfe7638191bc1596a57068074bb9218 Mon Sep 17 00:00:00 2001 From: Matthew Ellison Date: Thu, 12 May 2016 19:52:34 -0400 Subject: [PATCH 195/287] Remove Now Unused 'xt_xlate' --- iptc/xtables.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index 8537338..2cfccbb 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -110,10 +110,6 @@ class xt_option_entry(ct.Structure): ("max", ct.c_uint)] -class xt_xlate(ct.Structure): - _fields_ = [] - - class _U1(ct.Union): _fields_ = [("match", ct.POINTER(ct.POINTER(xt_entry_match))), ("target", ct.POINTER(ct.POINTER(xt_entry_target)))] From 2b4d08f973cfae724064e159a8120fd118f34d5d Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Mon, 23 May 2016 22:09:27 -0700 Subject: [PATCH 196/287] Release 0.11.0 --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index 273e3fd..e379bfe 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.11.0-dev" +__version__ = "0.11.0" From 21e40fa2bb30806983d8cd74a203ddf9f7394b85 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Mon, 23 May 2016 22:12:41 -0700 Subject: [PATCH 197/287] Start 0.12.0-dev --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index e379bfe..7e45c36 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.11.0" +__version__ = "0.12.0-dev" From 4e1f874f2c3b132952301e470e0641c6a1166768 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 26 Jun 2016 13:39:42 -0700 Subject: [PATCH 198/287] Fix #174 --- iptc/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/util.py b/iptc/util.py index a587e7e..7bb97fb 100644 --- a/iptc/util.py +++ b/iptc/util.py @@ -75,7 +75,7 @@ def _do_find_library(name): def _find_library(*names): - if version_info > (3, ): + if version_info >= (3, 3): ext = get_config_var("EXT_SUFFIX") else: ext = get_config_var('SO') From e8999bc14123c331c821660ed31c383b9c29e6bb Mon Sep 17 00:00:00 2001 From: Dan Fuhry Date: Fri, 11 Nov 2016 15:01:18 -0500 Subject: [PATCH 199/287] Update debian files and .gitignore * Sync debian package version to upstream version * Add debhelper-generated files to .gitignore --- .gitignore | 18 ++++++++++++++++++ debian/changelog | 6 ++++++ debian/control | 2 +- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index eeb8a34..30251e5 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,21 @@ /Vagrantfile /.vagrant /tags +*/__pycache__/ +*.pyc +/build/ +/.pybuild/ +*.egg-info + +/debian/python-iptables/ +/debian/python3-iptables/ +/debian/python-iptables-dbg/ +/debian/python3-iptables-dbg/ +/debian/python-iptables-doc/ +/debian/files +/debian/outfile +/debian/*.debhelper +/debian/*.log +/debian/*-stamp +/debian/*.substvars + diff --git a/debian/changelog b/debian/changelog index 0d7c90f..a5ae47d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +python-iptables (0.12.0-1) xenial; urgency=low + + * update debian changelog + + -- Dan Fuhry Fri, 11 Nov 2016 14:03:32 -0500 + python-iptables (0.5-git-20140925) precise; urgency=low * update debian/ diff --git a/debian/control b/debian/control index 468f1a5..2f82047 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: python-iptables Section: net Priority: extra Maintainer: Juliano Martinez -Build-depends: python-all-dev (>= 2.7), python-all-dbg (>= 2.7), python3-all-dev (>= 3.2), python3-all-dbg, debhelper (>= 7), python-support (>= 0.90) +Build-depends: python-all-dev (>= 2.7), python-all-dbg (>= 2.7), python3-all-dev (>= 3.2), python3-all-dbg, debhelper (>= 7) X-Python-Version: >= 2.7 X-Python3-Version: >= 3.2 Standards-Version: 3.8.4 From eb186ddfb43b5b54c2a43731427edb6a26a52c0c Mon Sep 17 00:00:00 2001 From: Pandu POLUAN Date: Thu, 15 Dec 2016 17:08:20 +0700 Subject: [PATCH 200/287] Initial pepoluan Commit Modify .hgignore to exclude PyCharm working dirs. --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index eeb8a34..739a2eb 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ /Vagrantfile /.vagrant /tags + +# Added exclusion for PyCharm files +.idea/* From 2d9eb2b7fad7a1b57287972eb83f765c86b766f4 Mon Sep 17 00:00:00 2001 From: Pandu POLUAN Date: Thu, 15 Dec 2016 17:18:19 +0700 Subject: [PATCH 201/287] Implement -g Support In general, changes made are: * Change constructor of Target to allow 'goto' option * Change create_target method of Rule, same purpose * Change how IPT_F_FRAG gets set in rule.entry.ip --- iptc/ip4tc.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 3c6d42a..35ae04c 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -662,7 +662,7 @@ class Target(IPTCModule): does not take any value in the iptables extension, an empty string i.e. "" should be used. """ - def __init__(self, rule, name=None, target=None, revision=None): + def __init__(self, rule, name=None, target=None, revision=None, goto=None): """ *rule* is the Rule object this match belongs to; it can be changed later via *set_rule()*. *name* is the name of the iptables target @@ -672,6 +672,7 @@ def __init__(self, rule, name=None, target=None, revision=None): should be used; different revisions use different structures in C and they usually only work with certain kernel versions. Python-iptables by default will use the latest revision available. + If goto is True, then it converts '-j' to '-g'. """ if name is None and target is None: raise ValueError("can't create target based on nothing") @@ -682,6 +683,17 @@ def __init__(self, rule, name=None, target=None, revision=None): self._orig_parse = None self._orig_options = None + if target is not None or goto is None: + # We are 'decoding' existing Target + self._goto = bool(rule.entry.ip.flags & ipt_ip.IPT_F_GOTO) + if goto is not None: + assert isinstance(goto, bool) + self._goto = goto + if goto: + rule.entry.ip.flags |= ipt_ip.IPT_F_GOTO + else: + rule.entry.ip.flags &= ~ipt_ip.IPT_F_GOTO + self._xt = xtables(rule.nfproto) module = (self._is_standard_target() and @@ -831,6 +843,10 @@ def _get_target(self): target = property(_get_target) """This is the C structure used by the extension.""" + def _get_goto(self): + return self._goto + goto = property(_get_goto) + class Policy(object): """ @@ -960,11 +976,11 @@ def create_match(self, name, revision=None): self.add_match(match) return match - def create_target(self, name, revision=None): + def create_target(self, name, revision=None, goto=False): """Create a new *target*, and set it as this rule's target. *name* is the name of the target extension, *revision* is the revision to - use.""" - target = Target(self, name=name, revision=revision) + use. *goto* determines if target uses '-j' (default) or '-g'.""" + target = Target(self, name=name, revision=revision, goto=False) self.target = target return target @@ -1207,7 +1223,10 @@ def get_fragment(self): def set_fragment(self, frag): self.entry.ip.invflags &= ~ipt_ip.IPT_INV_FRAG & ipt_ip.IPT_INV_MASK - self.entry.ip.flags = int(bool(frag)) + if frag: + self.entry.ip.flags |= ipt_ip.IPT_F_FRAG + else: + self.entry.ip.flags &= ~ipt_ip.IPT_F_FRAG fragment = property(get_fragment, set_fragment) """This means that the rule refers to the second and further fragments of From c9da2e93a3a8503fb7aa7a01854cd7d65f341d8d Mon Sep 17 00:00:00 2001 From: Pandu POLUAN Date: Thu, 15 Dec 2016 19:18:38 +0700 Subject: [PATCH 202/287] Bugfix #1 in this branch So create_target()'s' goto parameter did not get passed to Target()... fixed. --- iptc/ip4tc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 35ae04c..f43df7d 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -980,7 +980,7 @@ def create_target(self, name, revision=None, goto=False): """Create a new *target*, and set it as this rule's target. *name* is the name of the target extension, *revision* is the revision to use. *goto* determines if target uses '-j' (default) or '-g'.""" - target = Target(self, name=name, revision=revision, goto=False) + target = Target(self, name=name, revision=revision, goto=goto) self.target = target return target From d3df22b2520fc68c4eaa3852b571704d2d9787eb Mon Sep 17 00:00:00 2001 From: Pandu POLUAN Date: Fri, 16 Dec 2016 01:25:59 +0700 Subject: [PATCH 203/287] Make Compatible with ip6tc Now properly use get_ip() and introspection to ensure compatibility with ip6tc, and future. (IPv8, anyone?) --- iptc/ip4tc.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index f43df7d..19599a9 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -683,16 +683,27 @@ def __init__(self, rule, name=None, target=None, revision=None, goto=None): self._orig_parse = None self._orig_options = None + # NOTE: + # get_ip() returns the 'ip' structure that contains (1)the 'flags' field, and + # (2)the value for the GOTO flag. + # We *must* use get_ip() because the actual name of the field containing the + # structure apparently differs between implementation + ipstruct = rule.get_ip() + f_goto_attrs = [a for a in dir(ipstruct) if a.endswith('_F_GOTO')] + if len(f_goto_attrs) == 0: + raise RuntimeError('What kind of struct is this? It does not have "*_F_GOTO" constant!') + _F_GOTO = getattr(ipstruct, f_goto_attrs[0]) + if target is not None or goto is None: # We are 'decoding' existing Target - self._goto = bool(rule.entry.ip.flags & ipt_ip.IPT_F_GOTO) + self._goto = bool(ipstruct.flags & _F_GOTO) if goto is not None: assert isinstance(goto, bool) self._goto = goto if goto: - rule.entry.ip.flags |= ipt_ip.IPT_F_GOTO + ipstruct.flags |= _F_GOTO else: - rule.entry.ip.flags &= ~ipt_ip.IPT_F_GOTO + ipstruct.flags &= ~_F_GOTO self._xt = xtables(rule.nfproto) From 43db2ed2bef1c04c8b90786e778c57ea70b25de4 Mon Sep 17 00:00:00 2001 From: Jesus Llorente Date: Tue, 3 Jan 2017 18:12:56 +0200 Subject: [PATCH 204/287] Add sctp protocol support. Update tests to include sctp literal protocol and numeric protocol dccp (33) --- iptc/ip4tc.py | 5 +++++ tests/test_iptc.py | 18 +++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 19599a9..3a5791a 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -20,6 +20,10 @@ except: pass +# Add IPPROTO_SCTP to socket module if not available +if not hasattr(socket, 'IPPROTO_SCTP'): + setattr(socket, 'IPPROTO_SCTP', 132) + _IFNAMSIZ = 16 _libc = ct.CDLL("libc.so.6") @@ -932,6 +936,7 @@ class Rule(object): socket.IPPROTO_RAW: "raw", socket.IPPROTO_ROUTING: "routing", socket.IPPROTO_RSVP: "rsvp", + socket.IPPROTO_SCTP: "sctp", socket.IPPROTO_TCP: "tcp", socket.IPPROTO_TP: "tp", socket.IPPROTO_UDP: "udp", diff --git a/tests/test_iptc.py b/tests/test_iptc.py index 6317d20..a0b6ef9 100755 --- a/tests/test_iptc.py +++ b/tests/test_iptc.py @@ -432,7 +432,7 @@ def test_rule_interface(self): def test_rule_protocol(self): rule = iptc.Rule6() for proto in ["tcp", "udp", "icmp", "AH", "ESP", "!TCP", "!UDP", - "!ICMP", "!ah", "!esp"]: + "!ICMP", "!ah", "!esp", "sctp", "!SCTP"]: rule.protocol = proto self.assertEquals(proto.lower(), rule.protocol) for proto in ["", "asdf", "!"]: @@ -447,10 +447,10 @@ def test_rule_protocol(self): def test_rule_protocol_numeric(self): rule = iptc.Rule6() - rule.protocol = 132 - self.assertEquals(rule.protocol, '132') - rule.protocol = '!132' - self.assertEquals(rule.protocol, '!132') + rule.protocol = 33 + self.assertEquals(rule.protocol, '33') + rule.protocol = '!33' + self.assertEquals(rule.protocol, '!33') def test_rule_compare(self): r1 = iptc.Rule6() @@ -677,10 +677,10 @@ def test_rule_protocol(self): def test_rule_protocol_numeric(self): rule = iptc.Rule() - rule.protocol = 132 - self.assertEquals(rule.protocol, '132') - rule.protocol = '!132' - self.assertEquals(rule.protocol, '!132') + rule.protocol = 33 + self.assertEquals(rule.protocol, '33') + rule.protocol = '!33' + self.assertEquals(rule.protocol, '!33') def test_rule_compare(self): r1 = iptc.Rule() From bd0928ea3f0219641c1cf879d07ea3bc4bd249ed Mon Sep 17 00:00:00 2001 From: Dan Fuhry Date: Mon, 27 Feb 2017 16:11:44 -0500 Subject: [PATCH 205/287] Remove package version from Debian version number --- debian/changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index a5ae47d..e8bab22 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -python-iptables (0.12.0-1) xenial; urgency=low +python-iptables (0.12.0) xenial; urgency=low * update debian changelog From e947af15f0cfe3a6d5d1100fdd3c802144a2a1bd Mon Sep 17 00:00:00 2001 From: Dan Fuhry Date: Mon, 27 Feb 2017 17:25:22 -0500 Subject: [PATCH 206/287] Add /usr/lib/python3/dist-packages to ld search path Fixes module import on older Ubuntu releases --- debian/python3-iptables.postinst | 3 +++ debian/rules | 7 +++++++ 2 files changed, 10 insertions(+) create mode 100644 debian/python3-iptables.postinst diff --git a/debian/python3-iptables.postinst b/debian/python3-iptables.postinst new file mode 100644 index 0000000..7b46270 --- /dev/null +++ b/debian/python3-iptables.postinst @@ -0,0 +1,3 @@ +#!/bin/sh + +/sbin/ldconfig diff --git a/debian/rules b/debian/rules index b8ad1a9..4fd181a 100755 --- a/debian/rules +++ b/debian/rules @@ -21,6 +21,13 @@ override_dh_auto_install: $$py -B setup.py install --root debian/python3-iptables --install-layout deb; \ $$py-dbg -B setup.py install --root debian/python3-iptables-dbg --install-layout deb; \ done + + # On Ubuntu 14.04 and possibly other versions, /usr/lib/python3/dist-packages is not + # included in the system library search path, so we add it here. + set -e; \ + mkdir -p $(CURDIR)/debian/python3-iptables/etc/ld.so.conf.d; \ + echo "/usr/lib/python3/dist-packages" > $(CURDIR)/debian/python3-iptables/etc/ld.so.conf.d/python3-dist-packages.conf + set -e; \ for py in $(PY2VERS); do \ $$py -B setup.py install --root debian/python-iptables --install-layout deb; \ From 09bf65deff578d6e204adfb4d5a4a2e097c46123 Mon Sep 17 00:00:00 2001 From: Santosh Ananthakrishnan Date: Thu, 13 Apr 2017 02:52:51 -0700 Subject: [PATCH 207/287] Fix mask computation for v6 IPs --- iptc/ip6tc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/ip6tc.py b/iptc/ip6tc.py index 1611525..84deeda 100644 --- a/iptc/ip6tc.py +++ b/iptc/ip6tc.py @@ -273,7 +273,7 @@ def _create_mask(self, plen): if plen >= 8: mask.append(0xff) elif plen > 0: - mask.append(2 ** plen - 1) + mask.append(0xff>>(8-plen)<<(8-plen)) else: mask.append(0x00) plen -= 8 From 599d8b6c38ab9a975a9823e7cb594a9b350796af Mon Sep 17 00:00:00 2001 From: Santosh Ananthakrishnan Date: Thu, 13 Apr 2017 18:42:42 -0700 Subject: [PATCH 208/287] Add a test for create_match --- tests/test_iptc.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_iptc.py b/tests/test_iptc.py index a0b6ef9..a645304 100755 --- a/tests/test_iptc.py +++ b/tests/test_iptc.py @@ -374,6 +374,21 @@ def tearDown(self): self.chain.flush() self.chain.delete() + def test_create_mask(self): + rule = iptc.Rule6() + + # Mask /10 should return \xff\xc0\x00... + mask = rule._create_mask(10) + self.assertEquals(mask[0], 0xff) + self.assertEquals(mask[1], 0xc0) + self.assertEquals(mask[2:], [0x00]*14) + + # Mask /27 should return \xff\xff\xff\xe0... + mask = rule._create_mask(27) + self.assertEquals(mask[:3], [0xff, 0xff, 0xff]) + self.assertEquals(mask[3], 0xe0) + self.assertEquals(mask[4:], [0x00]*12) + def test_rule_address(self): # valid addresses rule = iptc.Rule6() From 0bc32ec166986e47c1900cf3b1b678bd7c9fffba Mon Sep 17 00:00:00 2001 From: James Oakley Date: Tue, 25 Apr 2017 07:27:04 -0700 Subject: [PATCH 209/287] Add support for xtables v12 --- iptc/xtables.py | 62 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index 2cfccbb..2688594 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -411,6 +411,7 @@ class _xtables_match_v10(ct.Structure): _xtables_match_v11 = _xtables_match_v10 +_xtables_match_v12 = _xtables_match_v10 class xtables_match(ct.Union): @@ -424,7 +425,8 @@ class xtables_match(ct.Union): # Apparently v8 was skipped ("v9", _xtables_match_v9), ("v10", _xtables_match_v10), - ("v11", _xtables_match_v11)] + ("v11", _xtables_match_v11), + ("v11", _xtables_match_v12)] class _xtables_target_v1(ct.Structure): @@ -667,6 +669,60 @@ class _xtables_target_v10(ct.Structure): _xtables_target_v11 = _xtables_target_v10 +class _xtables_target_v12(ct.Structure): + _fields_ = [("version", ct.c_char_p), + ("next", ct.c_void_p), + ("name", ct.c_char_p), + ("real_name", ct.c_char_p), + ("revision", ct.c_uint8), + ("ext_flags", ct.c_uint8), + ("family", ct.c_uint16), + ("size", ct.c_size_t), + ("userspacesize", ct.c_size_t), + ("help", ct.CFUNCTYPE(None)), + ("init", ct.CFUNCTYPE(None, ct.POINTER(xt_entry_target))), + # fourth parameter entry is struct ipt_entry for example + # int (*parse)(int c, char **argv, int invert, + # unsigned int *flags, const void *entry, + # struct xt_entry_target **target) + ("parse", ct.CFUNCTYPE(ct.c_int, + ct.POINTER(ct.c_char_p), ct.c_int, + ct.POINTER(ct.c_uint), ct.c_void_p, + ct.POINTER(ct.POINTER( + xt_entry_target)))), + ("final_check", ct.CFUNCTYPE(None, ct.c_uint)), + # prints out the target iff non-NULL: put space at end + # first parameter ip is struct ipt_ip * for example + ("print", ct.CFUNCTYPE(None, ct.c_void_p, + ct.POINTER(xt_entry_target), ct.c_int)), + # saves the target info in parsable form to stdout. + # first parameter ip is struct ipt_ip * for example + ("save", ct.CFUNCTYPE(None, ct.c_void_p, + ct.POINTER(xt_entry_target))), + # Print target name or alias + ("alias", ct.CFUNCTYPE(ct.c_char_p, + ct.POINTER(xt_entry_target))), + # pointer to list of extra command-line options + ("extra_opts", ct.POINTER(option)), + + # introduced with the new iptables API + ("x6_parse", ct.CFUNCTYPE(None, ct.POINTER(xt_option_call))), + ("x6_fcheck", ct.CFUNCTYPE(None, ct.POINTER(xt_fcheck_call))), + ("x6_options", ct.POINTER(xt_option_entry)), + + ('xt_xlate', ct.c_int), + + # size of per-extension instance extra "global" scratch space + ("udata_size", ct.c_size_t), + + # ignore these men behind the curtain: + ("udata", ct.c_void_p), + ("option_offset", ct.c_uint), + ("t", ct.POINTER(xt_entry_target)), + ("tflags", ct.c_uint), + ("used", ct.c_uint), + ("loaded", ct.c_uint)] + class xtables_target(ct.Union): _fields_ = [("v1", _xtables_target_v1), @@ -679,8 +735,8 @@ class xtables_target(ct.Union): # Apparently v8 was skipped ("v9", _xtables_target_v9), ("v10", _xtables_target_v10), - ("v11", _xtables_target_v11)] - + ("v11", _xtables_target_v11), + ("v12", _xtables_target_v12)] class XTablesError(Exception): """Raised when an xtables call fails for some reason.""" From b367788dcfacf50a9479d56803e0e9b00a4db02b Mon Sep 17 00:00:00 2001 From: James Oakley Date: Thu, 27 Apr 2017 14:19:37 -0700 Subject: [PATCH 210/287] Fix incorrect version for v12 match --- iptc/xtables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index 2688594..245a348 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -426,7 +426,7 @@ class xtables_match(ct.Union): ("v9", _xtables_match_v9), ("v10", _xtables_match_v10), ("v11", _xtables_match_v11), - ("v11", _xtables_match_v12)] + ("v12", _xtables_match_v12)] class _xtables_target_v1(ct.Structure): From 9a940571c7c6e067077bb1617f57d9a7910f51ef Mon Sep 17 00:00:00 2001 From: adisbladis Date: Thu, 4 May 2017 22:28:02 +0800 Subject: [PATCH 211/287] xtables v12 match structure also has member xt_xlate --- iptc/xtables.py | 54 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index 245a348..0d3b40f 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -411,7 +411,59 @@ class _xtables_match_v10(ct.Structure): _xtables_match_v11 = _xtables_match_v10 -_xtables_match_v12 = _xtables_match_v10 + + +class _xtables_match_v12(ct.Structure): + _fields_ = [("version", ct.c_char_p), + ("next", ct.c_void_p), + ("name", ct.c_char_p), + ("real_name", ct.c_char_p), + ("revision", ct.c_uint8), + ("ext_flags", ct.c_uint8), + ("family", ct.c_uint16), + ("size", ct.c_size_t), + ("userspacesize", ct.c_size_t), + ("help", ct.CFUNCTYPE(None)), + ("init", ct.CFUNCTYPE(None, ct.POINTER(xt_entry_match))), + # fourth parameter entry is struct ipt_entry for example + # int (*parse)(int c, char **argv, int invert, unsigned int + # *flags, const void *entry, struct xt_entry_match **match) + ("parse", ct.CFUNCTYPE(ct.c_int, ct.c_int, + ct.POINTER(ct.c_char_p), ct.c_int, + ct.POINTER(ct.c_uint), ct.c_void_p, + ct.POINTER(ct.POINTER( + xt_entry_match)))), + ("final_check", ct.CFUNCTYPE(None, ct.c_uint)), + # prints out the match iff non-NULL: put space at end + # first parameter ip is struct ipt_ip * for example + ("print", ct.CFUNCTYPE(None, ct.c_void_p, + ct.POINTER(xt_entry_match), ct.c_int)), + # saves the match info in parsable form to stdout. + # first parameter ip is struct ipt_ip * for example + ("save", ct.CFUNCTYPE(None, ct.c_void_p, + ct.POINTER(xt_entry_match))), + # Print match name or alias + ("alias", ct.CFUNCTYPE(ct.c_char_p, + ct.POINTER(xt_entry_match))), + # pointer to list of extra command-line options + ("extra_opts", ct.POINTER(option)), + + # introduced with the new iptables API + ("x6_parse", ct.CFUNCTYPE(None, ct.POINTER(xt_option_call))), + ("x6_fcheck", ct.CFUNCTYPE(None, ct.POINTER(xt_fcheck_call))), + ("x6_options", ct.POINTER(xt_option_entry)), + + ('xt_xlate', ct.c_int), + + # size of per-extension instance extra "global" scratch space + ("udata_size", ct.c_size_t), + + # ignore these men behind the curtain: + ("udata", ct.c_void_p), + ("option_offset", ct.c_uint), + ("m", ct.POINTER(xt_entry_match)), + ("mflags", ct.c_uint), + ("loaded", ct.c_uint)] class xtables_match(ct.Union): From 81de8a8a65590b7e2cc27ecb4a20ce1ed92584f1 Mon Sep 17 00:00:00 2001 From: Pandu POLUAN Date: Fri, 2 Jun 2017 09:48:27 +0700 Subject: [PATCH 212/287] Known Issues Update README.md with Known Issues --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) mode change 100644 => 100755 README.md diff --git a/README.md b/README.md old mode 100644 new mode 100755 index df46172..0e43e14 --- a/README.md +++ b/README.md @@ -537,3 +537,18 @@ or more rules, than commit it: The drawback is that Table is a singleton, and if you disable autocommit, it will be disabled for all instances of that Table. + + +Known Issues +============ + +These issues are mainly caused by complex interaction with upstream's +Netfilter implementation, and will require quite significant effort to +fix. Workarounds are available. + +* [Issue #201](https://github.com/ldx/python-iptables/issues/201) + -- The `hashlimit` match requires explicitly setting `hashlimit_htable_expire` + +* [Issue #204](https://github.com/ldx/python-iptables/issues/204) + -- The `NOTRACK` target is problematic; use `CT --notrack` instead + From 7ec8b6ea1cebc67a9f0f193149dd098e4ffe0671 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Mon, 5 Jun 2017 19:50:34 -0700 Subject: [PATCH 213/287] Release 0.12.0 --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index 7e45c36..ae0d7fb 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.12.0-dev" +__version__ = "0.12.0" From 60a64b97dd96496189564cef6a734d60055a2656 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Mon, 5 Jun 2017 19:56:34 -0700 Subject: [PATCH 214/287] Start 0.13.0-dev --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index ae0d7fb..7f73838 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.12.0" +__version__ = "0.13.0-dev" From 75f8202185306e7eb21b5bd97a8390d7f47e5efb Mon Sep 17 00:00:00 2001 From: Dmitriusan Date: Sun, 11 Jun 2017 12:45:15 +0300 Subject: [PATCH 215/287] Remove useless parentheses from error messages --- iptc/ip4tc.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 3a5791a..a333cd6 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -1714,7 +1714,7 @@ def set_policy(self, chain, policy, counters=None): rv = self._iptc.iptc_set_policy(chain.encode(), policy.encode(), cntrs, self._handle) if rv != 1: - raise IPTCError("can't set policy %s on chain %s: %s)" % + raise IPTCError("can't set policy %s on chain %s: %s" % (policy, chain, self.strerror())) @autocommit @@ -1739,7 +1739,7 @@ def append_entry(self, chain, entry): ct.cast(entry, ct.c_void_p), self._handle) if rv != 1: - raise IPTCError("can't append entry to chain %s: %s)" % + raise IPTCError("can't append entry to chain %s: %s" % (chain, self.strerror())) @autocommit @@ -1749,7 +1749,7 @@ def insert_entry(self, chain, entry, position): ct.cast(entry, ct.c_void_p), position, self._handle) if rv != 1: - raise IPTCError("can't insert entry into chain %s: %s)" % + raise IPTCError("can't insert entry into chain %s: %s" % (chain, self.strerror())) @autocommit @@ -1759,7 +1759,7 @@ def replace_entry(self, chain, entry, position): ct.cast(entry, ct.c_void_p), position, self._handle) if rv != 1: - raise IPTCError("can't replace entry in chain %s: %s)" % + raise IPTCError("can't replace entry in chain %s: %s" % (chain, self.strerror())) @autocommit @@ -1769,7 +1769,7 @@ def delete_entry(self, chain, entry, mask): ct.cast(entry, ct.c_void_p), mask, self._handle) if rv != 1: - raise IPTCError("can't delete entry from chain %s: %s)" % + raise IPTCError("can't delete entry from chain %s: %s" % (chain, self.strerror())) def first_rule(self, chain): From b6a1c4c91a48dc527ce8d003c11e536fd3683802 Mon Sep 17 00:00:00 2001 From: Pajaro_xdd Date: Thu, 31 Aug 2017 01:43:47 +0200 Subject: [PATCH 216/287] modified: iptc/xtables.py Add new xtdir to fix error on Debian --- iptc/xtables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index 0d3b40f..dd8033c 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -804,7 +804,7 @@ class XTablesError(Exception): import os.path for xtdir in ["/lib/xtables", "/lib64/xtables", "/usr/lib/xtables", "/usr/lib/iptables", "/usr/lib64/xtables", - "/usr/lib64/iptables", "/usr/local/lib/xtables"]: + "/usr/lib64/iptables", "/usr/local/lib/xtables", "/usr/lib/x86_64-linux-gnu/xtables"]: if os.path.isdir(xtdir): _xtables_libdir = xtdir break From 714e455795406c3c3ac8a93f7a74c172df2fb3a6 Mon Sep 17 00:00:00 2001 From: Edward Betts Date: Fri, 1 Sep 2017 21:03:54 +0100 Subject: [PATCH 217/287] correct spelling mistake --- iptc/ip4tc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index a333cd6..d72d1b7 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -102,7 +102,7 @@ class ipt_entry(ct.Structure): class IPTCError(Exception): """This exception is raised when a low-level libiptc error occurs. - It contains a short description about the error that occured while + It contains a short description about the error that occurred while executing an iptables operation. """ From c0d2400e25baa5c3c7a6b67e8483e821f84dbcda Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Fri, 1 Sep 2017 13:34:33 -0700 Subject: [PATCH 218/287] The source for docs is the RST files in doc/. --- doc/examples.rst | 30 ++++++++++++++++++++---------- doc/intro.rst | 10 +++++++++- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/doc/examples.rst b/doc/examples.rst index 5efbf42..b17c005 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -153,19 +153,19 @@ in/out inteface etc is. To print out all rules in the FILTER table:: >>> print "=======================" As you see in the code snippet above, rules are organized into chains, and -chains are in tables. You have a fixed set of tables; for IPv4:: +chains are in tables. You have a fixed set of tables; for IPv4: -* FILTER, -* NAT, -* MANGLE and -* RAW. +* ``FILTER``, +* ``NAT``, +* ``MANGLE`` and +* ``RAW``. -For IPv6 the tables are:: +For IPv6 the tables are: -* FILTER, -* MANGLE, -* RAW and -* SECURITY. +* ``FILTER``, +* ``MANGLE``, +* ``RAW`` and +* ``SECURITY``. To access a table:: @@ -409,3 +409,13 @@ commit it:: The drawback is that `Table` is a singleton, and if you disable autocommit, it will be disabled for all instances of that `Table`. + +Known Issues +============ + +These issues are mainly caused by complex interaction with upstream's +Netfilter implementation, and will require quite significant effort to +fix. Workarounds are available. + +- The ``hashlimit`` match requires explicitly setting ``hashlimit_htable_expire``. See `Issue #201 `_. +- The ``NOTRACK`` target is problematic; use ``CT --notrack`` instead. See `Issue #204 `_. diff --git a/doc/intro.rst b/doc/intro.rst index eab00f2..97ec5a0 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -44,6 +44,14 @@ If you are looking for ``ebtables`` python bindings, check out :target: https://travis-ci.org/ldx/python-iptables :alt: Build Status +.. image:: https://coveralls.io/repos/ldx/python-iptables/badge.svg?branch=codecoverage + :target: https://coveralls.io/r/ldx/python-iptables?branch=codecoverage + :alt: Coverage Status + +.. image:: https://landscape.io/github/ldx/python-iptables/codecoverage/landscape.svg + :target: https://landscape.io/github/ldx/python-iptables/codecoverage + :alt: Code Health + .. image:: https://pypip.in/d/python-iptables/badge.png :target: https://pypi.python.org/pypi/python-iptables :alt: Number of Downloads @@ -85,7 +93,7 @@ installs into ``/usr/local/lib``. Now you can run the tests:: - % sudo PATH=$PATH ./test.py + % sudo PATH=$PATH python setup.py test WARNING: this test will manipulate iptables rules. Don't do this on a production machine. Would you like to continue? y/n y From a0c9efe598b0411cda5795afdd358aa1bab13359 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Fri, 1 Sep 2017 13:35:24 -0700 Subject: [PATCH 219/287] Rebuild README.md via pandoc -o ../README.md -f rst -t markdown intro.rst examples.rst. --- README.md | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 0e43e14..dbc6cd6 100755 --- a/README.md +++ b/README.md @@ -36,7 +36,11 @@ If you are looking for `ebtables` python bindings, check out [![Latest Release](https://pypip.in/v/python-iptables/badge.png)](https://pypi.python.org/pypi/python-iptables) -[![Build Status](https://travis-ci.org/ldx/python-iptables.png?branch=master)](https://travis-ci.org/ldx/python-iptables) [![Coverage Status](https://coveralls.io/repos/ldx/python-iptables/badge.svg?branch=codecoverage)](https://coveralls.io/r/ldx/python-iptables?branch=codecoverage) [![Code Health](https://landscape.io/github/ldx/python-iptables/codecoverage/landscape.svg)](https://landscape.io/github/ldx/python-iptables/codecoverage) +[![Build Status](https://travis-ci.org/ldx/python-iptables.png?branch=master)](https://travis-ci.org/ldx/python-iptables) + +[![Coverage Status](https://coveralls.io/repos/ldx/python-iptables/badge.svg?branch=codecoverage)](https://coveralls.io/r/ldx/python-iptables?branch=codecoverage) + +[![Code Health](https://landscape.io/github/ldx/python-iptables/codecoverage/landscape.svg)](https://landscape.io/github/ldx/python-iptables/codecoverage) [![Number of Downloads](https://pypip.in/d/python-iptables/badge.png)](https://pypi.python.org/pypi/python-iptables) @@ -77,6 +81,10 @@ installs into `/usr/local/lib`. Now you can run the tests: % sudo PATH=$PATH python setup.py test + WARNING: this test will manipulate iptables rules. + Don't do this on a production machine. + Would you like to continue? y/n y + [...] The `PATH=$PATH` part is necessary after `sudo` if you have installed into a `virtualenv`, since `sudo` will reset your environment to a @@ -279,17 +287,17 @@ in/out inteface etc is. To print out all rules in the FILTER table: As you see in the code snippet above, rules are organized into chains, and chains are in tables. You have a fixed set of tables; for IPv4: - * FILTER, - * NAT, - * MANGLE and - * RAW. +- `FILTER`, +- `NAT`, +- `MANGLE` and +- `RAW`. For IPv6 the tables are: - * FILTER, - * MANGLE, - * RAW and - * SECURITY. +- `FILTER`, +- `MANGLE`, +- `RAW` and +- `SECURITY`. To access a table: @@ -538,7 +546,6 @@ or more rules, than commit it: The drawback is that Table is a singleton, and if you disable autocommit, it will be disabled for all instances of that Table. - Known Issues ============ @@ -546,9 +553,9 @@ These issues are mainly caused by complex interaction with upstream's Netfilter implementation, and will require quite significant effort to fix. Workarounds are available. -* [Issue #201](https://github.com/ldx/python-iptables/issues/201) - -- The `hashlimit` match requires explicitly setting `hashlimit_htable_expire` - -* [Issue #204](https://github.com/ldx/python-iptables/issues/204) - -- The `NOTRACK` target is problematic; use `CT --notrack` instead +- The `hashlimit` match requires explicitly setting + `hashlimit_htable_expire`. See [Issue + \#201](https://github.com/ldx/python-iptables/issues/201). +- The `NOTRACK` target is problematic; use `CT --notrack` instead. See + [Issue \#204](https://github.com/ldx/python-iptables/issues/204). From cd14daa58290ccbe8639f18a61480f71078fb260 Mon Sep 17 00:00:00 2001 From: Pajaro_xdd Date: Thu, 7 Sep 2017 16:35:17 +0200 Subject: [PATCH 220/287] modified: iptc/xtables.py Strip lines to <80 chars long --- iptc/xtables.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index dd8033c..03644cc 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -804,7 +804,8 @@ class XTablesError(Exception): import os.path for xtdir in ["/lib/xtables", "/lib64/xtables", "/usr/lib/xtables", "/usr/lib/iptables", "/usr/lib64/xtables", - "/usr/lib64/iptables", "/usr/local/lib/xtables", "/usr/lib/x86_64-linux-gnu/xtables"]: + "/usr/lib64/iptables", "/usr/local/lib/xtables", + "/usr/lib/x86_64-linux-gnu/xtables"]: if os.path.isdir(xtdir): _xtables_libdir = xtdir break From b4a53cbe1c788ed7b6a2f44ed7f2dda6b8e25f53 Mon Sep 17 00:00:00 2001 From: Jesus Llorente Date: Tue, 24 Oct 2017 12:17:38 +0300 Subject: [PATCH 221/287] Added missing table definitions to Table and Table6 classes --- iptc/ip4tc.py | 4 +++- iptc/ip6tc.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index d72d1b7..8afdbb9 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -1552,7 +1552,9 @@ class Table(object): """This is the constant for the raw table.""" NAT = "nat" """This is the constant for the nat table.""" - ALL = ["filter", "mangle", "raw", "nat"] + SECURITY = "security" + """This is the constant for the security table.""" + ALL = ["filter", "mangle", "raw", "nat", "security"] """This is the constant for all tables.""" _cache = dict() diff --git a/iptc/ip6tc.py b/iptc/ip6tc.py index 84deeda..ed10e17 100644 --- a/iptc/ip6tc.py +++ b/iptc/ip6tc.py @@ -571,9 +571,11 @@ class Table6(Table): """This is the constant for the mangle table.""" RAW = "raw" """This is the constant for the raw table.""" + NAT = "nat" + """This is the constant for the nat table.""" SECURITY = "security" """This is the constant for the security table.""" - ALL = ["filter", "mangle", "raw", "security"] + ALL = ["filter", "mangle", "raw", "nat", "security"] """This is the constant for all tables.""" _cache = dict() From acb035200c14a61f7df7b9f5815f11957a5ca768 Mon Sep 17 00:00:00 2001 From: Jesus Llorente Date: Mon, 13 Nov 2017 03:09:10 +0200 Subject: [PATCH 222/287] Added dictionary functionality to Rule(6) class --- iptc/ip4tc.py | 115 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 8afdbb9..5309cee 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -10,7 +10,7 @@ import weakref from .util import find_library, load_kernel -from .xtables import (XT_INV_PROTO, NFPROTO_IPV4, XTablesError, xtables, +from .xtables import (XT_INV_PROTO, NFPROTO_IPV4, NFPROTO_IPV6, XTablesError, xtables, xt_align, xt_counters, xt_entry_target, xt_entry_match) __all__ = ["Table", "Chain", "Rule", "Match", "Target", "Policy", "IPTCError"] @@ -1389,6 +1389,119 @@ def _get_mask(self): mask = property(_get_mask) """This is the raw mask buffer as iptables uses it when removing rules.""" + @classmethod + def from_dict(cls, rule_d): + """Generate a Rule(6) object from the input dictionary.""" + # Sanity check + assert(isinstance(rule_d, dict)) + # Basic rule attributes + rule_attr = ('src', 'dst', 'protocol', 'in-interface', 'out-interface', 'fragment') + iptc_rule = cls() + # Avoid issues with matches that require basic parameters to be configured first + for name in rule_attr: + if name in rule_d: + _iptc_setrule(iptc_rule, name, rule_d[name]) + for name, value in rule_d.items(): + try: + if name in rule_attr: + #_iptc_setrule(iptc_rule, name, value) + continue + elif name == 'target': + _iptc_settarget(iptc_rule, value) + else: + _iptc_setmatch(iptc_rule, name, value) + except Exception as e: + #print('Ignoring unsupported field <{}:{}>'.format(name, value)) + continue + return iptc_rule + + def to_dict(self): + """Generate a dictionary representation of the Rule(6) object.""" + d = {} + if self.nfproto==NFPROTO_IPV4 and self.src != '0.0.0.0/0.0.0.0': + d['src'] = self.src + elif self.nfproto==NFPROTO_IPV6 and self.src != '::/0': + d['src'] = self.src + if self.nfproto==NFPROTO_IPV4 and self.dst != '0.0.0.0/0.0.0.0': + d['dst'] = self.dst + elif self.nfproto==NFPROTO_IPV6 and self.dst != '::/0': + d['dst'] = self.dst + if self.protocol != 'ip': + d['protocol'] = self.protocol + if self.in_interface is not None: + d['in-interface'] = self.in_interface + if self.out_interface is not None: + d['out-interface'] = self.out_interface + if self.nfproto==NFPROTO_IPV4 and self.fragment: + d['fragment'] = self.fragment + for m in self.matches: + if m.name not in d: + d[m.name] = m.get_all_parameters() + elif isinstance(d[m.name], list): + d[m.name].append(m.get_all_parameters()) + else: + d[m.name] = [d[m.name], m.get_all_parameters()] + if self.target and self.target.name and len(self.target.get_all_parameters()): + name = self.target.name.replace('-', '_') + d['target'] = {name:self.target.get_all_parameters()} + elif self.target and self.target.name: + d['target'] = self.target.name + # Return a filtered dictionary + return _filter_empty_field(d) + +# Helper functions for dictionary operations over Rule(6) objects +def _iptc_setattr(object, name, value): + # Translate attribute name + name = name.replace('-', '_') + setattr(object, name, value) + +def _iptc_setattr_d(object, value_d): + for name, value in value_d.items(): + _iptc_setattr(object, name, value) + +def _iptc_setrule(iptc_rule, name, value): + _iptc_setattr(iptc_rule, name, value) + +def _iptc_setmatch(iptc_rule, name, value): + # Iterate list/tuple recursively + if isinstance(value, list) or isinstance(value, tuple): + for inner_value in value: + _iptc_setmatch(iptc_rule, name, inner_value) + # Assign dictionary value + elif isinstance(value, dict): + iptc_match = iptc_rule.create_match(name) + _iptc_setattr_d(iptc_match, value) + # Assign value directly + else: + iptc_match = iptc_rule.create_match(name) + _iptc_setattr(iptc_match, name, value) + +def _iptc_settarget(iptc_rule, value): + # Target is dictionary - Use only 1 pair key/value + if isinstance(value, dict): + for k, v in value.items(): + iptc_target = iptc_rule.create_target(k) + _iptc_setattr_d(iptc_target, v) + return + # Simple target + else: + iptc_target = iptc_rule.create_target(value) + +def _filter_empty_field(data_d): + """ + Remove empty lists from dictionary values + Before: {'target': {'CHECKSUM': {'checksum-fill': []}}} + After: {'target': {'CHECKSUM': {'checksum-fill': ''}}} + """ + for k, v in data_d.items(): + if isinstance(v, dict): + data_d[k] = _filter_empty_field(v) + elif isinstance(v, list) and len(v) != 0: + v = [_filter_empty_field(_v) if isinstance(_v, dict) else _v for _v in v ] + elif isinstance(v, list) and len(v) == 0: + data_d[k] = '' + return data_d + class Chain(object): """Rules are contained by chains. From 43730cbc138c894ca0d9ce56275fc610e71c75d5 Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Wed, 31 Jan 2018 14:06:57 -0800 Subject: [PATCH 223/287] don't call ctypes.util.find_library on non-sonames If IPTABLES_LIBDIR is given, we will pass potential paths to _do_find_library. In this case, calling ctypes.util.find_library is not helpful, and we should simply try to load the library directly. --- iptc/util.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/iptc/util.py b/iptc/util.py index 7bb97fb..ae5fb9b 100644 --- a/iptc/util.py +++ b/iptc/util.py @@ -52,6 +52,11 @@ def load_kernel(name, exc_if_failed=False): def _do_find_library(name): + if '/' in name: + try: + return ctypes.CDLL(name, mode=ctypes.RTLD_GLOBAL) + except Exception: + return None p = ctypes.util.find_library(name) if p: lib = ctypes.CDLL(p, mode=ctypes.RTLD_GLOBAL) From d4537ea14b5bb594c2676cfb9bd95529645f7d81 Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Wed, 31 Jan 2018 13:53:09 -0800 Subject: [PATCH 224/287] allow explicitly picking the xtables version If I have multiple libxtable v 80ersions, I might want python-iptables to use a version that doesn't come from the libxtables.so symlink. Add a PYTHON_IPTABLES_XTABLES_VERSION that facilitates this. For example, $ IPTABLES_LIBDIR=/usr/lib/x86_64-linux-gnu/ PYTHON_IPTABLES_XTABLES_VERSION=11 somecommand.py uses version 11 of xtables. --- iptc/xtables.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index 03644cc..ffc3d41 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -798,10 +798,14 @@ class XTablesError(Exception): _optind = ct.c_long.in_dll(_libc, "optind") _optarg = ct.c_char_p.in_dll(_libc, "optarg") -_lib_xtables, xtables_version = find_library("xtables") +xtables_version = os.getenv("PYTHON_IPTABLES_XTABLES_VERSION") +if xtables_version: + _searchlib = "libxtables.so.%s" % (xtables_version,) +else: + _searchlib = "xtables" +_lib_xtables, xtables_version = find_library(_searchlib) _xtables_libdir = os.getenv("XTABLES_LIBDIR") if _xtables_libdir is None: - import os.path for xtdir in ["/lib/xtables", "/lib64/xtables", "/usr/lib/xtables", "/usr/lib/iptables", "/usr/lib64/xtables", "/usr/lib64/iptables", "/usr/local/lib/xtables", From 0c3c0cb0039ff6881e115fb4af6caacdba5fb0ff Mon Sep 17 00:00:00 2001 From: Michael Overmeyer Date: Mon, 5 Mar 2018 19:26:55 +0000 Subject: [PATCH 225/287] Switched broken pypip.in badges to shields.io --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dbc6cd6..60ffa3c 100755 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ If you are looking for `ebtables` python bindings, check out [![Flattr](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=ldx&url=https%3A%2F%2Fgithub.com%2Fldx%2Fpython-iptables) -[![Latest Release](https://pypip.in/v/python-iptables/badge.png)](https://pypi.python.org/pypi/python-iptables) +[![Latest Release](https://img.shields.io/pypi/v/python-iptables.svg)](https://pypi.python.org/pypi/python-iptables) [![Build Status](https://travis-ci.org/ldx/python-iptables.png?branch=master)](https://travis-ci.org/ldx/python-iptables) @@ -42,9 +42,9 @@ If you are looking for `ebtables` python bindings, check out [![Code Health](https://landscape.io/github/ldx/python-iptables/codecoverage/landscape.svg)](https://landscape.io/github/ldx/python-iptables/codecoverage) -[![Number of Downloads](https://pypip.in/d/python-iptables/badge.png)](https://pypi.python.org/pypi/python-iptables) +[![Number of Downloads](https://img.shields.io/pypi/dm/python-iptables.svg)](https://pypi.python.org/pypi/python-iptables) -[![License](https://pypip.in/license/python-iptables/badge.png)](https://pypi.python.org/pypi/python-iptables) +[![License](https://img.shields.io/pypi/l/python-iptables.svg)](https://pypi.python.org/pypi/python-iptables) Installing via pip ------------------ From 92e08e201731b3ca8cad0ee38d43a4a932857b0f Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Wed, 27 Dec 2017 09:16:45 -0800 Subject: [PATCH 226/287] Create separate instances for IPv4 and IPv6 Chains --- iptc/ip4tc.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 8afdbb9..29d77ea 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -1400,10 +1400,11 @@ class Chain(object): _cache = weakref.WeakValueDictionary() def __new__(cls, table, name): - obj = Chain._cache.get(table.name + "." + name, None) + table_name = type(table).__name__ + "." + table.name + obj = Chain._cache.get(table_name + "." + name, None) if not obj: obj = object.__new__(cls) - Chain._cache[table.name + "." + name] = obj + Chain._cache[table_name + "." + name] = obj return obj def __init__(self, table, name): From 2abdb1d6103cdd4859a8a5a7e60f4d76ab641f95 Mon Sep 17 00:00:00 2001 From: Jesus Llorente Santos Date: Mon, 19 Mar 2018 08:23:54 +0200 Subject: [PATCH 227/287] Improved filtering of empty fields --- iptc/ip4tc.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 5309cee..2b6114f 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -1492,12 +1492,16 @@ def _filter_empty_field(data_d): Remove empty lists from dictionary values Before: {'target': {'CHECKSUM': {'checksum-fill': []}}} After: {'target': {'CHECKSUM': {'checksum-fill': ''}}} + Before: {'tcp': {'dport': ['22']}}} + After: {'tcp': {'dport': '22'}}} """ for k, v in data_d.items(): if isinstance(v, dict): data_d[k] = _filter_empty_field(v) elif isinstance(v, list) and len(v) != 0: v = [_filter_empty_field(_v) if isinstance(_v, dict) else _v for _v in v ] + if isinstance(v, list) and len(v) == 1: + data_d[k] = v.pop() elif isinstance(v, list) and len(v) == 0: data_d[k] = '' return data_d From 54aee9a3b1dcefdadccceca721f03801ee2a48b8 Mon Sep 17 00:00:00 2001 From: Vitaly Greck Date: Sun, 1 Apr 2018 14:08:14 -0700 Subject: [PATCH 228/287] Move errors out to the separate module to be able to catch the import level errors Autodetect lib dirs and import xtables --- iptc/__init__.py | 6 +++--- iptc/errors.py | 8 ++++++++ iptc/xtables.py | 28 ++++++++++++++++++---------- 3 files changed, 29 insertions(+), 13 deletions(-) create mode 100644 iptc/errors.py diff --git a/iptc/__init__.py b/iptc/__init__.py index 9818014..191f60b 100644 --- a/iptc/__init__.py +++ b/iptc/__init__.py @@ -7,9 +7,9 @@ .. moduleauthor:: Vilmos Nebehaj """ -from iptc.ip4tc import (is_table_available, Table, Chain, Rule, Match, Target, - Policy, IPTCError) +from iptc.ip4tc import (is_table_available, Table, Chain, Rule, Match, Target, Policy, IPTCError) from iptc.ip6tc import is_table6_available, Table6, Rule6 -from iptc.xtables import XTablesError +from iptc.errors import * + __all__ = [] diff --git a/iptc/errors.py b/iptc/errors.py new file mode 100644 index 0000000..fd25d9a --- /dev/null +++ b/iptc/errors.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + + +class XTablesError(Exception): + """Raised when an xtables call fails for some reason.""" + + +__all__ = ['XTablesError'] diff --git a/iptc/xtables.py b/iptc/xtables.py index ffc3d41..88cdf3a 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -7,6 +7,7 @@ from . import version from .util import find_library +from .errors import * XT_INV_PROTO = 0x40 # invert the sense of PROTO @@ -790,9 +791,6 @@ class xtables_target(ct.Union): ("v11", _xtables_target_v11), ("v12", _xtables_target_v12)] -class XTablesError(Exception): - """Raised when an xtables call fails for some reason.""" - _libc, _ = find_library("c") _optind = ct.c_long.in_dll(_libc, "optind") @@ -806,13 +804,23 @@ class XTablesError(Exception): _lib_xtables, xtables_version = find_library(_searchlib) _xtables_libdir = os.getenv("XTABLES_LIBDIR") if _xtables_libdir is None: - for xtdir in ["/lib/xtables", "/lib64/xtables", "/usr/lib/xtables", - "/usr/lib/iptables", "/usr/lib64/xtables", - "/usr/lib64/iptables", "/usr/local/lib/xtables", - "/usr/lib/x86_64-linux-gnu/xtables"]: - if os.path.isdir(xtdir): - _xtables_libdir = xtdir - break + import re + ldconfig_path_regex = re.compile('^(/.*):$') + import subprocess + ldconfig = subprocess.Popen( + ('ldconfig', '-N', '-v'), + stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, universal_newlines=True + ) + ldconfig_out, ldconfig_err = ldconfig.communicate() + if ldconfig.returncode != 0: + raise XTablesError("ldconfig failed, please set XTABLES_LIBDIR") + for ldconfig_out_line in ldconfig_out.splitlines(): + ldconfig_path_regex_match = ldconfig_path_regex.match(ldconfig_out_line) + if ldconfig_path_regex_match is not None: + ldconfig_path = ldconfig_path_regex_match.group(1) + if os.path.isdir(ldconfig_path): + _xtables_libdir = ldconfig_path + break if _xtables_libdir is None: raise XTablesError("can't find directory with extensions; " "please set XTABLES_LIBDIR") From b755c0b3e416c6283ddb1998125427614345a850 Mon Sep 17 00:00:00 2001 From: Vitaly Greck Date: Sun, 1 Apr 2018 16:40:33 -0700 Subject: [PATCH 229/287] Change subprocess.DEVNULL to subprocess.PIPE - compatibility issues --- iptc/xtables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index 88cdf3a..d8ff59d 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -809,7 +809,7 @@ class xtables_target(ct.Union): import subprocess ldconfig = subprocess.Popen( ('ldconfig', '-N', '-v'), - stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, universal_newlines=True + stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True ) ldconfig_out, ldconfig_err = ldconfig.communicate() if ldconfig.returncode != 0: From 971c44d20949fd17fca0e6f395208fe006e64322 Mon Sep 17 00:00:00 2001 From: Vitaly Greck Date: Sun, 1 Apr 2018 16:45:41 -0700 Subject: [PATCH 230/287] Add 'xtables' to the ldconfig path - sorry, mistake --- iptc/xtables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index d8ff59d..179649d 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -817,7 +817,7 @@ class xtables_target(ct.Union): for ldconfig_out_line in ldconfig_out.splitlines(): ldconfig_path_regex_match = ldconfig_path_regex.match(ldconfig_out_line) if ldconfig_path_regex_match is not None: - ldconfig_path = ldconfig_path_regex_match.group(1) + ldconfig_path = os.path.join(ldconfig_path_regex_match.group(1), 'xtables') if os.path.isdir(ldconfig_path): _xtables_libdir = ldconfig_path break From 2931bab42a6fb97ee9d0b88fef34363c7f8c609e Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Mon, 16 Apr 2018 17:19:37 -0700 Subject: [PATCH 231/287] Release 0.13.0 --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index 7f73838..205cedd 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.13.0-dev" +__version__ = "0.13.0" From e42e691732ec93d1bc05a954012246e78827f8d3 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Mon, 16 Apr 2018 17:21:13 -0700 Subject: [PATCH 232/287] Start 0.14.0-dev --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index 205cedd..978a432 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.13.0" +__version__ = "0.14.0-dev" From 0f8f08a4f7bdb645d3601959dad3320386cbaa4f Mon Sep 17 00:00:00 2001 From: Jesus Llorente Date: Sun, 17 Feb 2019 19:58:16 +0100 Subject: [PATCH 233/287] Update doc/examples.rst Added example of use --- doc/examples.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/doc/examples.rst b/doc/examples.rst index b17c005..1a8fc63 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -410,6 +410,21 @@ commit it:: The drawback is that `Table` is a singleton, and if you disable autocommit, it will be disabled for all instances of that `Table`. +Easy rules with dictionaries +---------------------------- +To simplify operations with ``python-iptables`` rules we have included support to define and convert Rules object into python dictionaries. + + >>> import iptc + >>> table = iptc.Table(iptc.Table.FILTER) + >>> chain = iptc.Chain(table, "INPUT") + >>> # Create an iptc.Rule object from dictionary + >>> rule_d = {'comment': {'comment': 'Match tcp.22'}, 'protocol': 'tcp', 'target': 'ACCEPT', 'tcp': {'dport': '22'}} + >>> rule = iptc.Rule.from_dict(rule_d) + >>> # Obtain a dictionary representation from the iptc.Rule + >>> rule.to_dict() + {'tcp': {'dport': '22'}, 'protocol': 'tcp', 'comment': {'comment': 'Match tcp.22'}, 'target': 'ACCEPT'} + + Known Issues ============ From edabe7c7529e684533ca001be17ff649f0cf6d3b Mon Sep 17 00:00:00 2001 From: Jesus Llorente Date: Sun, 17 Feb 2019 19:22:32 +0000 Subject: [PATCH 234/287] Added test cases for to_dict() and from_dict() --- tests/test_iptc.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/test_iptc.py b/tests/test_iptc.py index a645304..c01abef 100755 --- a/tests/test_iptc.py +++ b/tests/test_iptc.py @@ -574,6 +574,23 @@ def test_rule_insert(self): self.failUnless(rule in crules) crules.remove(rule) + def test_rule_to_dict(self): + rule = iptc.Rule6() + rule.protocol = "tcp" + rule.src = "::1" + target = iptc.Target(rule, "ACCEPT") + rule.target = target + rule_d = rule.to_dict() + self.assertEqual(rule_d, {"protocol": "tcp", "src": "::1/128", "target": "ACCEPT"}) + + def test_rule_from_dict(self): + rule = iptc.Rule6() + rule.protocol = "tcp" + rule.src = "::1" + target = iptc.Target(rule, "ACCEPT") + rule.target = target + rule2 = iptc.Rule6.from_dict({"protocol": "tcp", "src": "::1/128", "target": "ACCEPT"}) + self.assertEqual(rule, rule2) class TestRule(unittest.TestCase): def setUp(self): @@ -915,6 +932,23 @@ def test_rule_delete_nat(self): self.table_nat.commit() self.table_nat.refresh() + def test_rule_to_dict(self): + rule = iptc.Rule() + rule.protocol = "tcp" + rule.src = "127.0.0.1" + target = iptc.Target(rule, "ACCEPT") + rule.target = target + rule_d = rule.to_dict() + self.assertEqual(rule_d, {"protocol": "tcp", "src": "127.0.0.1/255.255.255.255", "target": "ACCEPT"}) + + def test_rule_from_dict(self): + rule = iptc.Rule() + rule.protocol = "tcp" + rule.src = "127.0.0.1" + target = iptc.Target(rule, "ACCEPT") + rule.target = target + rule2 = iptc.Rule.from_dict({"protocol": "tcp", "src": "127.0.0.1/255.255.255.255", "target": "ACCEPT"}) + self.assertEqual(rule, rule2) def suite(): suite_table6 = unittest.TestLoader().loadTestsFromTestCase(TestTable6) From 2584d477d2fa134719cc6ac5d434a75c44bae076 Mon Sep 17 00:00:00 2001 From: Jesus Llorente Santos Date: Tue, 26 Mar 2019 21:35:08 +0000 Subject: [PATCH 235/287] Added easy.py module with modified imports --- iptc/__init__.py | 1 + iptc/easy.py | 485 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 486 insertions(+) create mode 100644 iptc/easy.py diff --git a/iptc/__init__.py b/iptc/__init__.py index 191f60b..68c19bc 100644 --- a/iptc/__init__.py +++ b/iptc/__init__.py @@ -10,6 +10,7 @@ from iptc.ip4tc import (is_table_available, Table, Chain, Rule, Match, Target, Policy, IPTCError) from iptc.ip6tc import is_table6_available, Table6, Rule6 from iptc.errors import * +import iptc.easy __all__ = [] diff --git a/iptc/easy.py b/iptc/easy.py new file mode 100644 index 0000000..bc7021b --- /dev/null +++ b/iptc/easy.py @@ -0,0 +1,485 @@ +# TODO +# - Expose POLICY of a table + +from .ip4tc import Rule, Table, Chain, IPTCError +from .ip6tc import Rule6, Table6 + +BATCH_MODE = False + +def flush_all(ipv6=False): + """ Flush all available tables """ + for table in get_tables(ipv6): + flush_table(table, ipv6) + +def flush_table(table, ipv6=False, silent=False): + """ Flush a table """ + try: + iptc_table = _iptc_gettable(table, ipv6) + iptc_table.flush() + except Exception as e: + if not silent: raise + +def flush_chain(table, chain, ipv6=False, silent=False): + """ Flush a chain in table """ + try: + iptc_chain = _iptc_getchain(table, chain, ipv6) + iptc_chain.flush() + except Exception as e: + if not silent: raise + +def zero_all(table, ipv6=False): + """ Zero all tables """ + table_cls = Table6 if ipv6 else Table + for table in table_cls.ALL: + zero_table(table, ipv6) + +def zero_table(table, ipv6=False): + """ Zero a table """ + iptc_table = _iptc_gettable(table, ipv6) + iptc_table.zero_entries() + +def zero_chain(table, chain, ipv6=False): + """ Zero a chain in table """ + iptc_chain = _iptc_getchain(table, chain, ipv6) + iptc_chain.zero_counters() + +def has_chain(table, chain, ipv6=False): + """ Return True if chain exists in table False otherwise """ + return _iptc_gettable(table, ipv6).is_chain(chain) + +def has_rule(table, chain, rule_d, ipv6=False): + """ Return True if rule exists in chain False otherwise """ + iptc_chain = _iptc_getchain(table, chain, ipv6) + iptc_rule = _encode_iptc_rule(rule_d, ipv6) + return iptc_rule in iptc_chain.rules + +def add_chain(table, chain, ipv6=False, silent=False): + """ Return True if chain was added successfully to a table, raise Exception otherwise """ + try: + iptc_table = _iptc_gettable(table, ipv6) + iptc_table.create_chain(chain) + return True + except Exception as e: + if not silent: raise + return False + +def add_rule(table, chain, rule_d, position=0, ipv6=False): + """ Add a rule to a chain in a given position. 0=append, 1=first, n=nth position """ + iptc_chain = _iptc_getchain(table, chain, ipv6) + iptc_rule = _encode_iptc_rule(rule_d, ipv6) + if position == 0: + # Insert rule in last position -> append + iptc_chain.append_rule(iptc_rule) + elif position > 0: + # Insert rule in given position -> adjusted as iptables CLI + iptc_chain.insert_rule(iptc_rule, position - 1) + elif position < 0: + # Insert rule in given position starting from bottom -> not available in iptables CLI + nof_rules = len(iptc_chain.rules) + _position = position + nof_rules + # Insert at the top if the position has looped over + if _position <= 0: + _position = 0 + iptc_chain.insert_rule(iptc_rule, _position) + +def insert_rule(table, chain, rule_d, ipv6=False): + """ Add a rule to a chain in the 1st position """ + add_rule(table, chain, rule_d, position=1, ipv6=ipv6) + +def delete_chain(table, chain, ipv6=False, flush=False, silent=False): + """ Delete a chain """ + try: + if flush: + flush_chain(table, chain, ipv6, silent) + iptc_table = _iptc_gettable(table, ipv6) + iptc_table.delete_chain(chain) + except Exception as e: + if not silent: raise + +def delete_rule(table, chain, rule_d, ipv6=False, silent=False): + """ Delete a rule from a chain """ + try: + iptc_chain = _iptc_getchain(table, chain, ipv6) + iptc_rule = _encode_iptc_rule(rule_d, ipv6) + iptc_chain.delete_rule(iptc_rule) + except Exception as e: + if not silent: raise + +def get_tables(ipv6=False): + """ Get all tables """ + iptc_tables = _iptc_gettables(ipv6) + return [t.name for t in iptc_tables] + +def get_chains(table, ipv6=False): + """ Return the existing chains of a table """ + iptc_table = _iptc_gettable(table, ipv6) + return [iptc_chain.name for iptc_chain in iptc_table.chains] + +def get_rule(table, chain, position=0, ipv6=False, silent=False): + """ Get a rule from a chain in a given position. 0=all rules, 1=first, n=nth position """ + try: + if position == 0: + # Return all rules + return dump_chain(table, chain, ipv6) + elif position > 0: + # Return specific rule by position + iptc_chain = _iptc_getchain(table, chain, ipv6) + iptc_rule = iptc_chain.rules[position - 1] + return _decode_iptc_rule(iptc_rule, ipv6) + elif position < 0: + # Return last rule -> not available in iptables CLI + iptc_chain = _iptc_getchain(table, chain, ipv6) + iptc_rule = iptc_chain.rules[position] + return _decode_iptc_rule(iptc_rule, ipv6) + except Exception as e: + if not silent: raise + +def replace_rule(table, chain, old_rule_d, new_rule_d, ipv6=False): + """ Replaces an existing rule of a chain """ + iptc_chain = _iptc_getchain(table, chain, ipv6) + iptc_old_rule = _encode_iptc_rule(old_rule_d, ipv6) + iptc_new_rule = _encode_iptc_rule(new_rule_d, ipv6) + iptc_chain.replace_rule(iptc_new_rule, iptc_chain.rules.index(iptc_old_rule)) + +def get_rule_statistics(table, chain, rule_d, ipv6=False): + """ Return a tuple with the rule counters (numberOfBytes, numberOfPackets) """ + if not has_rule(table, chain, rule_d, ipv6): + raise AttributeError('Chain <{}@{}> has no rule <{}>'.format(chain, table, rule_d)) + iptc_chain = _iptc_getchain(table, chain, ipv6) + iptc_rule = _encode_iptc_rule(rule_d, ipv6) + iptc_rule_index = iptc_chain.rules.index(iptc_rule) + return iptc_chain.rules[iptc_rule_index].get_counters() + +def get_rule_position(table, chain, rule_d, ipv6=False): + """ Return the position of a rule within a chain """ + if not has_rule(table, chain, rule_d): + raise AttributeError('Chain <{}@{}> has no rule <{}>'.format(chain, table, rule_d)) + iptc_chain = _iptc_getchain(table, chain, ipv6) + iptc_rule = _encode_iptc_rule(rule_d, ipv6) + return iptc_chain.rules.index(iptc_rule) + + +def test_rule(rule_d, ipv6=False): + """ Return True if the rule is a well-formed dictionary, False otherwise """ + try: + _encode_iptc_rule(rule_d, ipv6) + return True + except: + return False + +def test_match(name, value, ipv6=False): + """ Return True if the match is valid, False otherwise """ + try: + iptc_rule = Rule6() if ipv6 else Rule() + _iptc_setmatch(iptc_rule, name, value) + return True + except: + return False + +def test_target(name, value, ipv6=False): + """ Return True if the target is valid, False otherwise """ + try: + iptc_rule = Rule6() if ipv6 else Rule() + _iptc_settarget(iptc_rule, {name:value}) + return True + except: + return False + + +def dump_all(ipv6=False): + """ Return a dictionary representation of all tables """ + d = {} + for table in get_tables(ipv6): + d[table] = dump_table(table, ipv6) + return d + +def dump_table(table, ipv6=False): + """ Return a dictionary representation of a table """ + d = {} + iptc_table = _iptc_gettable(table, ipv6) + for iptc_chain in iptc_table.chains: + d[iptc_chain.name] = dump_chain(iptc_table.name, iptc_chain.name, ipv6) + return d + +def dump_chain(table, chain, ipv6=False): + """ Return a list with the dictionary representation of the rules of a table """ + l = [] + iptc_chain = _iptc_getchain(table, chain, ipv6) + for i, iptc_rule in enumerate(iptc_chain.rules): + l.append(_decode_iptc_rule(iptc_rule, ipv6)) + return l + + +def batch_begin(table = None, ipv6=False): + """ Disable autocommit on a table """ + BATCH_MODE = True + if table: + tables = (table, ) + else: + tables = get_tables(ipv6) + for table in tables: + iptc_table = _iptc_gettable(table, ipv6) + iptc_table.autocommit = False + +def batch_end(table = None, ipv6=False): + """ Enable autocommit on table and commit changes """ + BATCH_MODE = False + if table: + tables = (table, ) + else: + tables = get_tables(ipv6) + for table in tables: + iptc_table = _iptc_gettable(table, ipv6) + iptc_table.autocommit = True + +def batch_add_chains(table, chains, ipv6=False, flush=True): + """ Add multiple chains to a table """ + iptc_table = _batch_begin_table(table, ipv6) + for chain in chains: + if iptc_table.is_chain(chain): + iptc_chain = Chain(iptc_table, chain) + else: + iptc_chain = iptc_table.create_chain(chain) + if flush: + iptc_chain.flush() + _batch_end_table(table, ipv6) + +def batch_delete_chains(table, chains, ipv6=False): + """ Delete multiple chains of a table """ + iptc_table = _batch_begin_table(table, ipv6) + for chain in chains: + if iptc_table.is_chain(chain): + iptc_chain = Chain(iptc_table, chain) + iptc_chain.flush() + iptc_table.delete_chain(chain) + _batch_end_table(table, ipv6) + +def batch_add_rules(table, batch_rules, ipv6=False): + """ Add multiple rules to a table with format (chain, rule_d, position) """ + iptc_table = _batch_begin_table(table, ipv6) + for (chain, rule_d, position) in batch_rules: + iptc_chain = Chain(iptc_table, chain) + iptc_rule = _encode_iptc_rule(rule_d, ipv6) + if position == 0: + # Insert rule in last position -> append + iptc_chain.append_rule(iptc_rule) + elif position > 0: + # Insert rule in given position -> adjusted as iptables CLI + iptc_chain.insert_rule(iptc_rule, position-1) + elif position < 0: + # Insert rule in given position starting from bottom -> not available in iptables CLI + nof_rules = len(iptc_chain.rules) + iptc_chain.insert_rule(iptc_rule, position + nof_rules) + _batch_end_table(table, ipv6) + +def batch_delete_rules(table, batch_rules, ipv6=False, silent=True): + """ Delete multiple rules from table with format (chain, rule_d) """ + try: + iptc_table = _batch_begin_table(table, ipv6) + for (chain, rule_d) in batch_rules: + iptc_chain = Chain(iptc_table, chain) + iptc_rule = _encode_iptc_rule(rule_d, ipv6) + iptc_chain.delete_rule(iptc_rule) + _batch_end_table(table, ipv6) + except Exception as e: + if not silent: raise + + +### INTERNAL FUNCTIONS ### +def _iptc_table_available(table, ipv6=False): + """ Return True if the table is available, False otherwise """ + try: + iptc_table = Table6(table) if ipv6 else Table(table) + return True + except: + return False + +def _iptc_gettables(ipv6=False): + """ Return an updated view of all available iptc_table """ + iptc_cls = Table6 if ipv6 else Table + return [_iptc_gettable(t, ipv6) for t in iptc_cls.ALL if _iptc_table_available(t, ipv6)] + +def _iptc_gettable(table, ipv6=False): + """ Return an updated view of an iptc_table """ + iptc_table = Table6(table) if ipv6 else Table(table) + if BATCH_MODE is False: + iptc_table.commit() + iptc_table.refresh() + return iptc_table + +def _iptc_getchain(table, chain, ipv6=False, silent=False): + """ Return an iptc_chain of an updated table """ + try: + iptc_table = _iptc_gettable(table, ipv6) + if not iptc_table.is_chain(chain): + raise AttributeError('Table <{}> has no chain <{}>'.format(table, chain)) + return Chain(iptc_table, chain) + except Exception as e: + if not silent: raise + +def _iptc_setattr(object, name, value): + # Translate attribute name + name = name.replace('-', '_') + setattr(object, name, value) + +def _iptc_setattr_d(object, value_d): + for name, value in value_d.items(): + _iptc_setattr(object, name, value) + +def _iptc_setrule(iptc_rule, name, value): + _iptc_setattr(iptc_rule, name, value) + +def _iptc_setmatch(iptc_rule, name, value): + # Iterate list/tuple recursively + if isinstance(value, list) or isinstance(value, tuple): + for inner_value in value: + _iptc_setmatch(iptc_rule, name, inner_value) + # Assign dictionary value + elif isinstance(value, dict): + iptc_match = iptc_rule.create_match(name) + _iptc_setattr_d(iptc_match, value) + # Assign value directly + else: + iptc_match = iptc_rule.create_match(name) + _iptc_setattr(iptc_match, name, value) + +def _iptc_settarget(iptc_rule, value): + # Target is dictionary - Use only 1 pair key/value + if isinstance(value, dict): + for k, v in value.items(): + iptc_target = iptc_rule.create_target(k) + _iptc_setattr_d(iptc_target, v) + return + # Simple target + else: + iptc_target = iptc_rule.create_target(value) + +def _encode_iptc_rule(rule_d, ipv6=False): + # Sanity check + assert(isinstance(rule_d, dict)) + # Basic rule attributes + rule_attr = ('src', 'dst', 'protocol', 'in-interface', 'out-interface', 'fragment') + iptc_rule = Rule6() if ipv6 else Rule() + # Avoid issues with matches that require basic parameters to be configured first + for name in rule_attr: + if name in rule_d: + _iptc_setrule(iptc_rule, name, rule_d[name]) + for name, value in rule_d.items(): + try: + if name in rule_attr: + #_iptc_setrule(iptc_rule, name, value) + continue + elif name == 'target': + _iptc_settarget(iptc_rule, value) + else: + _iptc_setmatch(iptc_rule, name, value) + except Exception as e: + #print('Ignoring unsupported field <{}:{}>'.format(name, value)) + continue + return iptc_rule + +def _decode_iptc_rule(iptc_rule, ipv6=False): + """ Return a dictionary representation of an iptc_rule """ + d = {} + if ipv6==False and iptc_rule.src != '0.0.0.0/0.0.0.0': + d['src'] = iptc_rule.src + elif ipv6==True and iptc_rule.src != '::/0': + d['src'] = iptc_rule.src + if ipv6==False and iptc_rule.dst != '0.0.0.0/0.0.0.0': + d['dst'] = iptc_rule.dst.rstrip('/255.255.255.255') + elif ipv6==True and iptc_rule.dst != '::/0': + d['dst'] = iptc_rule.dst.rstrip('/128') + if iptc_rule.protocol != 'ip': + d['protocol'] = iptc_rule.protocol + if iptc_rule.in_interface is not None: + d['in-interface'] = iptc_rule.in_interface + if iptc_rule.out_interface is not None: + d['out-interface'] = iptc_rule.out_interface + if ipv6 == False and iptc_rule.fragment: + d['fragment'] = iptc_rule.fragment + for m in iptc_rule.matches: + if m.name not in d: + d[m.name] = m.get_all_parameters() + elif isinstance(d[m.name], list): + d[m.name].append(m.get_all_parameters()) + else: + d[m.name] = [d[m.name], m.get_all_parameters()] + if iptc_rule.target and iptc_rule.target.name and len(iptc_rule.target.get_all_parameters()): + name = iptc_rule.target.name.replace('-', '_') + d['target'] = {name:iptc_rule.target.get_all_parameters()} + elif iptc_rule.target and iptc_rule.target.name: + d['target'] = iptc_rule.target.name + # Return a filtered dictionary + return _filter_empty_field(d) + +def _batch_begin_table(table, ipv6=False): + """ Disable autocommit on a table """ + iptc_table = _iptc_gettable(table, ipv6) + iptc_table.autocommit = False + return iptc_table + +def _batch_end_table(table, ipv6=False): + """ Enable autocommit on table and commit changes """ + iptc_table = _iptc_gettable(table, ipv6) + iptc_table.autocommit = True + return iptc_table + +def _filter_empty_field(data_d): + """ + Remove empty lists from dictionary values + Before: {'target': {'CHECKSUM': {'checksum-fill': []}}} + After: {'target': {'CHECKSUM': {'checksum-fill': ''}}} + Before: {'tcp': {'dport': ['22']}}} + After: {'tcp': {'dport': '22'}}} + """ + for k, v in data_d.items(): + if isinstance(v, dict): + data_d[k] = _filter_empty_field(v) + elif isinstance(v, list) and len(v) != 0: + v = [_filter_empty_field(_v) if isinstance(_v, dict) else _v for _v in v ] + if isinstance(v, list) and len(v) == 1: + data_d[k] = v.pop() + elif isinstance(v, list) and len(v) == 0: + data_d[k] = '' + return data_d + +### /INTERNAL FUNCTIONS ### + + +# How to use +if __name__== '__main__': + import uuid + # Generate chain name + chain = uuid.uuid4().hex[:10] + + for ipv6 in [False, True]: + table = 'filter' + if not has_chain(table, chain, ipv6): + print('Create new chain {}.{} ipv6={}'.format(table, chain, ipv6)) + add_chain(table, chain, ipv6) + else: + print('Flush existing chain {}.{} ipv6={}'.format(table, chain, ipv6)) + flush_chain(table, chain, ipv6) + + rule_d = {'in_interface':'wan0', + 'protocol':'tcp', + 'tcp': {'tcp-flags': ['FIN,SYN,RST,ACK', 'SYN']}, + 'conntrack': {'ctstate': ['NEW,RELATED']}, + 'comment':{'comment':'New incoming TCP connection'}, + 'mark':[{'mark': '0x1'},{'mark': '0x2'}], + 'target':'ACCEPT'} + # Append new rule + add_rule(table, chain, rule_d, position=0, ipv6=ipv6) + # Show chain rules + print('Display chain {}.{} ipv6={}'.format(table, chain, ipv6)) + print(dump_chain(table, chain, ipv6)) + # Show first rule + print('Display chain 1st rule {}.{} ipv6={}'.format(table, chain, ipv6)) + print(get_rule(table, chain, position=1, ipv6=ipv6)) + # Delete chain + delete_chain(table, chain, ipv6, flush=True) + print('Display table {} ipv6={}'.format(table, ipv6)) + print(dump_table(table, ipv6)) + # Show remaining chains of table + print('Display table chains {} ipv6={}'.format(table, ipv6)) + print(get_chains(table)) From 7111934eb67aa6615e89cd63fb00cfc847c45e07 Mon Sep 17 00:00:00 2001 From: Jesus Llorente Santos Date: Tue, 26 Mar 2019 21:47:45 +0000 Subject: [PATCH 236/287] Use raise_exc instead of silent --- iptc/easy.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/iptc/easy.py b/iptc/easy.py index bc7021b..4e1136a 100644 --- a/iptc/easy.py +++ b/iptc/easy.py @@ -11,21 +11,21 @@ def flush_all(ipv6=False): for table in get_tables(ipv6): flush_table(table, ipv6) -def flush_table(table, ipv6=False, silent=False): +def flush_table(table, ipv6=False, raise_exc=True): """ Flush a table """ try: iptc_table = _iptc_gettable(table, ipv6) iptc_table.flush() except Exception as e: - if not silent: raise + if raise_exc: raise -def flush_chain(table, chain, ipv6=False, silent=False): +def flush_chain(table, chain, ipv6=False, raise_exc=True): """ Flush a chain in table """ try: iptc_chain = _iptc_getchain(table, chain, ipv6) iptc_chain.flush() except Exception as e: - if not silent: raise + if raise_exc: raise def zero_all(table, ipv6=False): """ Zero all tables """ @@ -53,14 +53,14 @@ def has_rule(table, chain, rule_d, ipv6=False): iptc_rule = _encode_iptc_rule(rule_d, ipv6) return iptc_rule in iptc_chain.rules -def add_chain(table, chain, ipv6=False, silent=False): +def add_chain(table, chain, ipv6=False, raise_exc=True): """ Return True if chain was added successfully to a table, raise Exception otherwise """ try: iptc_table = _iptc_gettable(table, ipv6) iptc_table.create_chain(chain) return True except Exception as e: - if not silent: raise + if raise_exc: raise return False def add_rule(table, chain, rule_d, position=0, ipv6=False): @@ -86,24 +86,24 @@ def insert_rule(table, chain, rule_d, ipv6=False): """ Add a rule to a chain in the 1st position """ add_rule(table, chain, rule_d, position=1, ipv6=ipv6) -def delete_chain(table, chain, ipv6=False, flush=False, silent=False): +def delete_chain(table, chain, ipv6=False, flush=False, raise_exc=True): """ Delete a chain """ try: if flush: - flush_chain(table, chain, ipv6, silent) + flush_chain(table, chain, ipv6, raise_exc) iptc_table = _iptc_gettable(table, ipv6) iptc_table.delete_chain(chain) except Exception as e: - if not silent: raise + if raise_exc: raise -def delete_rule(table, chain, rule_d, ipv6=False, silent=False): +def delete_rule(table, chain, rule_d, ipv6=False, raise_exc=True): """ Delete a rule from a chain """ try: iptc_chain = _iptc_getchain(table, chain, ipv6) iptc_rule = _encode_iptc_rule(rule_d, ipv6) iptc_chain.delete_rule(iptc_rule) except Exception as e: - if not silent: raise + if raise_exc: raise def get_tables(ipv6=False): """ Get all tables """ @@ -115,7 +115,7 @@ def get_chains(table, ipv6=False): iptc_table = _iptc_gettable(table, ipv6) return [iptc_chain.name for iptc_chain in iptc_table.chains] -def get_rule(table, chain, position=0, ipv6=False, silent=False): +def get_rule(table, chain, position=0, ipv6=False, raise_exc=True): """ Get a rule from a chain in a given position. 0=all rules, 1=first, n=nth position """ try: if position == 0: @@ -132,7 +132,7 @@ def get_rule(table, chain, position=0, ipv6=False, silent=False): iptc_rule = iptc_chain.rules[position] return _decode_iptc_rule(iptc_rule, ipv6) except Exception as e: - if not silent: raise + if raise_exc: raise def replace_rule(table, chain, old_rule_d, new_rule_d, ipv6=False): """ Replaces an existing rule of a chain """ @@ -272,7 +272,7 @@ def batch_add_rules(table, batch_rules, ipv6=False): iptc_chain.insert_rule(iptc_rule, position + nof_rules) _batch_end_table(table, ipv6) -def batch_delete_rules(table, batch_rules, ipv6=False, silent=True): +def batch_delete_rules(table, batch_rules, ipv6=False, raise_exc=True): """ Delete multiple rules from table with format (chain, rule_d) """ try: iptc_table = _batch_begin_table(table, ipv6) @@ -282,7 +282,7 @@ def batch_delete_rules(table, batch_rules, ipv6=False, silent=True): iptc_chain.delete_rule(iptc_rule) _batch_end_table(table, ipv6) except Exception as e: - if not silent: raise + if raise_exc: raise ### INTERNAL FUNCTIONS ### @@ -307,7 +307,7 @@ def _iptc_gettable(table, ipv6=False): iptc_table.refresh() return iptc_table -def _iptc_getchain(table, chain, ipv6=False, silent=False): +def _iptc_getchain(table, chain, ipv6=False, raise_exc=True): """ Return an iptc_chain of an updated table """ try: iptc_table = _iptc_gettable(table, ipv6) @@ -315,7 +315,7 @@ def _iptc_getchain(table, chain, ipv6=False, silent=False): raise AttributeError('Table <{}> has no chain <{}>'.format(table, chain)) return Chain(iptc_table, chain) except Exception as e: - if not silent: raise + if raise_exc: raise def _iptc_setattr(object, name, value): # Translate attribute name From 05f8b89a29e9034c8f3f74861967bd85b9193508 Mon Sep 17 00:00:00 2001 From: Jesus Llorente Santos Date: Tue, 26 Mar 2019 21:48:15 +0000 Subject: [PATCH 237/287] Use more pythonic way for dump_() functions --- iptc/easy.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/iptc/easy.py b/iptc/easy.py index 4e1136a..137c3a0 100644 --- a/iptc/easy.py +++ b/iptc/easy.py @@ -188,26 +188,16 @@ def test_target(name, value, ipv6=False): def dump_all(ipv6=False): """ Return a dictionary representation of all tables """ - d = {} - for table in get_tables(ipv6): - d[table] = dump_table(table, ipv6) - return d + return {table: dump_table(table, ipv6) for table in get_tables(ipv6)} def dump_table(table, ipv6=False): """ Return a dictionary representation of a table """ - d = {} - iptc_table = _iptc_gettable(table, ipv6) - for iptc_chain in iptc_table.chains: - d[iptc_chain.name] = dump_chain(iptc_table.name, iptc_chain.name, ipv6) - return d + return {chain: dump_chain(table, chain, ipv6) for chain in get_chains(table, ipv6)} def dump_chain(table, chain, ipv6=False): """ Return a list with the dictionary representation of the rules of a table """ - l = [] iptc_chain = _iptc_getchain(table, chain, ipv6) - for i, iptc_rule in enumerate(iptc_chain.rules): - l.append(_decode_iptc_rule(iptc_rule, ipv6)) - return l + return [_decode_iptc_rule(iptc_rule, ipv6) for iptc_rule in iptc_chain.rules] def batch_begin(table = None, ipv6=False): From dcf72b4552c37614a76d5ad3a1e134884d19e6bd Mon Sep 17 00:00:00 2001 From: Jesus Llorente Santos Date: Tue, 26 Mar 2019 22:17:08 +0000 Subject: [PATCH 238/287] Use get_tables() instead of table.ALL --- iptc/easy.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/iptc/easy.py b/iptc/easy.py index 137c3a0..015583b 100644 --- a/iptc/easy.py +++ b/iptc/easy.py @@ -29,8 +29,7 @@ def flush_chain(table, chain, ipv6=False, raise_exc=True): def zero_all(table, ipv6=False): """ Zero all tables """ - table_cls = Table6 if ipv6 else Table - for table in table_cls.ALL: + for table in get_tables(ipv6): zero_table(table, ipv6) def zero_table(table, ipv6=False): From 4791e47925e875d5f33adbe22bb8a093f48c8bd0 Mon Sep 17 00:00:00 2001 From: Jesus Llorente Santos Date: Tue, 26 Mar 2019 22:18:15 +0000 Subject: [PATCH 239/287] Make public [encode,decode]_ipct_rule functions --- iptc/easy.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/iptc/easy.py b/iptc/easy.py index 015583b..e11e4b0 100644 --- a/iptc/easy.py +++ b/iptc/easy.py @@ -49,7 +49,7 @@ def has_chain(table, chain, ipv6=False): def has_rule(table, chain, rule_d, ipv6=False): """ Return True if rule exists in chain False otherwise """ iptc_chain = _iptc_getchain(table, chain, ipv6) - iptc_rule = _encode_iptc_rule(rule_d, ipv6) + iptc_rule = encode_iptc_rule(rule_d, ipv6) return iptc_rule in iptc_chain.rules def add_chain(table, chain, ipv6=False, raise_exc=True): @@ -65,7 +65,7 @@ def add_chain(table, chain, ipv6=False, raise_exc=True): def add_rule(table, chain, rule_d, position=0, ipv6=False): """ Add a rule to a chain in a given position. 0=append, 1=first, n=nth position """ iptc_chain = _iptc_getchain(table, chain, ipv6) - iptc_rule = _encode_iptc_rule(rule_d, ipv6) + iptc_rule = encode_iptc_rule(rule_d, ipv6) if position == 0: # Insert rule in last position -> append iptc_chain.append_rule(iptc_rule) @@ -99,7 +99,7 @@ def delete_rule(table, chain, rule_d, ipv6=False, raise_exc=True): """ Delete a rule from a chain """ try: iptc_chain = _iptc_getchain(table, chain, ipv6) - iptc_rule = _encode_iptc_rule(rule_d, ipv6) + iptc_rule = encode_iptc_rule(rule_d, ipv6) iptc_chain.delete_rule(iptc_rule) except Exception as e: if raise_exc: raise @@ -124,20 +124,20 @@ def get_rule(table, chain, position=0, ipv6=False, raise_exc=True): # Return specific rule by position iptc_chain = _iptc_getchain(table, chain, ipv6) iptc_rule = iptc_chain.rules[position - 1] - return _decode_iptc_rule(iptc_rule, ipv6) + return decode_iptc_rule(iptc_rule, ipv6) elif position < 0: # Return last rule -> not available in iptables CLI iptc_chain = _iptc_getchain(table, chain, ipv6) iptc_rule = iptc_chain.rules[position] - return _decode_iptc_rule(iptc_rule, ipv6) + return decode_iptc_rule(iptc_rule, ipv6) except Exception as e: if raise_exc: raise def replace_rule(table, chain, old_rule_d, new_rule_d, ipv6=False): """ Replaces an existing rule of a chain """ iptc_chain = _iptc_getchain(table, chain, ipv6) - iptc_old_rule = _encode_iptc_rule(old_rule_d, ipv6) - iptc_new_rule = _encode_iptc_rule(new_rule_d, ipv6) + iptc_old_rule = encode_iptc_rule(old_rule_d, ipv6) + iptc_new_rule = encode_iptc_rule(new_rule_d, ipv6) iptc_chain.replace_rule(iptc_new_rule, iptc_chain.rules.index(iptc_old_rule)) def get_rule_statistics(table, chain, rule_d, ipv6=False): @@ -145,7 +145,7 @@ def get_rule_statistics(table, chain, rule_d, ipv6=False): if not has_rule(table, chain, rule_d, ipv6): raise AttributeError('Chain <{}@{}> has no rule <{}>'.format(chain, table, rule_d)) iptc_chain = _iptc_getchain(table, chain, ipv6) - iptc_rule = _encode_iptc_rule(rule_d, ipv6) + iptc_rule = encode_iptc_rule(rule_d, ipv6) iptc_rule_index = iptc_chain.rules.index(iptc_rule) return iptc_chain.rules[iptc_rule_index].get_counters() @@ -154,14 +154,14 @@ def get_rule_position(table, chain, rule_d, ipv6=False): if not has_rule(table, chain, rule_d): raise AttributeError('Chain <{}@{}> has no rule <{}>'.format(chain, table, rule_d)) iptc_chain = _iptc_getchain(table, chain, ipv6) - iptc_rule = _encode_iptc_rule(rule_d, ipv6) + iptc_rule = encode_iptc_rule(rule_d, ipv6) return iptc_chain.rules.index(iptc_rule) def test_rule(rule_d, ipv6=False): """ Return True if the rule is a well-formed dictionary, False otherwise """ try: - _encode_iptc_rule(rule_d, ipv6) + encode_iptc_rule(rule_d, ipv6) return True except: return False @@ -196,7 +196,7 @@ def dump_table(table, ipv6=False): def dump_chain(table, chain, ipv6=False): """ Return a list with the dictionary representation of the rules of a table """ iptc_chain = _iptc_getchain(table, chain, ipv6) - return [_decode_iptc_rule(iptc_rule, ipv6) for iptc_rule in iptc_chain.rules] + return [decode_iptc_rule(iptc_rule, ipv6) for iptc_rule in iptc_chain.rules] def batch_begin(table = None, ipv6=False): @@ -248,7 +248,7 @@ def batch_add_rules(table, batch_rules, ipv6=False): iptc_table = _batch_begin_table(table, ipv6) for (chain, rule_d, position) in batch_rules: iptc_chain = Chain(iptc_table, chain) - iptc_rule = _encode_iptc_rule(rule_d, ipv6) + iptc_rule = encode_iptc_rule(rule_d, ipv6) if position == 0: # Insert rule in last position -> append iptc_chain.append_rule(iptc_rule) @@ -267,7 +267,7 @@ def batch_delete_rules(table, batch_rules, ipv6=False, raise_exc=True): iptc_table = _batch_begin_table(table, ipv6) for (chain, rule_d) in batch_rules: iptc_chain = Chain(iptc_table, chain) - iptc_rule = _encode_iptc_rule(rule_d, ipv6) + iptc_rule = encode_iptc_rule(rule_d, ipv6) iptc_chain.delete_rule(iptc_rule) _batch_end_table(table, ipv6) except Exception as e: @@ -343,7 +343,7 @@ def _iptc_settarget(iptc_rule, value): else: iptc_target = iptc_rule.create_target(value) -def _encode_iptc_rule(rule_d, ipv6=False): +def encode_iptc_rule(rule_d, ipv6=False): # Sanity check assert(isinstance(rule_d, dict)) # Basic rule attributes @@ -367,7 +367,7 @@ def _encode_iptc_rule(rule_d, ipv6=False): continue return iptc_rule -def _decode_iptc_rule(iptc_rule, ipv6=False): +def decode_iptc_rule(iptc_rule, ipv6=False): """ Return a dictionary representation of an iptc_rule """ d = {} if ipv6==False and iptc_rule.src != '0.0.0.0/0.0.0.0': From 3eb7d2340b93e9993b0413b6d26963de147fe517 Mon Sep 17 00:00:00 2001 From: Jesus Llorente Santos Date: Tue, 26 Mar 2019 22:35:20 +0000 Subject: [PATCH 240/287] Renamed get_rule_counters function --- iptc/easy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/easy.py b/iptc/easy.py index e11e4b0..62338f4 100644 --- a/iptc/easy.py +++ b/iptc/easy.py @@ -140,7 +140,7 @@ def replace_rule(table, chain, old_rule_d, new_rule_d, ipv6=False): iptc_new_rule = encode_iptc_rule(new_rule_d, ipv6) iptc_chain.replace_rule(iptc_new_rule, iptc_chain.rules.index(iptc_old_rule)) -def get_rule_statistics(table, chain, rule_d, ipv6=False): +def get_rule_counters(table, chain, rule_d, ipv6=False): """ Return a tuple with the rule counters (numberOfBytes, numberOfPackets) """ if not has_rule(table, chain, rule_d, ipv6): raise AttributeError('Chain <{}@{}> has no rule <{}>'.format(chain, table, rule_d)) From 0e6946287e7b2e86f2ad1598b5337c79d59f32a9 Mon Sep 17 00:00:00 2001 From: Jesus Llorente Santos Date: Tue, 26 Mar 2019 22:35:40 +0000 Subject: [PATCH 241/287] Add set/get policy functions --- iptc/easy.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/iptc/easy.py b/iptc/easy.py index 62338f4..8ce1f54 100644 --- a/iptc/easy.py +++ b/iptc/easy.py @@ -185,6 +185,17 @@ def test_target(name, value, ipv6=False): return False +def get_policy(table, chain, ipv6=False): + """ Return the default policy of chain in a table """ + iptc_chain = _iptc_getchain(table, chain, ipv6) + return iptc_chain.get_policy().name + +def set_policy(table, chain, policy='ACCEPT', ipv6=False): + """ Set the default policy of chain in a table """ + iptc_chain = _iptc_getchain(table, chain, ipv6) + iptc_chain.set_policy(policy) + + def dump_all(ipv6=False): """ Return a dictionary representation of all tables """ return {table: dump_table(table, ipv6) for table in get_tables(ipv6)} From 50074e9ab5762c84188428480c8c4c8131b5d60d Mon Sep 17 00:00:00 2001 From: Jesus Llorente Santos Date: Tue, 26 Mar 2019 22:37:26 +0000 Subject: [PATCH 242/287] Removed if __name__== '__main__' section --- iptc/easy.py | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/iptc/easy.py b/iptc/easy.py index 8ce1f54..c09e197 100644 --- a/iptc/easy.py +++ b/iptc/easy.py @@ -444,42 +444,3 @@ def _filter_empty_field(data_d): return data_d ### /INTERNAL FUNCTIONS ### - - -# How to use -if __name__== '__main__': - import uuid - # Generate chain name - chain = uuid.uuid4().hex[:10] - - for ipv6 in [False, True]: - table = 'filter' - if not has_chain(table, chain, ipv6): - print('Create new chain {}.{} ipv6={}'.format(table, chain, ipv6)) - add_chain(table, chain, ipv6) - else: - print('Flush existing chain {}.{} ipv6={}'.format(table, chain, ipv6)) - flush_chain(table, chain, ipv6) - - rule_d = {'in_interface':'wan0', - 'protocol':'tcp', - 'tcp': {'tcp-flags': ['FIN,SYN,RST,ACK', 'SYN']}, - 'conntrack': {'ctstate': ['NEW,RELATED']}, - 'comment':{'comment':'New incoming TCP connection'}, - 'mark':[{'mark': '0x1'},{'mark': '0x2'}], - 'target':'ACCEPT'} - # Append new rule - add_rule(table, chain, rule_d, position=0, ipv6=ipv6) - # Show chain rules - print('Display chain {}.{} ipv6={}'.format(table, chain, ipv6)) - print(dump_chain(table, chain, ipv6)) - # Show first rule - print('Display chain 1st rule {}.{} ipv6={}'.format(table, chain, ipv6)) - print(get_rule(table, chain, position=1, ipv6=ipv6)) - # Delete chain - delete_chain(table, chain, ipv6, flush=True) - print('Display table {} ipv6={}'.format(table, ipv6)) - print(dump_table(table, ipv6)) - # Show remaining chains of table - print('Display table chains {} ipv6={}'.format(table, ipv6)) - print(get_chains(table)) From 328146f05c7fc112c4c7c47d1264e438c632d845 Mon Sep 17 00:00:00 2001 From: Jesus Llorente Santos Date: Tue, 26 Mar 2019 22:39:03 +0000 Subject: [PATCH 243/287] Make _BATCH_MODE private --- iptc/easy.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/iptc/easy.py b/iptc/easy.py index c09e197..1204a05 100644 --- a/iptc/easy.py +++ b/iptc/easy.py @@ -1,10 +1,9 @@ -# TODO -# - Expose POLICY of a table +# -*- coding: utf-8 -*- from .ip4tc import Rule, Table, Chain, IPTCError from .ip6tc import Rule6, Table6 -BATCH_MODE = False +_BATCH_MODE = False def flush_all(ipv6=False): """ Flush all available tables """ @@ -212,7 +211,7 @@ def dump_chain(table, chain, ipv6=False): def batch_begin(table = None, ipv6=False): """ Disable autocommit on a table """ - BATCH_MODE = True + _BATCH_MODE = True if table: tables = (table, ) else: @@ -223,7 +222,7 @@ def batch_begin(table = None, ipv6=False): def batch_end(table = None, ipv6=False): """ Enable autocommit on table and commit changes """ - BATCH_MODE = False + _BATCH_MODE = False if table: tables = (table, ) else: @@ -302,7 +301,7 @@ def _iptc_gettables(ipv6=False): def _iptc_gettable(table, ipv6=False): """ Return an updated view of an iptc_table """ iptc_table = Table6(table) if ipv6 else Table(table) - if BATCH_MODE is False: + if _BATCH_MODE is False: iptc_table.commit() iptc_table.refresh() return iptc_table From dd3d92d2ee3459101e45e81428f139009910ff34 Mon Sep 17 00:00:00 2001 From: Jesus Llorente Santos Date: Tue, 26 Mar 2019 22:39:42 +0000 Subject: [PATCH 244/287] Add TODOs --- iptc/easy.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/iptc/easy.py b/iptc/easy.py index 1204a05..a64d846 100644 --- a/iptc/easy.py +++ b/iptc/easy.py @@ -1,5 +1,9 @@ # -*- coding: utf-8 -*- +# TODO: +# - Add documentation +# - Add HowToUse examples + from .ip4tc import Rule, Table, Chain, IPTCError from .ip6tc import Rule6, Table6 From b1ad40a8eec74c36fcbc95de94af311145ec2d69 Mon Sep 17 00:00:00 2001 From: Jesus Llorente Santos Date: Tue, 9 Apr 2019 19:45:08 +0000 Subject: [PATCH 245/287] Add Subnet Mask conversion --- iptc/easy.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/iptc/easy.py b/iptc/easy.py index a64d846..485955e 100644 --- a/iptc/easy.py +++ b/iptc/easy.py @@ -385,11 +385,15 @@ def decode_iptc_rule(iptc_rule, ipv6=False): """ Return a dictionary representation of an iptc_rule """ d = {} if ipv6==False and iptc_rule.src != '0.0.0.0/0.0.0.0': - d['src'] = iptc_rule.src + _ip, _netmask = iptc_rule.src.split('/') + _netmask = _netmask_v4_to_cidr(_netmask) + d['src'] = '{}/{}'.format(_ip, _netmask) elif ipv6==True and iptc_rule.src != '::/0': - d['src'] = iptc_rule.src + d['src'] = iptc_rule.src.rstrip('/128') if ipv6==False and iptc_rule.dst != '0.0.0.0/0.0.0.0': - d['dst'] = iptc_rule.dst.rstrip('/255.255.255.255') + _ip, _netmask = iptc_rule.dst.split('/') + _netmask = _netmask_v4_to_cidr(_netmask) + d['dst'] = '{}/{}'.format(_ip, _netmask) elif ipv6==True and iptc_rule.dst != '::/0': d['dst'] = iptc_rule.dst.rstrip('/128') if iptc_rule.protocol != 'ip': @@ -446,4 +450,8 @@ def _filter_empty_field(data_d): data_d[k] = '' return data_d +def _netmask_v4_to_cidr(netmask_addr): + # Implement Subnet Mask conversion without dependencies + return sum([bin(int(x)).count('1') for x in netmask_addr.split('.')]) + ### /INTERNAL FUNCTIONS ### From cccb2741987358d70eb5ebccb0d77fdc6dd1b78a Mon Sep 17 00:00:00 2001 From: Jesus Llorente Santos Date: Tue, 9 Apr 2019 20:04:55 +0000 Subject: [PATCH 246/287] Enforce CIDR in IPv6 addresses --- iptc/easy.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/iptc/easy.py b/iptc/easy.py index 485955e..def9997 100644 --- a/iptc/easy.py +++ b/iptc/easy.py @@ -382,20 +382,21 @@ def encode_iptc_rule(rule_d, ipv6=False): return iptc_rule def decode_iptc_rule(iptc_rule, ipv6=False): - """ Return a dictionary representation of an iptc_rule """ + """ Return a dictionary representation of an iptc_rule + Note: host IP addresses are appended their corresponding CIDR """ d = {} if ipv6==False and iptc_rule.src != '0.0.0.0/0.0.0.0': _ip, _netmask = iptc_rule.src.split('/') _netmask = _netmask_v4_to_cidr(_netmask) d['src'] = '{}/{}'.format(_ip, _netmask) elif ipv6==True and iptc_rule.src != '::/0': - d['src'] = iptc_rule.src.rstrip('/128') + d['src'] = iptc_rule.src if ipv6==False and iptc_rule.dst != '0.0.0.0/0.0.0.0': _ip, _netmask = iptc_rule.dst.split('/') _netmask = _netmask_v4_to_cidr(_netmask) d['dst'] = '{}/{}'.format(_ip, _netmask) elif ipv6==True and iptc_rule.dst != '::/0': - d['dst'] = iptc_rule.dst.rstrip('/128') + d['dst'] = iptc_rule.dst if iptc_rule.protocol != 'ip': d['protocol'] = iptc_rule.protocol if iptc_rule.in_interface is not None: From b898fa44dc532c2f0adf996e43ccfcb8ff1b65ba Mon Sep 17 00:00:00 2001 From: Jesus Llorente Santos Date: Tue, 9 Apr 2019 20:37:12 +0000 Subject: [PATCH 247/287] Added documentation. Cleanup --- README.md | 40 ++++++++++++++ doc/examples.rst | 29 ++++++++++- iptc/easy.py | 126 ++++++++++++++++++++++----------------------- iptc/ip4tc.py | 119 +----------------------------------------- tests/test_iptc.py | 18 +++---- 5 files changed, 140 insertions(+), 192 deletions(-) diff --git a/README.md b/README.md index 60ffa3c..31d2a3a 100755 --- a/README.md +++ b/README.md @@ -135,6 +135,31 @@ Full documentation with API reference is available Examples ======== +High level abstractions +----------------------- + +``python-iptables`` implements a low-level interface that tries to closely +match the underlying C libraries. The module ``iptc.easy`` improves the +usability of the library by providing a rich set of high-level functions +designed to simplify the interaction with the library, for example:: + + >>> import iptc + >>> iptc.easy.dump_table('nat', ipv6=False) + {'INPUT': [], 'OUTPUT': [], 'POSTROUTING': [], 'PREROUTING': []} + >>> iptc.easy.dump_chain('filter', 'OUTPUT', ipv6=False) + [{'comment': {'comment': 'DNS traffic to Google'}, + 'dst': '8.8.8.8/32', + 'protocol': 'udp', + 'target': 'ACCEPT', + 'udp': {'dport': '53'}}] + >>> iptc.easy.add_chain('filter', 'TestChain') + True + >>> rule_d = {'protocol': 'tcp', 'target': 'ACCEPT', 'tcp': {'dport': '22'}} + >>> iptc.easy.insert_rule('filter', 'TestChain', rule_d) + >>> iptc.easy.dump_chain('filter', 'TestChain') + [{'protocol': 'tcp', 'target': 'ACCEPT', 'tcp': {'dport': '22'}}] + >>> iptc.easy.delete_chain('filter', 'TestChain', flush=True) + Rules ----- @@ -546,6 +571,21 @@ or more rules, than commit it: The drawback is that Table is a singleton, and if you disable autocommit, it will be disabled for all instances of that Table. +Easy rules with dictionaries +---------------------------- +To simplify operations with ``python-iptables`` rules we have included support to define and convert Rules object into python dictionaries. + + >>> import iptc + >>> table = iptc.Table(iptc.Table.FILTER) + >>> chain = iptc.Chain(table, "INPUT") + >>> # Create an iptc.Rule object from dictionary + >>> rule_d = {'comment': {'comment': 'Match tcp.22'}, 'protocol': 'tcp', 'target': 'ACCEPT', 'tcp': {'dport': '22'}} + >>> rule = iptc.easy.encode_iptc_rule(rule_d) + >>> # Obtain a dictionary representation from the iptc.Rule + >>> iptc.easy.decode_iptc_rule(rule) + {'tcp': {'dport': '22'}, 'protocol': 'tcp', 'comment': {'comment': 'Match tcp.22'}, 'target': 'ACCEPT'} + + Known Issues ============ diff --git a/doc/examples.rst b/doc/examples.rst index 1a8fc63..2f6d9a4 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -1,6 +1,31 @@ Examples ======== +High level abstractions +----------------------- + +``python-iptables`` implements a low-level interface that tries to closely +match the underlying C libraries. The module ``iptc.easy`` improves the +usability of the library by providing a rich set of high-level functions +designed to simplify the interaction with the library, for example:: + + >>> import iptc + >>> iptc.easy.dump_table('nat', ipv6=False) + {'INPUT': [], 'OUTPUT': [], 'POSTROUTING': [], 'PREROUTING': []} + >>> iptc.easy.dump_chain('filter', 'OUTPUT', ipv6=False) + [{'comment': {'comment': 'DNS traffic to Google'}, + 'dst': '8.8.8.8/32', + 'protocol': 'udp', + 'target': 'ACCEPT', + 'udp': {'dport': '53'}}] + >>> iptc.easy.add_chain('filter', 'TestChain') + True + >>> rule_d = {'protocol': 'tcp', 'target': 'ACCEPT', 'tcp': {'dport': '22'}} + >>> iptc.easy.insert_rule('filter', 'TestChain', rule_d) + >>> iptc.easy.dump_chain('filter', 'TestChain') + [{'protocol': 'tcp', 'target': 'ACCEPT', 'tcp': {'dport': '22'}}] + >>> iptc.easy.delete_chain('filter', 'TestChain', flush=True) + Rules ----- @@ -419,9 +444,9 @@ To simplify operations with ``python-iptables`` rules we have included support t >>> chain = iptc.Chain(table, "INPUT") >>> # Create an iptc.Rule object from dictionary >>> rule_d = {'comment': {'comment': 'Match tcp.22'}, 'protocol': 'tcp', 'target': 'ACCEPT', 'tcp': {'dport': '22'}} - >>> rule = iptc.Rule.from_dict(rule_d) + >>> rule = iptc.easy.encode_iptc_rule(rule_d) >>> # Obtain a dictionary representation from the iptc.Rule - >>> rule.to_dict() + >>> iptc.easy.decode_iptc_rule(rule) {'tcp': {'dport': '22'}, 'protocol': 'tcp', 'comment': {'comment': 'Match tcp.22'}, 'target': 'ACCEPT'} diff --git a/iptc/easy.py b/iptc/easy.py index def9997..22f9743 100644 --- a/iptc/easy.py +++ b/iptc/easy.py @@ -288,6 +288,69 @@ def batch_delete_rules(table, batch_rules, ipv6=False, raise_exc=True): if raise_exc: raise +def encode_iptc_rule(rule_d, ipv6=False): + """ Return a Rule(6) object from the input dictionary """ + # Sanity check + assert(isinstance(rule_d, dict)) + # Basic rule attributes + rule_attr = ('src', 'dst', 'protocol', 'in-interface', 'out-interface', 'fragment') + iptc_rule = Rule6() if ipv6 else Rule() + # Avoid issues with matches that require basic parameters to be configured first + for name in rule_attr: + if name in rule_d: + _iptc_setrule(iptc_rule, name, rule_d[name]) + for name, value in rule_d.items(): + try: + if name in rule_attr: + continue + elif name == 'target': + _iptc_settarget(iptc_rule, value) + else: + _iptc_setmatch(iptc_rule, name, value) + except Exception as e: + #print('Ignoring unsupported field <{}:{}>'.format(name, value)) + continue + return iptc_rule + +def decode_iptc_rule(iptc_rule, ipv6=False): + """ Return a dictionary representation of the Rule(6) object + Note: host IP addresses are appended their corresponding CIDR """ + d = {} + if ipv6==False and iptc_rule.src != '0.0.0.0/0.0.0.0': + _ip, _netmask = iptc_rule.src.split('/') + _netmask = _netmask_v4_to_cidr(_netmask) + d['src'] = '{}/{}'.format(_ip, _netmask) + elif ipv6==True and iptc_rule.src != '::/0': + d['src'] = iptc_rule.src + if ipv6==False and iptc_rule.dst != '0.0.0.0/0.0.0.0': + _ip, _netmask = iptc_rule.dst.split('/') + _netmask = _netmask_v4_to_cidr(_netmask) + d['dst'] = '{}/{}'.format(_ip, _netmask) + elif ipv6==True and iptc_rule.dst != '::/0': + d['dst'] = iptc_rule.dst + if iptc_rule.protocol != 'ip': + d['protocol'] = iptc_rule.protocol + if iptc_rule.in_interface is not None: + d['in-interface'] = iptc_rule.in_interface + if iptc_rule.out_interface is not None: + d['out-interface'] = iptc_rule.out_interface + if ipv6 == False and iptc_rule.fragment: + d['fragment'] = iptc_rule.fragment + for m in iptc_rule.matches: + if m.name not in d: + d[m.name] = m.get_all_parameters() + elif isinstance(d[m.name], list): + d[m.name].append(m.get_all_parameters()) + else: + d[m.name] = [d[m.name], m.get_all_parameters()] + if iptc_rule.target and iptc_rule.target.name and len(iptc_rule.target.get_all_parameters()): + name = iptc_rule.target.name.replace('-', '_') + d['target'] = {name:iptc_rule.target.get_all_parameters()} + elif iptc_rule.target and iptc_rule.target.name: + d['target'] = iptc_rule.target.name + # Return a filtered dictionary + return _filter_empty_field(d) + ### INTERNAL FUNCTIONS ### def _iptc_table_available(table, ipv6=False): """ Return True if the table is available, False otherwise """ @@ -357,69 +420,6 @@ def _iptc_settarget(iptc_rule, value): else: iptc_target = iptc_rule.create_target(value) -def encode_iptc_rule(rule_d, ipv6=False): - # Sanity check - assert(isinstance(rule_d, dict)) - # Basic rule attributes - rule_attr = ('src', 'dst', 'protocol', 'in-interface', 'out-interface', 'fragment') - iptc_rule = Rule6() if ipv6 else Rule() - # Avoid issues with matches that require basic parameters to be configured first - for name in rule_attr: - if name in rule_d: - _iptc_setrule(iptc_rule, name, rule_d[name]) - for name, value in rule_d.items(): - try: - if name in rule_attr: - #_iptc_setrule(iptc_rule, name, value) - continue - elif name == 'target': - _iptc_settarget(iptc_rule, value) - else: - _iptc_setmatch(iptc_rule, name, value) - except Exception as e: - #print('Ignoring unsupported field <{}:{}>'.format(name, value)) - continue - return iptc_rule - -def decode_iptc_rule(iptc_rule, ipv6=False): - """ Return a dictionary representation of an iptc_rule - Note: host IP addresses are appended their corresponding CIDR """ - d = {} - if ipv6==False and iptc_rule.src != '0.0.0.0/0.0.0.0': - _ip, _netmask = iptc_rule.src.split('/') - _netmask = _netmask_v4_to_cidr(_netmask) - d['src'] = '{}/{}'.format(_ip, _netmask) - elif ipv6==True and iptc_rule.src != '::/0': - d['src'] = iptc_rule.src - if ipv6==False and iptc_rule.dst != '0.0.0.0/0.0.0.0': - _ip, _netmask = iptc_rule.dst.split('/') - _netmask = _netmask_v4_to_cidr(_netmask) - d['dst'] = '{}/{}'.format(_ip, _netmask) - elif ipv6==True and iptc_rule.dst != '::/0': - d['dst'] = iptc_rule.dst - if iptc_rule.protocol != 'ip': - d['protocol'] = iptc_rule.protocol - if iptc_rule.in_interface is not None: - d['in-interface'] = iptc_rule.in_interface - if iptc_rule.out_interface is not None: - d['out-interface'] = iptc_rule.out_interface - if ipv6 == False and iptc_rule.fragment: - d['fragment'] = iptc_rule.fragment - for m in iptc_rule.matches: - if m.name not in d: - d[m.name] = m.get_all_parameters() - elif isinstance(d[m.name], list): - d[m.name].append(m.get_all_parameters()) - else: - d[m.name] = [d[m.name], m.get_all_parameters()] - if iptc_rule.target and iptc_rule.target.name and len(iptc_rule.target.get_all_parameters()): - name = iptc_rule.target.name.replace('-', '_') - d['target'] = {name:iptc_rule.target.get_all_parameters()} - elif iptc_rule.target and iptc_rule.target.name: - d['target'] = iptc_rule.target.name - # Return a filtered dictionary - return _filter_empty_field(d) - def _batch_begin_table(table, ipv6=False): """ Disable autocommit on a table """ iptc_table = _iptc_gettable(table, ipv6) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index f68b259..29d77ea 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -10,7 +10,7 @@ import weakref from .util import find_library, load_kernel -from .xtables import (XT_INV_PROTO, NFPROTO_IPV4, NFPROTO_IPV6, XTablesError, xtables, +from .xtables import (XT_INV_PROTO, NFPROTO_IPV4, XTablesError, xtables, xt_align, xt_counters, xt_entry_target, xt_entry_match) __all__ = ["Table", "Chain", "Rule", "Match", "Target", "Policy", "IPTCError"] @@ -1389,123 +1389,6 @@ def _get_mask(self): mask = property(_get_mask) """This is the raw mask buffer as iptables uses it when removing rules.""" - @classmethod - def from_dict(cls, rule_d): - """Generate a Rule(6) object from the input dictionary.""" - # Sanity check - assert(isinstance(rule_d, dict)) - # Basic rule attributes - rule_attr = ('src', 'dst', 'protocol', 'in-interface', 'out-interface', 'fragment') - iptc_rule = cls() - # Avoid issues with matches that require basic parameters to be configured first - for name in rule_attr: - if name in rule_d: - _iptc_setrule(iptc_rule, name, rule_d[name]) - for name, value in rule_d.items(): - try: - if name in rule_attr: - #_iptc_setrule(iptc_rule, name, value) - continue - elif name == 'target': - _iptc_settarget(iptc_rule, value) - else: - _iptc_setmatch(iptc_rule, name, value) - except Exception as e: - #print('Ignoring unsupported field <{}:{}>'.format(name, value)) - continue - return iptc_rule - - def to_dict(self): - """Generate a dictionary representation of the Rule(6) object.""" - d = {} - if self.nfproto==NFPROTO_IPV4 and self.src != '0.0.0.0/0.0.0.0': - d['src'] = self.src - elif self.nfproto==NFPROTO_IPV6 and self.src != '::/0': - d['src'] = self.src - if self.nfproto==NFPROTO_IPV4 and self.dst != '0.0.0.0/0.0.0.0': - d['dst'] = self.dst - elif self.nfproto==NFPROTO_IPV6 and self.dst != '::/0': - d['dst'] = self.dst - if self.protocol != 'ip': - d['protocol'] = self.protocol - if self.in_interface is not None: - d['in-interface'] = self.in_interface - if self.out_interface is not None: - d['out-interface'] = self.out_interface - if self.nfproto==NFPROTO_IPV4 and self.fragment: - d['fragment'] = self.fragment - for m in self.matches: - if m.name not in d: - d[m.name] = m.get_all_parameters() - elif isinstance(d[m.name], list): - d[m.name].append(m.get_all_parameters()) - else: - d[m.name] = [d[m.name], m.get_all_parameters()] - if self.target and self.target.name and len(self.target.get_all_parameters()): - name = self.target.name.replace('-', '_') - d['target'] = {name:self.target.get_all_parameters()} - elif self.target and self.target.name: - d['target'] = self.target.name - # Return a filtered dictionary - return _filter_empty_field(d) - -# Helper functions for dictionary operations over Rule(6) objects -def _iptc_setattr(object, name, value): - # Translate attribute name - name = name.replace('-', '_') - setattr(object, name, value) - -def _iptc_setattr_d(object, value_d): - for name, value in value_d.items(): - _iptc_setattr(object, name, value) - -def _iptc_setrule(iptc_rule, name, value): - _iptc_setattr(iptc_rule, name, value) - -def _iptc_setmatch(iptc_rule, name, value): - # Iterate list/tuple recursively - if isinstance(value, list) or isinstance(value, tuple): - for inner_value in value: - _iptc_setmatch(iptc_rule, name, inner_value) - # Assign dictionary value - elif isinstance(value, dict): - iptc_match = iptc_rule.create_match(name) - _iptc_setattr_d(iptc_match, value) - # Assign value directly - else: - iptc_match = iptc_rule.create_match(name) - _iptc_setattr(iptc_match, name, value) - -def _iptc_settarget(iptc_rule, value): - # Target is dictionary - Use only 1 pair key/value - if isinstance(value, dict): - for k, v in value.items(): - iptc_target = iptc_rule.create_target(k) - _iptc_setattr_d(iptc_target, v) - return - # Simple target - else: - iptc_target = iptc_rule.create_target(value) - -def _filter_empty_field(data_d): - """ - Remove empty lists from dictionary values - Before: {'target': {'CHECKSUM': {'checksum-fill': []}}} - After: {'target': {'CHECKSUM': {'checksum-fill': ''}}} - Before: {'tcp': {'dport': ['22']}}} - After: {'tcp': {'dport': '22'}}} - """ - for k, v in data_d.items(): - if isinstance(v, dict): - data_d[k] = _filter_empty_field(v) - elif isinstance(v, list) and len(v) != 0: - v = [_filter_empty_field(_v) if isinstance(_v, dict) else _v for _v in v ] - if isinstance(v, list) and len(v) == 1: - data_d[k] = v.pop() - elif isinstance(v, list) and len(v) == 0: - data_d[k] = '' - return data_d - class Chain(object): """Rules are contained by chains. diff --git a/tests/test_iptc.py b/tests/test_iptc.py index c01abef..519e228 100755 --- a/tests/test_iptc.py +++ b/tests/test_iptc.py @@ -577,19 +577,19 @@ def test_rule_insert(self): def test_rule_to_dict(self): rule = iptc.Rule6() rule.protocol = "tcp" - rule.src = "::1" + rule.src = "::1/128" target = iptc.Target(rule, "ACCEPT") rule.target = target - rule_d = rule.to_dict() + rule_d = iptc.easy.decode_iptc_rule(rule, ipv6=True) self.assertEqual(rule_d, {"protocol": "tcp", "src": "::1/128", "target": "ACCEPT"}) def test_rule_from_dict(self): rule = iptc.Rule6() rule.protocol = "tcp" - rule.src = "::1" + rule.src = "::1/128" target = iptc.Target(rule, "ACCEPT") rule.target = target - rule2 = iptc.Rule6.from_dict({"protocol": "tcp", "src": "::1/128", "target": "ACCEPT"}) + rule2 = iptc.easy.encode_iptc_rule({"protocol": "tcp", "src": "::1/128", "target": "ACCEPT"}, ipv6=True) self.assertEqual(rule, rule2) class TestRule(unittest.TestCase): @@ -935,19 +935,19 @@ def test_rule_delete_nat(self): def test_rule_to_dict(self): rule = iptc.Rule() rule.protocol = "tcp" - rule.src = "127.0.0.1" + rule.src = "127.0.0.1/32" target = iptc.Target(rule, "ACCEPT") rule.target = target - rule_d = rule.to_dict() - self.assertEqual(rule_d, {"protocol": "tcp", "src": "127.0.0.1/255.255.255.255", "target": "ACCEPT"}) + rule_d = iptc.easy.decode_iptc_rule(rule) + self.assertEqual(rule_d, {"protocol": "tcp", "src": "127.0.0.1/32", "target": "ACCEPT"}) def test_rule_from_dict(self): rule = iptc.Rule() rule.protocol = "tcp" - rule.src = "127.0.0.1" + rule.src = "127.0.0.1/32" target = iptc.Target(rule, "ACCEPT") rule.target = target - rule2 = iptc.Rule.from_dict({"protocol": "tcp", "src": "127.0.0.1/255.255.255.255", "target": "ACCEPT"}) + rule2 = iptc.easy.encode_iptc_rule({"protocol": "tcp", "src": "127.0.0.1/32", "target": "ACCEPT"}) self.assertEqual(rule, rule2) def suite(): From ce1936f7f4c6de81944cd325db80e9f5ce00330f Mon Sep 17 00:00:00 2001 From: Jesus Llorente Santos Date: Sat, 18 May 2019 18:26:21 +0000 Subject: [PATCH 248/287] Fix comparison of Match instances and update test_matches file --- iptc/ip4tc.py | 4 +--- tests/test_matches.py | 54 ++++++++++++++++++++++--------------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 29d77ea..0bec5a5 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -574,9 +574,7 @@ def _check_alias(self): def __eq__(self, match): basesz = ct.sizeof(xt_entry_match) - if (self.match.u.match_size == match.match.u.match_size and - self.match.u.user.name == match.match.u.user.name and - self.match.u.user.revision == match.match.u.user.revision and + if (self.name == match.name and self.match_buf[basesz:self.usersize] == match.match_buf[basesz:match.usersize]): return True diff --git a/tests/test_matches.py b/tests/test_matches.py index 1bf86dd..063cfb4 100755 --- a/tests/test_matches.py +++ b/tests/test_matches.py @@ -19,7 +19,7 @@ def test_match_create(self): match = rule.create_match("udp") for m in rule.matches: - self.failUnless(m == match) + self.assertEqual(m, match) # check that we can change match parameters after creation match.sport = "12345:55555" @@ -29,7 +29,7 @@ def test_match_create(self): m.sport = "12345:55555" m.dport = "!33333" - self.failUnless(m == match) + self.assertEqual(m, match) def test_match_compare(self): m1 = iptc.Match(iptc.Rule(), "udp") @@ -40,28 +40,28 @@ def test_match_compare(self): m2.sport = "12345:55555" m2.dport = "!33333" - self.failUnless(m1 == m2) + self.assertEqual(m1, m2) m2.reset() m2.sport = "12345:55555" m2.dport = "33333" - self.failIf(m1 == m2) + self.assertNotEqual(m1, m2) def test_match_parameters(self): m = iptc.Match(iptc.Rule(), "udp") m.sport = "12345:55555" m.dport = "!33333" - self.failUnless(len(m.parameters) == 2) + self.assertEqual(len(m.parameters), 2) for p in m.parameters: - self.failUnless(p == "sport" or p == "dport") + self.assertTrue(p == "sport" or p == "dport") - self.failUnless(m.parameters["sport"] == "12345:55555") - self.failUnless(m.parameters["dport"] == "!33333") + self.assertEqual(m.parameters["sport"], "12345:55555") + self.assertEqual(m.parameters["dport"], "!33333") m.reset() - self.failUnless(len(m.parameters) == 0) + self.assertEqual(len(m.parameters), 0) def test_get_all_parameters(self): m = iptc.Match(iptc.Rule(), "udp") @@ -69,8 +69,8 @@ def test_get_all_parameters(self): m.dport = "!33333" params = m.get_all_parameters() - self.assertEquals(set(params['sport']), set(['12345:55555'])) - self.assertEquals(set(params['dport']), set(['!', '33333'])) + self.assertEqual(set(params['sport']), set(['12345:55555'])) + self.assertEqual(set(params['dport']), set(['!', '33333'])) class TestMultiportMatch(unittest.TestCase): @@ -103,14 +103,14 @@ def test_multiport(self): self.chain.insert_rule(self.rule) rule = self.chain.rules[0] match = rule.matches[0] - self.assertEquals(match.dports, '1111,2222') + self.assertEqual(match.dports, '1111,2222') def test_unicode_multiport(self): self.match.dports = u'1111,2222' self.chain.insert_rule(self.rule) rule = self.chain.rules[0] match = rule.matches[0] - self.assertEquals(match.dports, '1111,2222') + self.assertEqual(match.dports, '1111,2222') class TestXTUdpMatch(unittest.TestCase): @@ -135,9 +135,9 @@ def test_udp_port(self): "!12345:12346", "0:1234", "! 1234", "!0:12345", "!1234:65535"]: self.match.sport = port - self.assertEquals(self.match.sport, port.replace(" ", "")) + self.assertEqual(self.match.sport, port.replace(" ", "")) self.match.dport = port - self.assertEquals(self.match.dport, port.replace(" ", "")) + self.assertEqual(self.match.dport, port.replace(" ", "")) self.match.reset() for port in ["-1", "asdf", "!asdf"]: try: @@ -188,7 +188,7 @@ def tearDown(self): def test_mark(self): for mark in ["0x7b", "! 0x7b", "0x7b/0xfffefffe", "!0x7b/0xff00ff00"]: self.match.mark = mark - self.assertEquals(self.match.mark, mark.replace(" ", "")) + self.assertEqual(self.match.mark, mark.replace(" ", "")) self.match.reset() for mark in ["0xffffffffff", "123/0xffffffff1", "!asdf", "1234:1233"]: try: @@ -232,7 +232,7 @@ def tearDown(self): def test_limit(self): for limit in ["1/sec", "5/min", "3/hour"]: self.match.limit = limit - self.assertEquals(self.match.limit, limit) + self.assertEqual(self.match.limit, limit) self.match.reset() for limit in ["asdf", "123/1", "!1", "!1/second"]: try: @@ -284,7 +284,7 @@ def tearDown(self): def test_icmpv6(self): self.chain.insert_rule(self.rule) rule = self.chain.rules[0] - self.assertEquals(self.rule, rule) + self.assertEqual(self.rule, rule) class TestCommentMatch(unittest.TestCase): @@ -310,7 +310,7 @@ def test_comment(self): self.match.reset() self.match.comment = comment self.chain.insert_rule(self.rule) - self.assertEquals(self.match.comment, comment) + self.assertEqual(self.match.comment, comment) class TestIprangeMatch(unittest.TestCase): @@ -389,8 +389,10 @@ def test_state(self): self.chain.insert_rule(self.rule) rule = self.chain.rules[0] m = rule.matches[0] - self.assertEquals(m.name, "state") - self.assertEquals(m.state, "RELATED,ESTABLISHED") + self.assertEqual(m.name, "state") + self.assertEqual(m.state, "RELATED,ESTABLISHED") + self.assertEqual(rule.matches[0].name, self.rule.matches[0].name) + self.assertEqual(rule, self.rule) class TestXTConntrackMatch(unittest.TestCase): @@ -425,7 +427,7 @@ def test_state(self): rule = self.chain.rules[0] m = rule.matches[0] self.assertTrue(m.name, ["conntrack"]) - self.assertEquals(m.ctstate, "NEW,RELATED") + self.assertEqual(m.ctstate, "NEW,RELATED") class TestHashlimitMatch(unittest.TestCase): @@ -464,10 +466,10 @@ def test_hashlimit(self): rule = self.chain.rules[0] m = rule.matches[0] self.assertTrue(m.name, ["hashlimit"]) - self.assertEquals(m.hashlimit_name, "foo") - self.assertEquals(m.hashlimit_mode, "srcip") - self.assertEquals(m.hashlimit_upto, "200/sec") - self.assertEquals(m.hashlimit_burst, "5") + self.assertEqual(m.hashlimit_name, "foo") + self.assertEqual(m.hashlimit_mode, "srcip") + self.assertEqual(m.hashlimit_upto, "200/sec") + self.assertEqual(m.hashlimit_burst, "5") def suite(): From 54c63684f91457dba46095b7f4e4668bdb421e14 Mon Sep 17 00:00:00 2001 From: Jesus Llorente Santos Date: Sun, 19 May 2019 19:00:55 +0000 Subject: [PATCH 249/287] Add support for _missing_ and _goto_ target in iptc.easy module --- iptc/easy.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/iptc/easy.py b/iptc/easy.py index 22f9743..3eb056c 100644 --- a/iptc/easy.py +++ b/iptc/easy.py @@ -295,6 +295,8 @@ def encode_iptc_rule(rule_d, ipv6=False): # Basic rule attributes rule_attr = ('src', 'dst', 'protocol', 'in-interface', 'out-interface', 'fragment') iptc_rule = Rule6() if ipv6 else Rule() + # Set default target + rule_d.setdefault('target', '') # Avoid issues with matches that require basic parameters to be configured first for name in rule_attr: if name in rule_d: @@ -347,7 +349,10 @@ def decode_iptc_rule(iptc_rule, ipv6=False): name = iptc_rule.target.name.replace('-', '_') d['target'] = {name:iptc_rule.target.get_all_parameters()} elif iptc_rule.target and iptc_rule.target.name: - d['target'] = iptc_rule.target.name + if iptc_rule.target.goto: + d['target'] = {'goto':iptc_rule.target.name} + else: + d['target'] = iptc_rule.target.name # Return a filtered dictionary return _filter_empty_field(d) @@ -412,10 +417,12 @@ def _iptc_setmatch(iptc_rule, name, value): def _iptc_settarget(iptc_rule, value): # Target is dictionary - Use only 1 pair key/value if isinstance(value, dict): - for k, v in value.items(): - iptc_target = iptc_rule.create_target(k) - _iptc_setattr_d(iptc_target, v) - return + t_name, t_value = next(iter(value.items())) + if t_name == 'goto': + iptc_target = iptc_rule.create_target(t_value, goto=True) + else: + iptc_target = iptc_rule.create_target(t_name) + _iptc_setattr_d(iptc_target, t_value) # Simple target else: iptc_target = iptc_rule.create_target(value) From 57a7cc5f12f3941e7aafef0aed3025892ad8acf6 Mon Sep 17 00:00:00 2001 From: Jesus Llorente Santos Date: Sun, 19 May 2019 19:09:20 +0000 Subject: [PATCH 250/287] Add documentation for goto rules with iptc.easy --- README.md | 7 ++++++- doc/examples.rst | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 31d2a3a..8d391aa 100755 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ High level abstractions ``python-iptables`` implements a low-level interface that tries to closely match the underlying C libraries. The module ``iptc.easy`` improves the usability of the library by providing a rich set of high-level functions -designed to simplify the interaction with the library, for example:: +designed to simplify the interaction with the library, for example: >>> import iptc >>> iptc.easy.dump_table('nat', ipv6=False) @@ -160,6 +160,11 @@ designed to simplify the interaction with the library, for example:: [{'protocol': 'tcp', 'target': 'ACCEPT', 'tcp': {'dport': '22'}}] >>> iptc.easy.delete_chain('filter', 'TestChain', flush=True) + >>> # Example of goto rule // iptables -A FORWARD -p gre -g TestChainGoto + >>> iptc.easy.add_chain('filter', 'TestChainGoto') + >>> rule_goto_d = {'protocol': 'gre', 'target': {'goto': 'TestChainGoto'}} + >>> iptc.easy.insert_rule('filter', 'FORWARD', rule_goto_d) + Rules ----- diff --git a/doc/examples.rst b/doc/examples.rst index 2f6d9a4..b489e08 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -7,7 +7,7 @@ High level abstractions ``python-iptables`` implements a low-level interface that tries to closely match the underlying C libraries. The module ``iptc.easy`` improves the usability of the library by providing a rich set of high-level functions -designed to simplify the interaction with the library, for example:: +designed to simplify the interaction with the library, for example: >>> import iptc >>> iptc.easy.dump_table('nat', ipv6=False) @@ -26,6 +26,11 @@ designed to simplify the interaction with the library, for example:: [{'protocol': 'tcp', 'target': 'ACCEPT', 'tcp': {'dport': '22'}}] >>> iptc.easy.delete_chain('filter', 'TestChain', flush=True) + >>> # Example of goto rule // iptables -A FORWARD -p gre -g TestChainGoto + >>> iptc.easy.add_chain('filter', 'TestChainGoto') + >>> rule_goto_d = {'protocol': 'gre', 'target': {'goto': 'TestChainGoto'}} + >>> iptc.easy.insert_rule('filter', 'FORWARD', rule_goto_d) + Rules ----- From 9f348f0fa93f4b4a00104e825241be25674019fc Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Wed, 22 May 2019 15:15:26 -0700 Subject: [PATCH 251/287] Update list of Python test envs on Travis --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 23e2a11..e7f9a8c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,11 @@ +dist: xenial language: python python: - - "2.6" - "2.7" - "3.4" + - "3.5" + - "3.6" + - "3.7" install: - python setup.py build - python setup.py install From eb5915a9038b2009a147c34ced191baecd440245 Mon Sep 17 00:00:00 2001 From: Jesus Llorente Santos Date: Sun, 19 May 2019 19:37:13 +0000 Subject: [PATCH 252/287] Apply subnet mask to IP address --- iptc/ip4tc.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 29d77ea..b48eaa6 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -1064,9 +1064,6 @@ def set_src(self, src): saddr = _a_to_i(socket.inet_pton(socket.AF_INET, addr)) except socket.error: raise ValueError("invalid address %s" % (addr)) - ina = in_addr() - ina.s_addr = ct.c_uint32(saddr) - self.entry.ip.src = ina if not netm.isdigit(): try: @@ -1080,8 +1077,11 @@ def set_src(self, src): nmask = socket.htonl((2 ** imask - 1) << (32 - imask)) neta = in_addr() neta.s_addr = ct.c_uint32(nmask) - self.entry.ip.smsk = neta + # Apply subnet mask to IP address + ina = in_addr() + ina.s_addr = ct.c_uint32(saddr & nmask) + self.entry.ip.src = ina src = property(get_src, set_src) """This is the source network address with an optional network mask in @@ -1125,9 +1125,6 @@ def set_dst(self, dst): daddr = _a_to_i(socket.inet_pton(socket.AF_INET, addr)) except socket.error: raise ValueError("invalid address %s" % (addr)) - ina = in_addr() - ina.s_addr = ct.c_uint32(daddr) - self.entry.ip.dst = ina if not netm.isdigit(): try: @@ -1142,6 +1139,10 @@ def set_dst(self, dst): neta = in_addr() neta.s_addr = ct.c_uint32(nmask) self.entry.ip.dmsk = neta + # Apply subnet mask to IP address + ina = in_addr() + ina.s_addr = ct.c_uint32(daddr & nmask) + self.entry.ip.dst = ina dst = property(get_dst, set_dst) """This is the destination network address with an optional network mask From d788d71fc74765bf01a92a3f990e79a65cce4b30 Mon Sep 17 00:00:00 2001 From: Jesus Llorente Santos Date: Wed, 22 May 2019 18:40:44 +0000 Subject: [PATCH 253/287] Fix test_rule_address to compare against valid addresses --- tests/test_iptc.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/test_iptc.py b/tests/test_iptc.py index 519e228..1d9f775 100755 --- a/tests/test_iptc.py +++ b/tests/test_iptc.py @@ -620,13 +620,13 @@ def tearDown(self): def test_rule_address(self): # valid addresses rule = iptc.Rule() - for addr in [("127.0.0.1/255.255.255.0", "127.0.0.1/255.255.255.0"), - ("!127.0.0.1/255.255.255.0", "!127.0.0.1/255.255.255.0"), - ("127.0.0.1/255.255.128.0", "127.0.0.1/255.255.128.0"), - ("127.0.0.1/16", "127.0.0.1/255.255.0.0"), - ("127.0.0.1/24", "127.0.0.1/255.255.255.0"), - ("127.0.0.1/17", "127.0.0.1/255.255.128.0"), - ("!127.0.0.1/17", "!127.0.0.1/255.255.128.0")]: + for addr in [("127.0.0.1/255.255.255.0", "127.0.0.0/255.255.255.0"), + ("!127.0.0.1/255.255.255.0", "!127.0.0.0/255.255.255.0"), + ("127.0.0.1/255.255.128.0", "127.0.0.0/255.255.128.0"), + ("127.0.0.1/16", "127.0.0.0/255.255.0.0"), + ("127.0.0.1/24", "127.0.0.0/255.255.255.0"), + ("127.0.0.1/17", "127.0.0.0/255.255.128.0"), + ("!127.0.0.1/17", "!127.0.0.0/255.255.128.0")]: rule.src = addr[0] self.assertEquals(rule.src, addr[1]) rule.dst = addr[0] From ee827b3fc7a9b783a50682ae149c64b0347cbc02 Mon Sep 17 00:00:00 2001 From: Jesus Llorente Santos Date: Sun, 19 May 2019 19:00:55 +0000 Subject: [PATCH 254/287] Add support for _missing_ and _goto_ target in iptc.easy module --- iptc/easy.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/iptc/easy.py b/iptc/easy.py index 22f9743..3eb056c 100644 --- a/iptc/easy.py +++ b/iptc/easy.py @@ -295,6 +295,8 @@ def encode_iptc_rule(rule_d, ipv6=False): # Basic rule attributes rule_attr = ('src', 'dst', 'protocol', 'in-interface', 'out-interface', 'fragment') iptc_rule = Rule6() if ipv6 else Rule() + # Set default target + rule_d.setdefault('target', '') # Avoid issues with matches that require basic parameters to be configured first for name in rule_attr: if name in rule_d: @@ -347,7 +349,10 @@ def decode_iptc_rule(iptc_rule, ipv6=False): name = iptc_rule.target.name.replace('-', '_') d['target'] = {name:iptc_rule.target.get_all_parameters()} elif iptc_rule.target and iptc_rule.target.name: - d['target'] = iptc_rule.target.name + if iptc_rule.target.goto: + d['target'] = {'goto':iptc_rule.target.name} + else: + d['target'] = iptc_rule.target.name # Return a filtered dictionary return _filter_empty_field(d) @@ -412,10 +417,12 @@ def _iptc_setmatch(iptc_rule, name, value): def _iptc_settarget(iptc_rule, value): # Target is dictionary - Use only 1 pair key/value if isinstance(value, dict): - for k, v in value.items(): - iptc_target = iptc_rule.create_target(k) - _iptc_setattr_d(iptc_target, v) - return + t_name, t_value = next(iter(value.items())) + if t_name == 'goto': + iptc_target = iptc_rule.create_target(t_value, goto=True) + else: + iptc_target = iptc_rule.create_target(t_name) + _iptc_setattr_d(iptc_target, t_value) # Simple target else: iptc_target = iptc_rule.create_target(value) From 7aeadfb79d6f96c69c950a86d8a3737f55caa3d0 Mon Sep 17 00:00:00 2001 From: Jesus Llorente Santos Date: Sun, 19 May 2019 19:09:20 +0000 Subject: [PATCH 255/287] Add documentation for goto rules with iptc.easy --- README.md | 7 ++++++- doc/examples.rst | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 31d2a3a..8d391aa 100755 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ High level abstractions ``python-iptables`` implements a low-level interface that tries to closely match the underlying C libraries. The module ``iptc.easy`` improves the usability of the library by providing a rich set of high-level functions -designed to simplify the interaction with the library, for example:: +designed to simplify the interaction with the library, for example: >>> import iptc >>> iptc.easy.dump_table('nat', ipv6=False) @@ -160,6 +160,11 @@ designed to simplify the interaction with the library, for example:: [{'protocol': 'tcp', 'target': 'ACCEPT', 'tcp': {'dport': '22'}}] >>> iptc.easy.delete_chain('filter', 'TestChain', flush=True) + >>> # Example of goto rule // iptables -A FORWARD -p gre -g TestChainGoto + >>> iptc.easy.add_chain('filter', 'TestChainGoto') + >>> rule_goto_d = {'protocol': 'gre', 'target': {'goto': 'TestChainGoto'}} + >>> iptc.easy.insert_rule('filter', 'FORWARD', rule_goto_d) + Rules ----- diff --git a/doc/examples.rst b/doc/examples.rst index 2f6d9a4..b489e08 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -7,7 +7,7 @@ High level abstractions ``python-iptables`` implements a low-level interface that tries to closely match the underlying C libraries. The module ``iptc.easy`` improves the usability of the library by providing a rich set of high-level functions -designed to simplify the interaction with the library, for example:: +designed to simplify the interaction with the library, for example: >>> import iptc >>> iptc.easy.dump_table('nat', ipv6=False) @@ -26,6 +26,11 @@ designed to simplify the interaction with the library, for example:: [{'protocol': 'tcp', 'target': 'ACCEPT', 'tcp': {'dport': '22'}}] >>> iptc.easy.delete_chain('filter', 'TestChain', flush=True) + >>> # Example of goto rule // iptables -A FORWARD -p gre -g TestChainGoto + >>> iptc.easy.add_chain('filter', 'TestChainGoto') + >>> rule_goto_d = {'protocol': 'gre', 'target': {'goto': 'TestChainGoto'}} + >>> iptc.easy.insert_rule('filter', 'FORWARD', rule_goto_d) + Rules ----- From 8f52d820d48070f0756f239760c1d35e4dba168a Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Tue, 4 Jun 2019 15:48:27 -0700 Subject: [PATCH 256/287] Release 0.14.0 --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index 978a432..4f11b38 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.14.0-dev" +__version__ = "0.14.0" From cc1af53e4e650a8f0da052c9b74377579949269d Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Tue, 4 Jun 2019 15:52:37 -0700 Subject: [PATCH 257/287] Start 0.15.0-dev --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index 4f11b38..1ded765 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.14.0" +__version__ = "0.15.0-dev" From 1b83634b19ef5276669ad40af65a85d5e76ba50b Mon Sep 17 00:00:00 2001 From: Jesus Llorente Santos Date: Tue, 25 Jun 2019 22:17:14 +0000 Subject: [PATCH 258/287] Add unittest for failing match recent module --- tests/test_matches.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/tests/test_matches.py b/tests/test_matches.py index 063cfb4..2093ad2 100755 --- a/tests/test_matches.py +++ b/tests/test_matches.py @@ -471,6 +471,35 @@ def test_hashlimit(self): self.assertEqual(m.hashlimit_upto, "200/sec") self.assertEqual(m.hashlimit_burst, "5") +class TestRecentMatch(unittest.TestCase): + def setUp(self): + self.table = 'filter' + self.chain = 'iptc_test_recent' + iptc.easy.delete_chain(self.table, self.chain, ipv6=False, flush=True, raise_exc=False) + iptc.easy.add_chain(self.table, self.chain, ipv6=False, raise_exc=True) + + def tearDown(self): + iptc.easy.delete_chain(self.table, self.chain, ipv6=False, flush=True, raise_exc=False) + + def test_recent(self): + rule_d = { + 'protocol': 'udp', + 'recent': { + 'mask': '255.255.255.255', + 'update': '', + 'seconds': '60', + 'rsource': '', + 'name': 'UDP-PORTSCAN', + }, + 'target': { + 'REJECT':{ + 'reject-with': 'icmp-port-unreachable' + } + } + } + iptc.easy.add_rule(self.table, self.chain, rule_d) + rule2_d = iptc.easy.get_rule(self.table, self.chain, -1) + self.assertEqual(rule_d, rule2_d) def suite(): suite_match = unittest.TestLoader().loadTestsFromTestCase(TestMatch) @@ -488,6 +517,8 @@ def suite(): TestXTConntrackMatch) suite_hashlimit = unittest.TestLoader().loadTestsFromTestCase( TestHashlimitMatch) + suite_recent = unittest.TestLoader().loadTestsFromTestCase( + TestRecentMatch) extra_suites = [] if is_table6_available(iptc.Table6.FILTER): extra_suites += unittest.TestLoader().loadTestsFromTestCase( @@ -496,7 +527,7 @@ def suite(): return unittest.TestSuite([suite_match, suite_udp, suite_mark, suite_limit, suite_mport, suite_comment, suite_iprange, suite_state, suite_conntrack, - suite_hashlimit] + extra_suites) + suite_hashlimit, suite_recent] + extra_suites) def run_tests(): From 9b1300f34ad46ea3f92139689e0fd25bd97be505 Mon Sep 17 00:00:00 2001 From: Jesus Llorente Santos Date: Tue, 25 Jun 2019 22:32:01 +0000 Subject: [PATCH 259/287] Fix set attribute functions for match, target and rule --- iptc/easy.py | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/iptc/easy.py b/iptc/easy.py index 3eb056c..7f17a4c 100644 --- a/iptc/easy.py +++ b/iptc/easy.py @@ -300,7 +300,7 @@ def encode_iptc_rule(rule_d, ipv6=False): # Avoid issues with matches that require basic parameters to be configured first for name in rule_attr: if name in rule_d: - _iptc_setrule(iptc_rule, name, rule_d[name]) + setattr(iptc_rule, name.replace('-', '_'), rule_d[name]) for name, value in rule_d.items(): try: if name in rule_attr: @@ -388,18 +388,6 @@ def _iptc_getchain(table, chain, ipv6=False, raise_exc=True): except Exception as e: if raise_exc: raise -def _iptc_setattr(object, name, value): - # Translate attribute name - name = name.replace('-', '_') - setattr(object, name, value) - -def _iptc_setattr_d(object, value_d): - for name, value in value_d.items(): - _iptc_setattr(object, name, value) - -def _iptc_setrule(iptc_rule, name, value): - _iptc_setattr(iptc_rule, name, value) - def _iptc_setmatch(iptc_rule, name, value): # Iterate list/tuple recursively if isinstance(value, list) or isinstance(value, tuple): @@ -408,21 +396,21 @@ def _iptc_setmatch(iptc_rule, name, value): # Assign dictionary value elif isinstance(value, dict): iptc_match = iptc_rule.create_match(name) - _iptc_setattr_d(iptc_match, value) + [iptc_match.set_parameter(k, v) for k, v in value.items()] # Assign value directly else: iptc_match = iptc_rule.create_match(name) - _iptc_setattr(iptc_match, name, value) + iptc_match.set_parameter(name, value) def _iptc_settarget(iptc_rule, value): - # Target is dictionary - Use only 1 pair key/value + # Target is dictionary - Use only 1st pair key/value if isinstance(value, dict): t_name, t_value = next(iter(value.items())) if t_name == 'goto': iptc_target = iptc_rule.create_target(t_value, goto=True) else: iptc_target = iptc_rule.create_target(t_name) - _iptc_setattr_d(iptc_target, t_value) + [iptc_target.set_parameter(k, v) for k, v in t_value.items()] # Simple target else: iptc_target = iptc_rule.create_target(value) From 6bf2cf1b8d34fa1140e4170d940421bec5fe0d6c Mon Sep 17 00:00:00 2001 From: Fabian Dellwing Date: Tue, 8 Oct 2019 08:14:10 +0200 Subject: [PATCH 260/287] use absolute path for ldconfig --- iptc/xtables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index 179649d..fafb6af 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -808,7 +808,7 @@ class xtables_target(ct.Union): ldconfig_path_regex = re.compile('^(/.*):$') import subprocess ldconfig = subprocess.Popen( - ('ldconfig', '-N', '-v'), + ('/sbin/ldconfig', '-N', '-v'), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True ) ldconfig_out, ldconfig_err = ldconfig.communicate() From c4dced49810299a781d7eb41e25a5b8a10e4c8d7 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Wed, 16 Oct 2019 11:12:44 -0700 Subject: [PATCH 261/287] This fixes #271 --- iptc/ip4tc.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 7080d50..86c1c8b 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -419,6 +419,7 @@ def get_all_parameters(self): res = shlex.split(buf) res.reverse() inv = False + key = None while len(res) > 0: x = res.pop() if x == '!': @@ -433,7 +434,11 @@ def get_all_parameters(self): params[key] = [] inv = False continue - params[key].append(x) # This is a parameter value. + # At this point key should be set, unless the output from save is + # not formatted right. Let's be defensive, since some users + # reported that problem. + if key is not None: + params[key].append(x) # This is a parameter value. return params def _update_parameters(self): From 7fe9a9982bb9ddb0fa09cb12a0bc9f3d1001b382 Mon Sep 17 00:00:00 2001 From: Jesus Llorente Date: Mon, 28 Oct 2019 22:29:35 +0100 Subject: [PATCH 262/287] Extended rule counters capabilities (#289) * Extended rule counters capabilities * Remove counters in tests when comparing rules --- README.md | 1 + doc/examples.rst | 1 + iptc/easy.py | 8 ++++++++ iptc/ip4tc.py | 9 +++++++++ tests/test_iptc.py | 4 ++++ tests/test_matches.py | 2 ++ 6 files changed, 25 insertions(+) diff --git a/README.md b/README.md index 8d391aa..bd966b4 100755 --- a/README.md +++ b/README.md @@ -148,6 +148,7 @@ designed to simplify the interaction with the library, for example: {'INPUT': [], 'OUTPUT': [], 'POSTROUTING': [], 'PREROUTING': []} >>> iptc.easy.dump_chain('filter', 'OUTPUT', ipv6=False) [{'comment': {'comment': 'DNS traffic to Google'}, + 'counters': (1, 56), 'dst': '8.8.8.8/32', 'protocol': 'udp', 'target': 'ACCEPT', diff --git a/doc/examples.rst b/doc/examples.rst index b489e08..98c6378 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -14,6 +14,7 @@ designed to simplify the interaction with the library, for example: {'INPUT': [], 'OUTPUT': [], 'POSTROUTING': [], 'PREROUTING': []} >>> iptc.easy.dump_chain('filter', 'OUTPUT', ipv6=False) [{'comment': {'comment': 'DNS traffic to Google'}, + 'counters': (1, 56), 'dst': '8.8.8.8/32', 'protocol': 'udp', 'target': 'ACCEPT', diff --git a/iptc/easy.py b/iptc/easy.py index 7f17a4c..d4df85e 100644 --- a/iptc/easy.py +++ b/iptc/easy.py @@ -305,6 +305,8 @@ def encode_iptc_rule(rule_d, ipv6=False): try: if name in rule_attr: continue + elif name == 'counters': + _iptc_setcounters(iptc_rule, value) elif name == 'target': _iptc_settarget(iptc_rule, value) else: @@ -353,6 +355,8 @@ def decode_iptc_rule(iptc_rule, ipv6=False): d['target'] = {'goto':iptc_rule.target.name} else: d['target'] = iptc_rule.target.name + # Get counters + d['counters'] = iptc_rule.counters # Return a filtered dictionary return _filter_empty_field(d) @@ -388,6 +392,10 @@ def _iptc_getchain(table, chain, ipv6=False, raise_exc=True): except Exception as e: if raise_exc: raise +def _iptc_setcounters(iptc_rule, value): + # Value is a tuple (numberOfBytes, numberOfPackets) + iptc_rule.counters = value + def _iptc_setmatch(iptc_rule, name, value): # Iterate list/tuple recursively if isinstance(value, list) or isinstance(value, tuple): diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 86c1c8b..4c5d690 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -1286,6 +1286,15 @@ def get_counters(self): counters = self.entry.counters return counters.pcnt, counters.bcnt + def set_counters(self, counters): + """This method set a tuple pair of the packet and byte counters of + the rule.""" + self.entry.counters.pcnt = counters[0] + self.entry.counters.bcnt = counters[1] + + counters = property(get_counters, set_counters) + """This is the packet and byte counters of the rule.""" + # override the following three for the IPv6 subclass def _entry_size(self): return xt_align(ct.sizeof(ipt_entry)) diff --git a/tests/test_iptc.py b/tests/test_iptc.py index 1d9f775..d630c87 100755 --- a/tests/test_iptc.py +++ b/tests/test_iptc.py @@ -581,6 +581,8 @@ def test_rule_to_dict(self): target = iptc.Target(rule, "ACCEPT") rule.target = target rule_d = iptc.easy.decode_iptc_rule(rule, ipv6=True) + # Remove counters when comparing rules + rule_d.pop('counters', None) self.assertEqual(rule_d, {"protocol": "tcp", "src": "::1/128", "target": "ACCEPT"}) def test_rule_from_dict(self): @@ -939,6 +941,8 @@ def test_rule_to_dict(self): target = iptc.Target(rule, "ACCEPT") rule.target = target rule_d = iptc.easy.decode_iptc_rule(rule) + # Remove counters when comparing rules + rule_d.pop('counters', None) self.assertEqual(rule_d, {"protocol": "tcp", "src": "127.0.0.1/32", "target": "ACCEPT"}) def test_rule_from_dict(self): diff --git a/tests/test_matches.py b/tests/test_matches.py index 2093ad2..d7c1995 100755 --- a/tests/test_matches.py +++ b/tests/test_matches.py @@ -499,6 +499,8 @@ def test_recent(self): } iptc.easy.add_rule(self.table, self.chain, rule_d) rule2_d = iptc.easy.get_rule(self.table, self.chain, -1) + # Remove counters when comparing rules + rule2_d.pop('counters', None) self.assertEqual(rule_d, rule2_d) def suite(): From 282c790738a111b1ddc27b43ecb0acfab8b09024 Mon Sep 17 00:00:00 2001 From: Chris Heath Date: Mon, 17 Feb 2020 17:01:23 -0500 Subject: [PATCH 263/287] Don't set xtables_{matches,targets} to NULL. Protocol-independent extensions will be looked up once for each protocol and added to the protocol-specific cache. Fixes #282. --- iptc/xtables.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index fafb6af..93bc080 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -1020,10 +1020,6 @@ def find_match(self, name): if ext is not None: return ext - xtables._xtables_matches.value = ct.c_void_p(None).value - if xtables._xtables_pending_matches: - xtables._xtables_pending_matches.value = ct.c_void_p(None).value - match = xtables._xtables_find_match(name, XTF_TRY_LOAD, None) if not match: self._try_register(name) @@ -1045,10 +1041,6 @@ def find_target(self, name): if ext is not None: return ext - xtables._xtables_targets.value = ct.c_void_p(None).value - if xtables._xtables_pending_targets: - xtables._xtables_pending_targets.value = ct.c_void_p(None).value - target = xtables._xtables_find_target(name, XTF_TRY_LOAD) if not target: self._try_register(name) From e3557528d7cdcdc2c579212be8837bc9b54635a4 Mon Sep 17 00:00:00 2001 From: Frank Vanbever Date: Thu, 20 Feb 2020 12:14:08 +0100 Subject: [PATCH 264/287] Add separate mechanism to load libc ctypes.util.find_library() always returns None for systems which do not have the tools installed to determine the location of a given shared library (i.e. ldconfig, gcc, objdump). If find_libary() fails attempt to load known libc by SONAME. Signed-off-by: Frank Vanbever --- iptc/ip4tc.py | 4 ++-- iptc/util.py | 16 ++++++++++++++++ iptc/xtables.py | 4 ++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 4c5d690..4ddd2dc 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -9,7 +9,7 @@ import struct import weakref -from .util import find_library, load_kernel +from .util import find_library, load_kernel, find_libc from .xtables import (XT_INV_PROTO, NFPROTO_IPV4, XTablesError, xtables, xt_align, xt_counters, xt_entry_target, xt_entry_match) @@ -26,7 +26,7 @@ _IFNAMSIZ = 16 -_libc = ct.CDLL("libc.so.6") +_libc = find_libc() _get_errno_loc = _libc.__errno_location _get_errno_loc.restype = ct.POINTER(ct.c_int) _malloc = _libc.malloc diff --git a/iptc/util.py b/iptc/util.py index ae5fb9b..e6b1649 100644 --- a/iptc/util.py +++ b/iptc/util.py @@ -109,3 +109,19 @@ def find_library(*names): major = int(m.group(1)) return lib, major return None, None + + +def find_libc(): + lib = ctypes.util.find_library('c') + if lib is not None: + return ctypes.CDLL(lib, mode=ctypes.RTLD_GLOBAL) + + libnames = ['libc.so.6', 'libc.so.0', 'libc.so'] + for name in libnames: + try: + lib = ctypes.CDLL(name, mode=ctypes.RTLD_GLOBAL) + return lib + except: + pass + + return None diff --git a/iptc/xtables.py b/iptc/xtables.py index 93bc080..cf21029 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -6,7 +6,7 @@ import weakref from . import version -from .util import find_library +from .util import find_library, find_libc from .errors import * XT_INV_PROTO = 0x40 # invert the sense of PROTO @@ -792,7 +792,7 @@ class xtables_target(ct.Union): ("v12", _xtables_target_v12)] -_libc, _ = find_library("c") +_libc = find_libc() _optind = ct.c_long.in_dll(_libc, "optind") _optarg = ct.c_char_p.in_dll(_libc, "optarg") From 899d25c511c6ce779b7153e9ae2e41055b30b9c5 Mon Sep 17 00:00:00 2001 From: Frank Vanbever Date: Mon, 9 Mar 2020 12:36:47 +0100 Subject: [PATCH 265/287] Add '.so' as additional shared object suffix EXT_SUFFIX includes a platform information tag starting from Python 3.5 [0] For example: >>> sysconfig.get_config_var("EXT_SUFFIX") '.cpython-38-aarch64-linux-gnu.so' This suffix only applies to cpython extensions i.e. not to the iptables shared objects. Adding '.so' as an additional suffix for shared objects fixes the issue. Fixes: Issue #301 Signed-off-by: Frank Vanbever [0]: https://docs.python.org/3/whatsnew/3.5.html#build-and-c-api-changes --- iptc/util.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/iptc/util.py b/iptc/util.py index e6b1649..04fe905 100644 --- a/iptc/util.py +++ b/iptc/util.py @@ -80,12 +80,19 @@ def _do_find_library(name): def _find_library(*names): + exts = [] if version_info >= (3, 3): - ext = get_config_var("EXT_SUFFIX") + exts.append(get_config_var("EXT_SUFFIX")) else: - ext = get_config_var('SO') + exts.append(get_config_var('SO')) + + if version_info >= (3, 5): + exts.append('.so') + for name in names: - libnames = [name, "lib" + name, name + ext, "lib" + name + ext] + libnames = [name, "lib" + name] + for ext in exts: + libnames += [name + ext, "lib" + name + ext] libdir = os.environ.get('IPTABLES_LIBDIR', None) if libdir is not None: libdirs = libdir.split(':') From 30cd7ce19d0eb8e8d96ad3b03a9073e80b98bdd0 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 14 Jun 2020 14:10:22 -0700 Subject: [PATCH 266/287] Release v1.0.0 --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index 1ded765..2657151 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "0.15.0-dev" +__version__ = "1.0.0" From 34e5cf653aa107a0a2279936dd3f6850bb3ed0bb Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 14 Jun 2020 14:34:50 -0700 Subject: [PATCH 267/287] Start v1.0.1-dev --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index 2657151..13c18f6 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "1.0.0" +__version__ = "1.0.1-dev" From 542efdb739b4e3ef6f28274d23b506bf0027eec2 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 14 Jun 2020 14:39:01 -0700 Subject: [PATCH 268/287] Fix README.md permissions --- README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 README.md diff --git a/README.md b/README.md old mode 100755 new mode 100644 From 8e68d16969632d629fa42a1fafc0bf2edc84e678 Mon Sep 17 00:00:00 2001 From: AZaugg Date: Thu, 13 Oct 2022 10:20:46 -0700 Subject: [PATCH 269/287] Changed regex used to ind xtables libs Updated the regular expression for ldconfig versions packaged in glibc2.28 and greater Closes #329 --- iptc/xtables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/xtables.py b/iptc/xtables.py index cf21029..024779c 100644 --- a/iptc/xtables.py +++ b/iptc/xtables.py @@ -805,7 +805,7 @@ class xtables_target(ct.Union): _xtables_libdir = os.getenv("XTABLES_LIBDIR") if _xtables_libdir is None: import re - ldconfig_path_regex = re.compile('^(/.*):$') + ldconfig_path_regex = re.compile('^(/.*):($| \(.*\)$)') import subprocess ldconfig = subprocess.Popen( ('/sbin/ldconfig', '-N', '-v'), From f51bd140a7864533e286aefed0aa0c7316243015 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 21 Jan 2023 18:42:37 -0800 Subject: [PATCH 270/287] Add GHA workflow --- .github/workflows/build.yaml | 32 ++++++++++++++++++++++++++++++++ .travis.yml | 16 ---------------- 2 files changed, 32 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/build.yaml delete mode 100644 .travis.yml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..bedecd5 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,32 @@ +name: Build & Test +on: + push: + branches: + - main + pull_request: +jobs: + build-and-test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: + - "3.7" + - "3.8" + - "3.9" + - "3.10" + - "3.11" + steps: + - name: Check out code + uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Build package + run: python setup.py build + - name: Install package + run: python setup.py install + - name: Install coveralls + run: sudo pip install coveralls + - name: Run tests + run: sudo PATH=$PATH coverage run setup.py test diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e7f9a8c..0000000 --- a/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -dist: xenial -language: python -python: - - "2.7" - - "3.4" - - "3.5" - - "3.6" - - "3.7" -install: - - python setup.py build - - python setup.py install - - sudo pip install coveralls -script: - - sudo PATH=$PATH coverage run setup.py test -after_success: - coveralls From 479b09d765988dfa52503f23bd035eb30ea6ee9c Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sat, 21 Jan 2023 16:36:06 -0800 Subject: [PATCH 271/287] Release v1.0.1 --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index 13c18f6..80ed889 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "1.0.1-dev" +__version__ = "1.0.1" From bea578318f596c94a8a263867985130068e1fd10 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 22 Jan 2023 10:58:05 -0800 Subject: [PATCH 272/287] Update languages in setup.py --- setup.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.py b/setup.py index 145f0c3..efefa07 100644 --- a/setup.py +++ b/setup.py @@ -38,10 +38,7 @@ "Topic :: System :: Networking :: Firewalls", "Topic :: System :: Systems Administration", "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", "Programming Language :: Python :: Implementation :: CPython", ], license="Apache License, Version 2.0", From 9bc14a02c1e6d21aa5f4cf1757154c22b5644857 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 22 Jan 2023 11:39:49 -0800 Subject: [PATCH 273/287] Automate releases --- .github/workflows/build.yaml | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index bedecd5..c3b8396 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -3,6 +3,8 @@ on: push: branches: - main + tags: + - "v*" pull_request: jobs: build-and-test: @@ -23,10 +25,38 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Build package - run: python setup.py build - - name: Install package - run: python setup.py install + run: | + python -m pip install --upgrade build twine + python -m build + twine check --strict dist/* - name: Install coveralls run: sudo pip install coveralls - name: Run tests run: sudo PATH=$PATH coverage run setup.py test + + release: + runs-on: ubuntu-latest + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + needs: + - build-and-test (3.7) + - build-and-test (3.8) + - build-and-test (3.9) + - build-and-test (3.10) + - build-and-test (3.11) + steps: + - name: Check out code + uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: "3.x" + - name: Build package + run: | + python -m pip install --upgrade build twine + python -m build + twine check --strict dist/* + - name: Publish package + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} From 5ea087f886bd74e85664d97fc4b4b79d833dc840 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 22 Jan 2023 11:40:01 -0800 Subject: [PATCH 274/287] Add missing fields to setup.py --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index efefa07..c3e4a46 100644 --- a/setup.py +++ b/setup.py @@ -15,6 +15,8 @@ name=__pkgname__, version=__version__, description="Python bindings for iptables", + long_description="Python bindings for classic iptables", + long_description_content_type="text/x-rst", author="Vilmos Nebehaj", author_email="v.nebehaj@gmail.com", url="https://github.com/ldx/python-iptables", From d6f0faf52f2267510996db9575106a83ac34c287 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 22 Jan 2023 11:42:07 -0800 Subject: [PATCH 275/287] Fix dependency --- .github/workflows/build.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index c3b8396..b530f88 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -38,11 +38,7 @@ jobs: runs-on: ubuntu-latest if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') needs: - - build-and-test (3.7) - - build-and-test (3.8) - - build-and-test (3.9) - - build-and-test (3.10) - - build-and-test (3.11) + - build-and-test steps: - name: Check out code uses: actions/checkout@v2 From 158a82a31b95d6421c12e4ae9328febd8ed7ffb9 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 22 Jan 2023 11:48:15 -0800 Subject: [PATCH 276/287] Upload release to GitHub --- .github/workflows/build.yaml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index b530f88..2efe8e8 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -56,3 +56,28 @@ jobs: with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} + - name: Create GitHub release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: ${{ github.ref }} + draft: false + prerelease: false + - name: Set asset name + run: | + export PKG=$(ls dist/ | grep tar) + set -- $PKG + echo "name=$1" >> $GITHUB_ENV + - name: Upload release asset to GitHub + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: dist/${{ env.name }} + asset_name: ${{ env.name }} + asset_content_type: application/zip From bb2b43b77016bb09bf1f11524a0779bdbdbe5a8e Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 22 Jan 2023 11:53:12 -0800 Subject: [PATCH 277/287] Fix indentation --- .github/workflows/build.yaml | 50 ++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2efe8e8..2003385 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -56,28 +56,28 @@ jobs: with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} - - name: Create GitHub release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref }} - release_name: ${{ github.ref }} - draft: false - prerelease: false - - name: Set asset name - run: | - export PKG=$(ls dist/ | grep tar) - set -- $PKG - echo "name=$1" >> $GITHUB_ENV - - name: Upload release asset to GitHub - id: upload-release-asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: dist/${{ env.name }} - asset_name: ${{ env.name }} - asset_content_type: application/zip + - name: Create GitHub release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: ${{ github.ref }} + draft: false + prerelease: false + - name: Set asset name + run: | + export PKG=$(ls dist/ | grep tar) + set -- $PKG + echo "name=$1" >> $GITHUB_ENV + - name: Upload release asset to GitHub + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: dist/${{ env.name }} + asset_name: ${{ env.name }} + asset_content_type: application/zip From 3b0d79c6bf7f54cca36f1ddd39d3374f8542d223 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 22 Jan 2023 12:50:31 -0800 Subject: [PATCH 278/287] Fix upload --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2003385..561a473 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -51,6 +51,7 @@ jobs: python -m pip install --upgrade build twine python -m build twine check --strict dist/* + rm -f dist/*.whl - name: Publish package uses: pypa/gh-action-pypi-publish@release/v1 with: From 2cd09a36f3db37d4e5fa9112dc2e2092a0560966 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 22 Jan 2023 12:57:45 -0800 Subject: [PATCH 279/287] Start v1.0.2-dev --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index 80ed889..8a2f162 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "1.0.1" +__version__ = "1.0.2-dev" From af932f847f411f9765034a34e2ec33e839f3df61 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Wed, 19 Feb 2025 09:11:02 +0530 Subject: [PATCH 280/287] Trigger build --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 561a473..fe95d83 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -6,6 +6,7 @@ on: tags: - "v*" pull_request: + jobs: build-and-test: runs-on: ubuntu-latest From eb0fb688527cce2db0222552f1a6ae9dbd6da5e0 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Wed, 19 Feb 2025 09:14:10 +0530 Subject: [PATCH 281/287] Update actions --- .github/workflows/build.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index fe95d83..3c4fe42 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -20,9 +20,9 @@ jobs: - "3.11" steps: - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Build package @@ -42,9 +42,9 @@ jobs: - build-and-test steps: - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: "3.x" - name: Build package From 4fffc0ee73273e70dff138fd76c767ff6b5d24a1 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Wed, 19 Feb 2025 09:17:30 +0530 Subject: [PATCH 282/287] Use earlier image --- .github/workflows/build.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 3c4fe42..efc2167 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -9,7 +9,7 @@ on: jobs: build-and-test: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 strategy: matrix: python-version: @@ -36,7 +36,7 @@ jobs: run: sudo PATH=$PATH coverage run setup.py test release: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') needs: - build-and-test From d9a70d478d118ddef8cc3ff84c4febc9a0e4e884 Mon Sep 17 00:00:00 2001 From: Colin Watson Date: Thu, 20 Feb 2025 03:49:04 +0000 Subject: [PATCH 283/287] Port to Sphinx 8.0 (#349) The old `intersphinx_mapping` format has been removed; it must now map identifiers to (target, inventory) tuples. --- doc/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index 6abeedf..ea780c0 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -196,6 +196,6 @@ # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'http://docs.python.org/': None} +intersphinx_mapping = {'python': ('http://docs.python.org/', None)} autoclass_content="both" From 794aace1fc7b38756ba53c83e4c19a96f81c3763 Mon Sep 17 00:00:00 2001 From: Jeremiah Millay <5504449+floatingstatic@users.noreply.github.com> Date: Wed, 19 Feb 2025 22:51:09 -0500 Subject: [PATCH 284/287] Make python-iptables compatible with python 3.12 (#351) --- iptc/util.py | 7 ++++++- iptc/version.py | 2 +- setup.py | 1 - 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/iptc/util.py b/iptc/util.py index 04fe905..6c1592c 100644 --- a/iptc/util.py +++ b/iptc/util.py @@ -3,7 +3,6 @@ import sys import ctypes import ctypes.util -from distutils.sysconfig import get_python_lib from itertools import product from subprocess import Popen, PIPE from sys import version_info @@ -14,6 +13,12 @@ def get_config_var(name): if name == 'SO': return '.so' raise Exception('Not implemented') +try: + from distutils.sysconfig import get_python_lib +except ModuleNotFoundError: + import sysconfig + def get_python_lib(): + return sysconfig.get_path("purelib") def _insert_ko(modprobe, modname): diff --git a/iptc/version.py b/iptc/version.py index 8a2f162..e67fbc7 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "1.0.2-dev" +__version__ = "1.1.0" diff --git a/setup.py b/setup.py index c3e4a46..1929f93 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,6 @@ """python-iptables setup script""" from setuptools import setup, Extension -#from distutils.core import setup, Extension # make pyflakes happy __pkgname__ = None From 9c9b4b7b6b042e475f1c58f8bd2e939dfea3b410 Mon Sep 17 00:00:00 2001 From: Or Yaacov <48175064+oryaacov@users.noreply.github.com> Date: Tue, 4 Mar 2025 09:59:22 -0800 Subject: [PATCH 285/287] fix(ip6tc): ipv6 interface name with 15 chars is too big (#348) * fix(ip6tc): ipv6 interface name with 15 chars is too big * feat(tests): add case for max valid length network interface name --- iptc/ip6tc.py | 55 ++++++++++++++++++++-------------------------- tests/test_iptc.py | 4 +++- 2 files changed, 27 insertions(+), 32 deletions(-) diff --git a/iptc/ip6tc.py b/iptc/ip6tc.py index ed10e17..ea4a1d7 100644 --- a/iptc/ip6tc.py +++ b/iptc/ip6tc.py @@ -412,22 +412,19 @@ def set_dst(self, dst): def get_in_interface(self): intf = "" if self.entry.ipv6.invflags & ip6t_ip6.IP6T_INV_VIA_IN: - intf = "".join(["!", intf]) - iface = bytearray(_IFNAMSIZ) - iface[:len(self.entry.ipv6.iniface)] = self.entry.ipv6.iniface - mask = bytearray(_IFNAMSIZ) - mask[:len(self.entry.ipv6.iniface_mask)] = self.entry.ipv6.iniface_mask - if mask[0] == 0: + intf = "!" + + iface = self.entry.ipv6.iniface.decode() + mask = self.entry.ipv6.iniface_mask + + if len(mask) == 0: return None - for i in range(_IFNAMSIZ): - if mask[i] != 0: - intf = "".join([intf, chr(iface[i])]) - else: - if iface[i - 1] != 0: - intf = "".join([intf, "+"]) - else: - intf = intf[:-1] - break + + intf += iface + if len(iface) == len(mask): + intf += '+' + intf = intf[:_IFNAMSIZ] + return intf def set_in_interface(self, intf): @@ -456,23 +453,19 @@ def set_in_interface(self, intf): def get_out_interface(self): intf = "" if self.entry.ipv6.invflags & ip6t_ip6.IP6T_INV_VIA_OUT: - intf = "".join(["!", intf]) - iface = bytearray(_IFNAMSIZ) - iface[:len(self.entry.ipv6.outiface)] = self.entry.ipv6.outiface - mask = bytearray(_IFNAMSIZ) - mask[:len(self.entry.ipv6.outiface_mask)] = \ - self.entry.ipv6.outiface_mask - if mask[0] == 0: + intf = "!" + + iface = self.entry.ipv6.outiface.decode() + mask = self.entry.ipv6.outiface_mask + + if len(mask) == 0: return None - for i in range(_IFNAMSIZ): - if mask[i] != 0: - intf = "".join([intf, chr(iface[i])]) - else: - if iface[i - 1] != 0: - intf = "".join([intf, "+"]) - else: - intf = intf[:-1] - break + + intf += iface + if len(iface) == len(mask): + intf += '+' + intf = intf[:_IFNAMSIZ] + return intf def set_out_interface(self, intf): diff --git a/tests/test_iptc.py b/tests/test_iptc.py index d630c87..816df86 100755 --- a/tests/test_iptc.py +++ b/tests/test_iptc.py @@ -423,7 +423,9 @@ def test_rule_address(self): def test_rule_interface(self): # valid interfaces rule = iptc.Rule6() - for intf in ["eth0", "eth+", "ip6tnl1", "ip6tnl+", "!ppp0", "!ppp+"]: + + max_length_valid_interface_name = "0123456789abcde" + for intf in ["eth0", "eth+", "ip6tnl1", "ip6tnl+", "!ppp0", "!ppp+", max_length_valid_interface_name]: rule.in_interface = intf self.assertEquals(intf, rule.in_interface) rule.out_interface = intf From c2d0e67abeef7dceec0a1891566dcde79889e048 Mon Sep 17 00:00:00 2001 From: jkklemm Date: Sun, 6 Apr 2025 23:26:56 +0200 Subject: [PATCH 286/287] Speed up code for arm architecture when creating new rules (#341) * speed up code for arm architecture when creating new rules * moved dict to static class variable --- iptc/ip4tc.py | 13 +++++++++++++ iptc/ip6tc.py | 6 ++++++ 2 files changed, 19 insertions(+) diff --git a/iptc/ip4tc.py b/iptc/ip4tc.py index 4ddd2dc..90f5ee7 100644 --- a/iptc/ip4tc.py +++ b/iptc/ip4tc.py @@ -42,10 +42,14 @@ def is_table_available(name): try: + if name in Table.existing_table_names: + return Table.existing_table_names[name] Table(name) + Table.existing_table_names[name] = True return True except IPTCError: pass + Table.existing_table_names[name] = False return False @@ -669,6 +673,11 @@ class Target(IPTCModule): does not take any value in the iptables extension, an empty string i.e. "" should be used. """ + + STANDARD_TARGETS = ["", "ACCEPT", "DROP", "REJECT", "RETURN", "REDIRECT", "SNAT", "DNAT", \ + "MASQUERADE", "MIRROR", "TOS", "MARK", "QUEUE", "LOG"] + """This is the constant for all standard targets.""" + def __init__(self, rule, name=None, target=None, revision=None, goto=None): """ *rule* is the Rule object this match belongs to; it can be changed @@ -784,6 +793,8 @@ def _create_buffer(self, target): self.reset() def _is_standard_target(self): + if self._name in Target.STANDARD_TARGETS: + return False for t in self._rule.tables: if t.is_chain(self._name): return True @@ -1572,6 +1583,8 @@ class Table(object): """This is the constant for all tables.""" _cache = dict() + existing_table_names = dict() + """Dictionary to check faster if a table is available.""" def __new__(cls, name, autocommit=None): obj = Table._cache.get(name, None) diff --git a/iptc/ip6tc.py b/iptc/ip6tc.py index ea4a1d7..e0d8787 100644 --- a/iptc/ip6tc.py +++ b/iptc/ip6tc.py @@ -19,10 +19,14 @@ def is_table6_available(name): try: + if name in Table6.existing_table_names: + return Table6.existing_table_names[name] Table6(name) + Table6.existing_table_names[name] = True return True except IPTCError: pass + Table6.existing_table_names[name] = False return False @@ -572,6 +576,8 @@ class Table6(Table): """This is the constant for all tables.""" _cache = dict() + existing_table_names = dict() + """Dictionary to check faster if a table is available.""" def __new__(cls, name, autocommit=None): obj = Table6._cache.get(name, None) From 2703efb3063f4b62282722f85a1a7f584d9d64c1 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Sun, 6 Apr 2025 15:11:08 -0700 Subject: [PATCH 287/287] Bump version to v1.2.0 (#355) --- iptc/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iptc/version.py b/iptc/version.py index e67fbc7..f6a5157 100644 --- a/iptc/version.py +++ b/iptc/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- __pkgname__ = "python-iptables" -__version__ = "1.1.0" +__version__ = "1.2.0"