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.service.util import Restorable, ConfigWriter, RestartHandler, \
9 ReloadHandler, TransactionalHandler, \
10 ServiceHandler, ListSubHandler, call
12 __all__ = ('NatHandler', 'get_service')
15 def get_service(config):
16 return NatHandler(config.nat.pickle_dir)
19 class PortForward(Sequence):
20 r"""PortForward(dev, protocol, port, dst[, dst_port[, ...]]) -> PortForward.
22 dev - Netword device to use.
23 protocol - TCP or UDP.
24 port - Port to forward.
25 dst - Destination IP address.
26 dst_port - Destination port (at dst).
27 src_net - Source network to apply the forward (as IP/mask).
28 dst_net - Source network to apply the forward (as IP/mask).
31 def __init__(self, dev, protocol, port, dst, dst_port=None, src_net=None,
33 r"Initialize object, see class documentation for details."
36 self.protocol = protocol
39 self.dst_port = dst_port
40 self.src_net = src_net
41 self.dst_net = dst_net
43 def update(self, dev=None, protocol=None, port=None, dst=None,
44 dst_port=None, src_net=None, dst_net=None):
45 r"update([dev[, ...]]) -> Update the values of a port (see class doc)."
47 if dev is not None: self.dev = dev
48 if protocol is not None: self.protocol = protocol
49 if port is not None: self.port = port
50 if dst is not None: self.dst = dst
51 if dst_port is not None: self.dst_port = dst_port
52 if src_net is not None: self.src_net = src_net
53 if dst_net is not None: self.dst_net = dst_net
56 r"Return a tuple representing the port forward."
57 return (self.dev, self.protocol, self.port, self.dst, self.dst_port,
58 self.src_net, self.dst_net)
60 def as_call_list(self, index=None):
61 if self.dst_port is not None:
62 self.dst = self.dst + ':' + self.dst_port
63 cmd = ['-t', 'nat', '-I', 'PREROUTING']
65 cmd.append(str(index))
66 cmd.extend(('-i', self.dev, '-j', 'DNAT', '--to', self.dst,
67 '-p', self.protocol, '--dport', self.port))
68 if self.src_net is not None:
69 cmd.extend(('-s', self.src_net))
70 if self.dst_net is not None:
71 cmd.extend(('-d', self.dst_net))
74 class PortForwardHandler(ListSubHandler):
75 r"""PortForwardHandler(parent) -> PortForwardHandler instance.
77 This class is a helper for NatHandler to do all the work related to port
80 parent - The parent service handler.
83 handler_help = u"Manage NAT port forwarding."
85 _cont_subhandler_attr = 'ports'
86 _cont_subhandler_class = PortForward
89 r"""SNat(dev, src[, src_net]) -> SNat instance.
91 dev - Netword device to use.
92 src - Source IP address.
93 src_net - Source network to apply the NAT (as IP/mask).
96 def __init__(self, dev, src, src_net=None):
97 r"Initialize object, see class documentation for details."
101 self.src_net = src_net
103 def update(self, dev=None, src=None, src_net=None):
104 r"update([dev[, ...]]) -> Update the values of a snat (see class doc)."
106 if dev is not None: self.dev = dev
107 if src is not None: self.src = src
108 if src_net is not None: self.src_net = src_net
110 def __cmp__(self, other):
111 r"Compares two SNat objects."
112 return cmp(self.as_tuple(), other.as_tuple())
115 r"Return a tuple representing the snat."
116 return (self.dev, self.src, self.src_net)
118 def as_call_list(self, index=None):
119 cmd = ['-t', 'nat', '-I', 'POSTROUTING']
120 if index is not None:
121 cmd.append(str(index))
122 cmd.extend(('-o', self.dev, '-j', 'SNAT', '--to', self.src))
123 if self.src_net is not None:
124 cmd.extend(('-s', self.src_net))
127 class SNatHandler(ListSubHandler):
128 r"""SNatHandler(parent) -> SNatHandler instance.
130 This class is a helper for NatHandler to do all the work related to
133 parent - The parent service handler.
136 handler_help = u"Manage source NAT."
138 _cont_subhandler_attr = 'snats'
139 _cont_subhandler_class = SNat
141 class Masq(Sequence):
142 r"""Masq(dev, src_net) -> Masq instance.
144 dev - Netword device to use.
145 src_net - Source network to apply the masquerade (as IP/mask).
148 def __init__(self, dev, src_net):
149 r"Initialize object, see class documentation for details."
152 self.src_net = src_net
154 def update(self, dev=None, src_net=None):
155 r"update([dev[, ...]]) -> Update the values of a masq (see class doc)."
157 if dev is not None: self.dev = dev
158 if src_net is not None: self.src_net = src_net
160 def __cmp__(self, other):
161 r"Compares two Masq objects."
162 return cmp(self.as_tuple(), other.as_tuple())
165 r"Return a tuple representing the masquerade."
166 return (self.dev, self.src_net)
168 def as_call_list(self, index=None):
169 cmd = ['-t', 'nat', '-I', 'POSTROUTING']
170 if index is not None:
171 cmd.append(str(index))
172 cmd.extend(('-o', self.dev, '-j', 'MASQUERADE', '-s', self.src_net))
175 class MasqHandler(ListSubHandler):
176 r"""MasqHandler(parent) -> MasqHandler instance.
178 This class is a helper for NatHandler to do all the work related to
181 parent - The parent service handler.
184 handler_help = u"Manage NAT masquerading."
186 _cont_subhandler_attr = 'masqs'
187 _cont_subhandler_class = Masq
189 class NatHandler(Restorable, ConfigWriter, ReloadHandler, ServiceHandler,
190 TransactionalHandler):
191 r"""NatHandler([pickle_dir[, config_dir]]) -> NatHandler instance.
193 Handles NAT commands using iptables.
195 pickle_dir - Directory where to write the persistent configuration data.
197 config_dir - Directory where to store de generated configuration files.
199 Both defaults to the current working directory.
202 handler_help = u"Manage NAT (Network Address Translation) service."
204 _persistent_attrs = ('ports', 'snats', 'masqs')
206 _restorable_defaults = dict(
212 def _service_start(self):
213 log.debug(u'NatHandler._service_start(): flushing nat table')
214 call(('iptables', '-t', 'nat', '-F'))
215 for (index, port) in enumerate(self.ports):
216 log.debug(u'NatHandler._service_start: adding port %r', port)
217 call(['iptables'] + port.as_call_list(index+1))
218 for (index, snat) in enumerate(self.snats):
219 log.debug(u'NatHandler._service_start: adding snat %r', snat)
220 call(['iptables'] + snat.as_call_list(index+1))
221 for (index, masq) in enumerate(self.masqs):
222 log.debug(u'NatHandler._service_start: adding masq %r', masq)
223 call(['iptables'] + masq.as_call_list(index+1))
225 def _service_stop(self):
226 log.debug(u'NatHandler._service_stop(): flushing nat table')
227 call(('iptables', '-t', 'nat', '-F'))
229 _service_restart = _service_start
231 def __init__(self, pickle_dir='.'):
232 r"Initialize the object, see class documentation for details."
233 log.debug(u'NatHandler(%r)', pickle_dir)
234 self._persistent_dir = pickle_dir
235 ServiceHandler.__init__(self)
236 self.forward = PortForwardHandler(self)
237 self.snat = SNatHandler(self)
238 self.masq = MasqHandler(self)
241 if __name__ == '__main__':
244 level = logging.DEBUG,
245 format = '%(asctime)s %(levelname)-8s %(message)s',
246 datefmt = '%H:%M:%S',
251 handler = NatHandler()
255 print 'Forwarded ports:'
256 print handler.forward.show()
259 print handler.snat.show()
262 print handler.masq.show()
266 handler.forward.add('eth0','tcp','80', '192.168.0.9', '8080')
267 handler.forward.update(0, dst_net='192.168.0.188/32')
268 handler.forward.add('eth0', 'udp', '53', '192.168.1.0')
272 handler.snat.add('eth0', '192.168.0.9')
273 handler.snat.update(0, src_net='192.168.0.188/32')
274 handler.snat.add('eth0', '192.168.1.0')
278 handler.masq.add('eth0', '192.168.0.9/24')
279 handler.masq.update(0, src_net='192.168.0.188/30')
280 handler.masq.add('eth1', '192.168.1.0/24')
284 os.system('rm -f *.pkl')