1 # vim: set encoding=utf-8 et sw=4 sts=4 :
4 import logging ; log = logging.getLogger('pymin.services.nat')
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
12 __all__ = ('NatHandler',)
14 class PortForward(Sequence):
15 r"""PortForward(dev, protocol, port, dst[, dst_port[, ...]]) -> PortForward.
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).
26 def __init__(self, dev, protocol, port, dst, dst_port=None, src_net=None,
28 r"Initialize object, see class documentation for details."
31 self.protocol = protocol
34 self.dst_port = dst_port
35 self.src_net = src_net
36 self.dst_net = dst_net
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)."
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
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)
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']
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))
69 class PortForwardHandler(ListSubHandler):
70 r"""PortForwardHandler(parent) -> PortForwardHandler instance.
72 This class is a helper for NatHandler to do all the work related to port
75 parent - The parent service handler.
78 handler_help = u"Manage NAT port forwarding."
80 _cont_subhandler_attr = 'ports'
81 _cont_subhandler_class = PortForward
84 r"""SNat(dev, src[, src_net]) -> SNat instance.
86 dev - Netword device to use.
87 src - Source IP address.
88 src_net - Source network to apply the NAT (as IP/mask).
91 def __init__(self, dev, src, src_net=None):
92 r"Initialize object, see class documentation for details."
96 self.src_net = src_net
98 def update(self, dev=None, src=None, src_net=None):
99 r"update([dev[, ...]]) -> Update the values of a snat (see class doc)."
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
105 def __cmp__(self, other):
106 r"Compares two SNat objects."
107 return cmp(self.as_tuple(), other.as_tuple())
110 r"Return a tuple representing the snat."
111 return (self.dev, self.src, self.src_net)
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))
122 class SNatHandler(ListSubHandler):
123 r"""SNatHandler(parent) -> SNatHandler instance.
125 This class is a helper for NatHandler to do all the work related to
128 parent - The parent service handler.
131 handler_help = u"Manage source NAT."
133 _cont_subhandler_attr = 'snats'
134 _cont_subhandler_class = SNat
136 class Masq(Sequence):
137 r"""Masq(dev, src_net) -> Masq instance.
139 dev - Netword device to use.
140 src_net - Source network to apply the masquerade (as IP/mask).
143 def __init__(self, dev, src_net):
144 r"Initialize object, see class documentation for details."
147 self.src_net = src_net
149 def update(self, dev=None, src_net=None):
150 r"update([dev[, ...]]) -> Update the values of a masq (see class doc)."
152 if dev is not None: self.dev = dev
153 if src_net is not None: self.src_net = src_net
155 def __cmp__(self, other):
156 r"Compares two Masq objects."
157 return cmp(self.as_tuple(), other.as_tuple())
160 r"Return a tuple representing the masquerade."
161 return (self.dev, self.src_net)
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))
170 class MasqHandler(ListSubHandler):
171 r"""MasqHandler(parent) -> MasqHandler instance.
173 This class is a helper for NatHandler to do all the work related to
176 parent - The parent service handler.
179 handler_help = u"Manage NAT masquerading."
181 _cont_subhandler_attr = 'masqs'
182 _cont_subhandler_class = Masq
184 class NatHandler(Restorable, ConfigWriter, ReloadHandler, ServiceHandler,
185 TransactionalHandler):
186 r"""NatHandler([pickle_dir[, config_dir]]) -> NatHandler instance.
188 Handles NAT commands using iptables.
190 pickle_dir - Directory where to write the persistent configuration data.
192 config_dir - Directory where to store de generated configuration files.
194 Both defaults to the current working directory.
197 handler_help = u"Manage NAT (Network Address Translation) service."
199 _persistent_attrs = ('ports', 'snats', 'masqs')
201 _restorable_defaults = dict(
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))
220 def _service_stop(self):
221 log.debug(u'NatHandler._service_stop(): flushing nat table')
222 call(('iptables', '-t', 'nat', '-F'))
224 _service_restart = _service_start
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)
236 if __name__ == '__main__':
239 level = logging.DEBUG,
240 format = '%(asctime)s %(levelname)-8s %(message)s',
241 datefmt = '%H:%M:%S',
246 handler = NatHandler()
250 print 'Forwarded ports:'
251 print handler.forward.show()
254 print handler.snat.show()
257 print handler.masq.show()
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')
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')
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')
279 os.system('rm -f *.pkl')