Merge branch 'devel' of ssh://git.planet-lab.org/git/nodemanager into devel
[nodemanager.git] / iptables.py
1 #!/usr/bin/python -tt
2 #
3 # $Id$
4 # $URL$
5 #
6 # Author: Daniel Hokka Zakrisson <daniel@hozac.com>
7 # $Id$
8
9 import os
10 import subprocess
11
12 class IPTables:
13     """A class to encapsulate iptables operations"""
14     IPTABLES_RESTORE = "/sbin/iptables-restore"
15
16     def __init__(self):
17         self.extifs = []
18         self.intifs = []
19         self.pfs = []
20
21     def add_ext(self, interface):
22         """Adds an external interface. An external interface is one where
23            outgoing traffic will be NATed, and incoming traffic will go to
24            the port forward chain."""
25         self.extifs.append(interface)
26
27     def add_int(self, interface):
28         """Adds an internal interface. An internal interface is trusted,
29            and traffic coming in on it is allowed through."""
30         self.intifs.append(interface)
31
32     def add_pf(self, pf):
33         """Adds a port forward. The argument is a dict consisting of:
34            'protocol'       tcp/udp
35            'destination'    the new destination IP
36            'dport'          the destination port
37            'new_dport'      the new destination port
38            and optionally:
39            'interface'      the incoming interface
40            'source'         limit the redirect to these IPs"""
41         # XXX Should make sure the required fields are there
42         self.pfs.append(pf)
43
44     def commit(self):
45         """Call commit when all the rules are ready to be applied.
46            This is a no-op if no port forwards, external or internal
47            interfaces have been declared."""
48
49         # XXX This should check for errors
50         #     and make sure the new ruleset differs from the current one
51
52         if (len(self.extifs) + len(self.intifs) + len(self.pfs)) == 0:
53             return True
54
55         restore = subprocess.Popen([self.IPTABLES_RESTORE, "--noflush"], stdin=subprocess.PIPE)
56         restore.stdin.write("""*filter
57 :INPUT ACCEPT [0:0]
58 :FORWARD ACCEPT [0:0]
59 :OUTPUT ACCEPT [0:0]
60 :LOGDROP - [0:0]
61 :SLICESPRE - [0:0]
62 :SLICES - [0:0]
63 :PORTFW - [0:0]
64
65 -F INPUT
66 -F FORWARD
67 -F OUTPUT
68
69 -A LOGDROP -j LOG
70 -A LOGDROP -j DROP
71 -A OUTPUT -j BLACKLIST
72 -A OUTPUT -m mark ! --mark 0/65535 -j SLICESPRE
73 -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
74 """)
75
76         for int in self.intifs:
77             # Allow all traffic from internal to external
78             for ext in self.extifs:
79                 restore.stdin.write("-A FORWARD -i %s -o %s -j ACCEPT\n" % (int, ext))
80             # Traffic from slices to internal networks is scrutinized
81             restore.stdin.write("-A SLICESPRE -o %s -j SLICES\n" % int)
82
83         restore.stdin.write("-A FORWARD -m state --state NEW -j PORTFW\n")
84         for pf in self.pfs:
85             # Port forwards, redirect incoming external traffic to some internal address
86             rule = "-A PORTFW -p %s -d %s " % (pf['protocol'], pf['destination'])
87             if 'interface' in pf:
88                 rule += "-i %s " % pf['interface']
89             if 'source' in pf:
90                 rule += "-s %s " % pf['source']
91             rule += "--dport %s" % pf['new_dport']
92             restore.stdin.write(rule + "\n")
93
94         restore.stdin.write("-A FORWARD -j LOGDROP\n")
95
96         # This should have a way to add rules
97         restore.stdin.write("-A SLICES -j LOGDROP\n")
98         restore.stdin.write("""COMMIT
99 *nat
100 :PREROUTING ACCEPT [0:0]
101 :POSTROUTING ACCEPT [0:0]
102 :OUTPUT ACCEPT [0:0]
103 :PORTFW - [0:0]
104 :MASQ - [0:0]
105
106 -F PREROUTING
107 -F POSTROUTING
108 -F OUTPUT
109 """)
110
111         # Outgoing traffic on external interfaces needs to be NATed
112         for ext in self.extifs:
113             restore.stdin.write("-A MASQ -o %s -j MASQUERADE\n")
114
115         # Redirect port forwards to their real destination
116         for pf in self.pfs:
117             rule = "-A PORTFW -p %s " % pf['protocol']
118             if 'interface' in pf:
119                 rule += "-i %s " % pf['interface']
120             if 'source' in pf:
121                 rule += "-s %s " % pf['source']
122             rule += "--dport %s -j DNAT --to %s:%s" % (pf['dport'], pf['destination'],
123                     pf['new_dport'])
124             restore.stdin.write(rule + "\n")
125
126         restore.stdin.write("COMMIT\n")
127         restore.stdin.close()
128         return restore.wait() == 0