Skip to content

Commit b898fa4

Browse files
committed
Added documentation. Cleanup
1 parent cccb274 commit b898fa4

File tree

5 files changed

+140
-192
lines changed

5 files changed

+140
-192
lines changed

README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,31 @@ Full documentation with API reference is available
135135
Examples
136136
========
137137

138+
High level abstractions
139+
-----------------------
140+
141+
``python-iptables`` implements a low-level interface that tries to closely
142+
match the underlying C libraries. The module ``iptc.easy`` improves the
143+
usability of the library by providing a rich set of high-level functions
144+
designed to simplify the interaction with the library, for example::
145+
146+
>>> import iptc
147+
>>> iptc.easy.dump_table('nat', ipv6=False)
148+
{'INPUT': [], 'OUTPUT': [], 'POSTROUTING': [], 'PREROUTING': []}
149+
>>> iptc.easy.dump_chain('filter', 'OUTPUT', ipv6=False)
150+
[{'comment': {'comment': 'DNS traffic to Google'},
151+
'dst': '8.8.8.8/32',
152+
'protocol': 'udp',
153+
'target': 'ACCEPT',
154+
'udp': {'dport': '53'}}]
155+
>>> iptc.easy.add_chain('filter', 'TestChain')
156+
True
157+
>>> rule_d = {'protocol': 'tcp', 'target': 'ACCEPT', 'tcp': {'dport': '22'}}
158+
>>> iptc.easy.insert_rule('filter', 'TestChain', rule_d)
159+
>>> iptc.easy.dump_chain('filter', 'TestChain')
160+
[{'protocol': 'tcp', 'target': 'ACCEPT', 'tcp': {'dport': '22'}}]
161+
>>> iptc.easy.delete_chain('filter', 'TestChain', flush=True)
162+
138163
Rules
139164
-----
140165

@@ -546,6 +571,21 @@ or more rules, than commit it:
546571
The drawback is that Table is a singleton, and if you disable
547572
autocommit, it will be disabled for all instances of that Table.
548573

574+
Easy rules with dictionaries
575+
----------------------------
576+
To simplify operations with ``python-iptables`` rules we have included support to define and convert Rules object into python dictionaries.
577+
578+
>>> import iptc
579+
>>> table = iptc.Table(iptc.Table.FILTER)
580+
>>> chain = iptc.Chain(table, "INPUT")
581+
>>> # Create an iptc.Rule object from dictionary
582+
>>> rule_d = {'comment': {'comment': 'Match tcp.22'}, 'protocol': 'tcp', 'target': 'ACCEPT', 'tcp': {'dport': '22'}}
583+
>>> rule = iptc.easy.encode_iptc_rule(rule_d)
584+
>>> # Obtain a dictionary representation from the iptc.Rule
585+
>>> iptc.easy.decode_iptc_rule(rule)
586+
{'tcp': {'dport': '22'}, 'protocol': 'tcp', 'comment': {'comment': 'Match tcp.22'}, 'target': 'ACCEPT'}
587+
588+
549589
Known Issues
550590
============
551591

doc/examples.rst

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,31 @@
11
Examples
22
========
33

4+
High level abstractions
5+
-----------------------
6+
7+
``python-iptables`` implements a low-level interface that tries to closely
8+
match the underlying C libraries. The module ``iptc.easy`` improves the
9+
usability of the library by providing a rich set of high-level functions
10+
designed to simplify the interaction with the library, for example::
11+
12+
>>> import iptc
13+
>>> iptc.easy.dump_table('nat', ipv6=False)
14+
{'INPUT': [], 'OUTPUT': [], 'POSTROUTING': [], 'PREROUTING': []}
15+
>>> iptc.easy.dump_chain('filter', 'OUTPUT', ipv6=False)
16+
[{'comment': {'comment': 'DNS traffic to Google'},
17+
'dst': '8.8.8.8/32',
18+
'protocol': 'udp',
19+
'target': 'ACCEPT',
20+
'udp': {'dport': '53'}}]
21+
>>> iptc.easy.add_chain('filter', 'TestChain')
22+
True
23+
>>> rule_d = {'protocol': 'tcp', 'target': 'ACCEPT', 'tcp': {'dport': '22'}}
24+
>>> iptc.easy.insert_rule('filter', 'TestChain', rule_d)
25+
>>> iptc.easy.dump_chain('filter', 'TestChain')
26+
[{'protocol': 'tcp', 'target': 'ACCEPT', 'tcp': {'dport': '22'}}]
27+
>>> iptc.easy.delete_chain('filter', 'TestChain', flush=True)
28+
429
Rules
530
-----
631

@@ -419,9 +444,9 @@ To simplify operations with ``python-iptables`` rules we have included support t
419444
>>> chain = iptc.Chain(table, "INPUT")
420445
>>> # Create an iptc.Rule object from dictionary
421446
>>> rule_d = {'comment': {'comment': 'Match tcp.22'}, 'protocol': 'tcp', 'target': 'ACCEPT', 'tcp': {'dport': '22'}}
422-
>>> rule = iptc.Rule.from_dict(rule_d)
447+
>>> rule = iptc.easy.encode_iptc_rule(rule_d)
423448
>>> # Obtain a dictionary representation from the iptc.Rule
424-
>>> rule.to_dict()
449+
>>> iptc.easy.decode_iptc_rule(rule)
425450
{'tcp': {'dport': '22'}, 'protocol': 'tcp', 'comment': {'comment': 'Match tcp.22'}, 'target': 'ACCEPT'}
426451

427452

iptc/easy.py

Lines changed: 63 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,69 @@ def batch_delete_rules(table, batch_rules, ipv6=False, raise_exc=True):
288288
if raise_exc: raise
289289

290290

291+
def encode_iptc_rule(rule_d, ipv6=False):
292+
""" Return a Rule(6) object from the input dictionary """
293+
# Sanity check
294+
assert(isinstance(rule_d, dict))
295+
# Basic rule attributes
296+
rule_attr = ('src', 'dst', 'protocol', 'in-interface', 'out-interface', 'fragment')
297+
iptc_rule = Rule6() if ipv6 else Rule()
298+
# Avoid issues with matches that require basic parameters to be configured first
299+
for name in rule_attr:
300+
if name in rule_d:
301+
_iptc_setrule(iptc_rule, name, rule_d[name])
302+
for name, value in rule_d.items():
303+
try:
304+
if name in rule_attr:
305+
continue
306+
elif name == 'target':
307+
_iptc_settarget(iptc_rule, value)
308+
else:
309+
_iptc_setmatch(iptc_rule, name, value)
310+
except Exception as e:
311+
#print('Ignoring unsupported field <{}:{}>'.format(name, value))
312+
continue
313+
return iptc_rule
314+
315+
def decode_iptc_rule(iptc_rule, ipv6=False):
316+
""" Return a dictionary representation of the Rule(6) object
317+
Note: host IP addresses are appended their corresponding CIDR """
318+
d = {}
319+
if ipv6==False and iptc_rule.src != '0.0.0.0/0.0.0.0':
320+
_ip, _netmask = iptc_rule.src.split('/')
321+
_netmask = _netmask_v4_to_cidr(_netmask)
322+
d['src'] = '{}/{}'.format(_ip, _netmask)
323+
elif ipv6==True and iptc_rule.src != '::/0':
324+
d['src'] = iptc_rule.src
325+
if ipv6==False and iptc_rule.dst != '0.0.0.0/0.0.0.0':
326+
_ip, _netmask = iptc_rule.dst.split('/')
327+
_netmask = _netmask_v4_to_cidr(_netmask)
328+
d['dst'] = '{}/{}'.format(_ip, _netmask)
329+
elif ipv6==True and iptc_rule.dst != '::/0':
330+
d['dst'] = iptc_rule.dst
331+
if iptc_rule.protocol != 'ip':
332+
d['protocol'] = iptc_rule.protocol
333+
if iptc_rule.in_interface is not None:
334+
d['in-interface'] = iptc_rule.in_interface
335+
if iptc_rule.out_interface is not None:
336+
d['out-interface'] = iptc_rule.out_interface
337+
if ipv6 == False and iptc_rule.fragment:
338+
d['fragment'] = iptc_rule.fragment
339+
for m in iptc_rule.matches:
340+
if m.name not in d:
341+
d[m.name] = m.get_all_parameters()
342+
elif isinstance(d[m.name], list):
343+
d[m.name].append(m.get_all_parameters())
344+
else:
345+
d[m.name] = [d[m.name], m.get_all_parameters()]
346+
if iptc_rule.target and iptc_rule.target.name and len(iptc_rule.target.get_all_parameters()):
347+
name = iptc_rule.target.name.replace('-', '_')
348+
d['target'] = {name:iptc_rule.target.get_all_parameters()}
349+
elif iptc_rule.target and iptc_rule.target.name:
350+
d['target'] = iptc_rule.target.name
351+
# Return a filtered dictionary
352+
return _filter_empty_field(d)
353+
291354
### INTERNAL FUNCTIONS ###
292355
def _iptc_table_available(table, ipv6=False):
293356
""" Return True if the table is available, False otherwise """
@@ -357,69 +420,6 @@ def _iptc_settarget(iptc_rule, value):
357420
else:
358421
iptc_target = iptc_rule.create_target(value)
359422

360-
def encode_iptc_rule(rule_d, ipv6=False):
361-
# Sanity check
362-
assert(isinstance(rule_d, dict))
363-
# Basic rule attributes
364-
rule_attr = ('src', 'dst', 'protocol', 'in-interface', 'out-interface', 'fragment')
365-
iptc_rule = Rule6() if ipv6 else Rule()
366-
# Avoid issues with matches that require basic parameters to be configured first
367-
for name in rule_attr:
368-
if name in rule_d:
369-
_iptc_setrule(iptc_rule, name, rule_d[name])
370-
for name, value in rule_d.items():
371-
try:
372-
if name in rule_attr:
373-
#_iptc_setrule(iptc_rule, name, value)
374-
continue
375-
elif name == 'target':
376-
_iptc_settarget(iptc_rule, value)
377-
else:
378-
_iptc_setmatch(iptc_rule, name, value)
379-
except Exception as e:
380-
#print('Ignoring unsupported field <{}:{}>'.format(name, value))
381-
continue
382-
return iptc_rule
383-
384-
def decode_iptc_rule(iptc_rule, ipv6=False):
385-
""" Return a dictionary representation of an iptc_rule
386-
Note: host IP addresses are appended their corresponding CIDR """
387-
d = {}
388-
if ipv6==False and iptc_rule.src != '0.0.0.0/0.0.0.0':
389-
_ip, _netmask = iptc_rule.src.split('/')
390-
_netmask = _netmask_v4_to_cidr(_netmask)
391-
d['src'] = '{}/{}'.format(_ip, _netmask)
392-
elif ipv6==True and iptc_rule.src != '::/0':
393-
d['src'] = iptc_rule.src
394-
if ipv6==False and iptc_rule.dst != '0.0.0.0/0.0.0.0':
395-
_ip, _netmask = iptc_rule.dst.split('/')
396-
_netmask = _netmask_v4_to_cidr(_netmask)
397-
d['dst'] = '{}/{}'.format(_ip, _netmask)
398-
elif ipv6==True and iptc_rule.dst != '::/0':
399-
d['dst'] = iptc_rule.dst
400-
if iptc_rule.protocol != 'ip':
401-
d['protocol'] = iptc_rule.protocol
402-
if iptc_rule.in_interface is not None:
403-
d['in-interface'] = iptc_rule.in_interface
404-
if iptc_rule.out_interface is not None:
405-
d['out-interface'] = iptc_rule.out_interface
406-
if ipv6 == False and iptc_rule.fragment:
407-
d['fragment'] = iptc_rule.fragment
408-
for m in iptc_rule.matches:
409-
if m.name not in d:
410-
d[m.name] = m.get_all_parameters()
411-
elif isinstance(d[m.name], list):
412-
d[m.name].append(m.get_all_parameters())
413-
else:
414-
d[m.name] = [d[m.name], m.get_all_parameters()]
415-
if iptc_rule.target and iptc_rule.target.name and len(iptc_rule.target.get_all_parameters()):
416-
name = iptc_rule.target.name.replace('-', '_')
417-
d['target'] = {name:iptc_rule.target.get_all_parameters()}
418-
elif iptc_rule.target and iptc_rule.target.name:
419-
d['target'] = iptc_rule.target.name
420-
# Return a filtered dictionary
421-
return _filter_empty_field(d)
422-
423423
def _batch_begin_table(table, ipv6=False):
424424
""" Disable autocommit on a table """
425425
iptc_table = _iptc_gettable(table, ipv6)

iptc/ip4tc.py

Lines changed: 1 addition & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import weakref
1111

1212
from .util import find_library, load_kernel
13-
from .xtables import (XT_INV_PROTO, NFPROTO_IPV4, NFPROTO_IPV6, XTablesError, xtables,
13+
from .xtables import (XT_INV_PROTO, NFPROTO_IPV4, XTablesError, xtables,
1414
xt_align, xt_counters, xt_entry_target, xt_entry_match)
1515

1616
__all__ = ["Table", "Chain", "Rule", "Match", "Target", "Policy", "IPTCError"]
@@ -1389,123 +1389,6 @@ def _get_mask(self):
13891389
mask = property(_get_mask)
13901390
"""This is the raw mask buffer as iptables uses it when removing rules."""
13911391

1392-
@classmethod
1393-
def from_dict(cls, rule_d):
1394-
"""Generate a Rule(6) object from the input dictionary."""
1395-
# Sanity check
1396-
assert(isinstance(rule_d, dict))
1397-
# Basic rule attributes
1398-
rule_attr = ('src', 'dst', 'protocol', 'in-interface', 'out-interface', 'fragment')
1399-
iptc_rule = cls()
1400-
# Avoid issues with matches that require basic parameters to be configured first
1401-
for name in rule_attr:
1402-
if name in rule_d:
1403-
_iptc_setrule(iptc_rule, name, rule_d[name])
1404-
for name, value in rule_d.items():
1405-
try:
1406-
if name in rule_attr:
1407-
#_iptc_setrule(iptc_rule, name, value)
1408-
continue
1409-
elif name == 'target':
1410-
_iptc_settarget(iptc_rule, value)
1411-
else:
1412-
_iptc_setmatch(iptc_rule, name, value)
1413-
except Exception as e:
1414-
#print('Ignoring unsupported field <{}:{}>'.format(name, value))
1415-
continue
1416-
return iptc_rule
1417-
1418-
def to_dict(self):
1419-
"""Generate a dictionary representation of the Rule(6) object."""
1420-
d = {}
1421-
if self.nfproto==NFPROTO_IPV4 and self.src != '0.0.0.0/0.0.0.0':
1422-
d['src'] = self.src
1423-
elif self.nfproto==NFPROTO_IPV6 and self.src != '::/0':
1424-
d['src'] = self.src
1425-
if self.nfproto==NFPROTO_IPV4 and self.dst != '0.0.0.0/0.0.0.0':
1426-
d['dst'] = self.dst
1427-
elif self.nfproto==NFPROTO_IPV6 and self.dst != '::/0':
1428-
d['dst'] = self.dst
1429-
if self.protocol != 'ip':
1430-
d['protocol'] = self.protocol
1431-
if self.in_interface is not None:
1432-
d['in-interface'] = self.in_interface
1433-
if self.out_interface is not None:
1434-
d['out-interface'] = self.out_interface
1435-
if self.nfproto==NFPROTO_IPV4 and self.fragment:
1436-
d['fragment'] = self.fragment
1437-
for m in self.matches:
1438-
if m.name not in d:
1439-
d[m.name] = m.get_all_parameters()
1440-
elif isinstance(d[m.name], list):
1441-
d[m.name].append(m.get_all_parameters())
1442-
else:
1443-
d[m.name] = [d[m.name], m.get_all_parameters()]
1444-
if self.target and self.target.name and len(self.target.get_all_parameters()):
1445-
name = self.target.name.replace('-', '_')
1446-
d['target'] = {name:self.target.get_all_parameters()}
1447-
elif self.target and self.target.name:
1448-
d['target'] = self.target.name
1449-
# Return a filtered dictionary
1450-
return _filter_empty_field(d)
1451-
1452-
# Helper functions for dictionary operations over Rule(6) objects
1453-
def _iptc_setattr(object, name, value):
1454-
# Translate attribute name
1455-
name = name.replace('-', '_')
1456-
setattr(object, name, value)
1457-
1458-
def _iptc_setattr_d(object, value_d):
1459-
for name, value in value_d.items():
1460-
_iptc_setattr(object, name, value)
1461-
1462-
def _iptc_setrule(iptc_rule, name, value):
1463-
_iptc_setattr(iptc_rule, name, value)
1464-
1465-
def _iptc_setmatch(iptc_rule, name, value):
1466-
# Iterate list/tuple recursively
1467-
if isinstance(value, list) or isinstance(value, tuple):
1468-
for inner_value in value:
1469-
_iptc_setmatch(iptc_rule, name, inner_value)
1470-
# Assign dictionary value
1471-
elif isinstance(value, dict):
1472-
iptc_match = iptc_rule.create_match(name)
1473-
_iptc_setattr_d(iptc_match, value)
1474-
# Assign value directly
1475-
else:
1476-
iptc_match = iptc_rule.create_match(name)
1477-
_iptc_setattr(iptc_match, name, value)
1478-
1479-
def _iptc_settarget(iptc_rule, value):
1480-
# Target is dictionary - Use only 1 pair key/value
1481-
if isinstance(value, dict):
1482-
for k, v in value.items():
1483-
iptc_target = iptc_rule.create_target(k)
1484-
_iptc_setattr_d(iptc_target, v)
1485-
return
1486-
# Simple target
1487-
else:
1488-
iptc_target = iptc_rule.create_target(value)
1489-
1490-
def _filter_empty_field(data_d):
1491-
"""
1492-
Remove empty lists from dictionary values
1493-
Before: {'target': {'CHECKSUM': {'checksum-fill': []}}}
1494-
After: {'target': {'CHECKSUM': {'checksum-fill': ''}}}
1495-
Before: {'tcp': {'dport': ['22']}}}
1496-
After: {'tcp': {'dport': '22'}}}
1497-
"""
1498-
for k, v in data_d.items():
1499-
if isinstance(v, dict):
1500-
data_d[k] = _filter_empty_field(v)
1501-
elif isinstance(v, list) and len(v) != 0:
1502-
v = [_filter_empty_field(_v) if isinstance(_v, dict) else _v for _v in v ]
1503-
if isinstance(v, list) and len(v) == 1:
1504-
data_d[k] = v.pop()
1505-
elif isinstance(v, list) and len(v) == 0:
1506-
data_d[k] = ''
1507-
return data_d
1508-
15091392

15101393
class Chain(object):
15111394
"""Rules are contained by chains.

0 commit comments

Comments
 (0)