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