]> git.llucax.com Git - software/pymin.git/blob - pymin/services/nat/__init__.py
Add NatHandler to handle NAT (port forwarding, snating and masquerading).
[software/pymin.git] / pymin / services / nat / __init__.py
1 # vim: set encoding=utf-8 et sw=4 sts=4 :
2
3 from os import path
4
5 from pymin.seqtools import Sequence
6 from pymin.dispatcher import Handler, handler, HandlerError
7 from pymin.services.util import Restorable, ConfigWriter, RestartHandler, \
8                                 ReloadHandler, TransactionalHandler, \
9                                 ListSubHandler, call
10
11 __ALL__ = ('NatHandler', 'Error')
12
13 class Error(HandlerError):
14     r"""
15     Error(command) -> Error instance :: Base NatHandler exception class.
16
17     All exceptions raised by the NatHandler inherits from this one, so you can
18     easily catch any NatHandler exception.
19
20     message - A descriptive error message.
21     """
22     pass
23
24 class PortForward(Sequence):
25     r"""PortForward(dev, protocol, port, dst[, dst_port[, ...]]) -> PortForward.
26
27     dev - Netword device to use.
28     protocol - TCP or UDP.
29     port - Port to forward.
30     dst - Destination IP address.
31     dst_port - Destination port (at dst).
32     src_net - Source network to apply the forward (as IP/mask).
33     dst_net - Source network to apply the forward (as IP/mask).
34     """
35
36     def __init__(self, dev, protocol, port, dst, dst_port=None, src_net=None,
37                        dst_net=None):
38         r"Initialize object, see class documentation for details."
39         # TODO Validate
40         self.dev = dev
41         self.protocol = protocol
42         self.port = port
43         self.dst = dst
44         self.dst_port = dst_port
45         self.src_net = src_net
46         self.dst_net = dst_net
47
48     def update(self, dev=None, protocol=None, port=None, dst=None,
49                     dst_port=None, src_net=None, dst_net=None):
50         r"update([dev[, ...]]) -> Update the values of a port (see class doc)."
51         # TODO Validate
52         if dev is not None: self.dev = dev
53         if protocol is not None: self.protocol = protocol
54         if port is not None: self.port = port
55         if dst is not None: self.dst = dst
56         if dst_port is not None: self.dst_port = dst_port
57         if src_net is not None: self.src_net = src_net
58         if dst_net is not None: self.dst_net = dst_net
59
60     def __cmp__(self, other):
61         r"Compares two PortForward objects."
62         return cmp(self.as_tuple(), other.as_tuple())
63
64     def as_tuple(self):
65         r"Return a tuple representing the port forward."
66         return (self.dev, self.protocol, self.port, self.dst, self.dst_port,
67                     self.src_net, self.dst_net)
68
69     def as_call_list(self, index=None):
70         if self.dst_port is not None:
71             self.dst = self.dst + ':' + self.dst_port
72         cmd = ['-t', 'nat', '-I', 'PREROUTING']
73         if index is not None:
74             cmd.append(str(index))
75         cmd.extend(('-i', self.dev, '-j', 'DNAT', '--to', self.dst,
76                 '-p', self.protocol, '--dport', self.port))
77         if self.src_net is not None:
78             cmd.extend(('-s', self.src_net))
79         if self.dst_net is not None:
80             cmd.extend(('-d', self.dst_net))
81         return cmd
82
83 class PortForwardHandler(ListSubHandler):
84     r"""PortForwardHandler(parent) -> PortForwardHandler instance.
85
86     This class is a helper for NatHandler to do all the work related to port
87     forwarding.
88
89     parent - The parent service handler.
90     """
91
92     handler_help = u"Manage NAT port forwarding."
93
94     _cont_subhandler_attr = 'ports'
95     _cont_subhandler_class = PortForward
96
97 class SNat(Sequence):
98     r"""SNat(dev, src[, src_net]) -> SNat instance.
99
100     dev - Netword device to use.
101     src - Source IP address.
102     src_net - Source network to apply the NAT (as IP/mask).
103     """
104
105     def __init__(self, dev, src, src_net=None):
106         r"Initialize object, see class documentation for details."
107         # TODO Validate
108         self.dev = dev
109         self.src = src
110         self.src_net = src_net
111
112     def update(self, dev=None, src=None, src_net=None):
113         r"update([dev[, ...]]) -> Update the values of a snat (see class doc)."
114         # TODO Validate
115         if dev is not None: self.dev = dev
116         if src is not None: self.src = src
117         if src_net is not None: self.src_net = src_net
118
119     def __cmp__(self, other):
120         r"Compares two SNat objects."
121         return cmp(self.as_tuple(), other.as_tuple())
122
123     def as_tuple(self):
124         r"Return a tuple representing the snat."
125         return (self.dev, self.src, self.src_net)
126
127     def as_call_list(self, index=None):
128         cmd = ['-t', 'nat', '-I', 'POSTROUTING']
129         if index is not None:
130             cmd.append(str(index))
131         cmd.extend(('-o', self.dev, '-j', 'SNAT', '--to', self.src))
132         if self.src_net is not None:
133             cmd.extend(('-s', self.src_net))
134         return cmd
135
136 class SNatHandler(ListSubHandler):
137     r"""SNatHandler(parent) -> SNatHandler instance.
138
139     This class is a helper for NatHandler to do all the work related to
140     Source NAT.
141
142     parent - The parent service handler.
143     """
144
145     handler_help = u"Manage source NAT."
146
147     _cont_subhandler_attr = 'snats'
148     _cont_subhandler_class = SNat
149
150 class Masq(Sequence):
151     r"""Masq(dev, src_net) -> Masq instance.
152
153     dev - Netword device to use.
154     src_net - Source network to apply the masquerade (as IP/mask).
155     """
156
157     def __init__(self, dev, src_net):
158         r"Initialize object, see class documentation for details."
159         # TODO Validate
160         self.dev = dev
161         self.src_net = src_net
162
163     def update(self, dev=None, src_net=None):
164         r"update([dev[, ...]]) -> Update the values of a masq (see class doc)."
165         # TODO Validate
166         if dev is not None: self.dev = dev
167         if src_net is not None: self.src_net = src_net
168
169     def __cmp__(self, other):
170         r"Compares two Masq objects."
171         return cmp(self.as_tuple(), other.as_tuple())
172
173     def as_tuple(self):
174         r"Return a tuple representing the masquerade."
175         return (self.dev, self.src_net)
176
177     def as_call_list(self, index=None):
178         cmd = ['-t', 'nat', '-I', 'POSTROUTING']
179         if index is not None:
180             cmd.append(str(index))
181         cmd.extend(('-o', self.dev, '-j', 'MASQUERADE', '-s', self.src_net))
182         return cmd
183
184 class MasqHandler(ListSubHandler):
185     r"""MasqHandler(parent) -> MasqHandler instance.
186
187     This class is a helper for NatHandler to do all the work related to
188     masquerading.
189
190     parent - The parent service handler.
191     """
192
193     handler_help = u"Manage NAT masquerading."
194
195     _cont_subhandler_attr = 'masqs'
196     _cont_subhandler_class = Masq
197
198 class NatHandler(Restorable, ConfigWriter, RestartHandler, ReloadHandler,
199                       TransactionalHandler):
200     r"""NatHandler([pickle_dir[, config_dir]]) -> NatHandler instance.
201
202     Handles NAT commands using iptables.
203
204     pickle_dir - Directory where to write the persistent configuration data.
205
206     config_dir - Directory where to store de generated configuration files.
207
208     Both defaults to the current working directory.
209     """
210
211     handler_help = u"Manage NAT (Network Address Translation) service."
212
213     _persistent_attrs = ('ports', 'snats', 'masqs')
214
215     _restorable_defaults = dict(
216         ports=list(),
217         snats=list(),
218         masqs=list(),
219     )
220
221     @handler(u'Start the service.')
222     def start(self):
223         for (index, port) in enumerate(self.ports):
224             call(['iptables'] + port.as_call_list(index+1))
225         for (index, snat) in enumerate(self.snats):
226             call(['iptables'] + snat.as_call_list(index+1))
227         for (index, masq) in enumerate(self.masqs):
228             call(['iptables'] + masq.as_call_list(index+1))
229
230     @handler(u'Stop the service.')
231     def stop(self):
232         call(('iptables', '-t', 'nat', '-F'))
233
234     def __init__(self, pickle_dir='.'):
235         r"Initialize the object, see class documentation for details."
236         self._persistent_dir = pickle_dir
237         self._restore()
238         self.forward = PortForwardHandler(self)
239         self.snat = SNatHandler(self)
240         self.masq = MasqHandler(self)
241
242
243 if __name__ == '__main__':
244
245     import os
246
247     handler = NatHandler()
248
249     def dump():
250         print '-' * 80
251         print 'Forwarded ports:'
252         print handler.forward.show()
253         print '-' * 10
254         print 'SNat:'
255         print handler.snat.show()
256         print '-' * 10
257         print 'Masq:'
258         print handler.masq.show()
259         print '-' * 80
260
261     dump()
262     handler.forward.add('eth0','tcp','80', '192.168.0.9', '8080')
263     handler.forward.update(0, dst_net='192.168.0.188/32')
264     handler.forward.add('eth0', 'udp', '53', '192.168.1.0')
265     handler.commit()
266     handler.stop()
267     dump()
268     handler.snat.add('eth0', '192.168.0.9')
269     handler.snat.update(0, src_net='192.168.0.188/32')
270     handler.snat.add('eth0', '192.168.1.0')
271     handler.commit()
272     dump()
273     dump()
274     handler.masq.add('eth0', '192.168.0.9/24')
275     handler.masq.update(0, src_net='192.168.0.188/30')
276     handler.masq.add('eth1', '192.168.1.0/24')
277     handler.commit()
278     dump()
279
280     os.system('rm -f *.pkl')
281