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