]> git.llucax.com Git - software/pymin.git/commitdiff
Add NatHandler to handle NAT (port forwarding, snating and masquerading).
authorLeandro Lucarella <llucax@gmail.com>
Mon, 15 Oct 2007 05:22:06 +0000 (02:22 -0300)
committerLeandro Lucarella <llucax@gmail.com>
Mon, 15 Oct 2007 05:22:06 +0000 (02:22 -0300)
TODO
config.py
doc/config/iptables.txt
pymin/services/__init__.py
pymin/services/nat/__init__.py [new file with mode: 0644]

diff --git a/TODO b/TODO
index 2aead12fbcb348447997a2d39d89c8f5c7467159..3438f7e8d8f80c7820786bfd089278f58369df4d 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,6 +1,17 @@
 
 Ideas / TODO:
 
 
 Ideas / TODO:
 
+* Revisar interacción entre firewall y nat que ambos usan iptables. Es probable
+  que al manipular reglas por número de índice se complique todo porque tengo
+  indices separados por tipo de regla, entonces si pongo borrar la 2 tal vez es
+  la 13 en vez de la dos porque hay otras 11 reglas de otros sub-servicios que
+  usan iptables. Tal vez la solución simple es hacer algo como:
+  router firewall add [regla]
+  router nat masq add [masq]
+  router nat forward add [port]
+  router nat snat add [snat]
+  (u organizándolo de otra forma pero que tengan todos un root en común)
+
 * Agregar soporte de opciones de línea de comando/archivo de conf para:
   * Dry run.
   * Seleccionar servicios a usar.
 * Agregar soporte de opciones de línea de comando/archivo de conf para:
   * Dry run.
   * Seleccionar servicios a usar.
index c6a50ed0a74028433a1ab90cb706656423fb3a3d..3706ce9b53ec342a09f71e282cee9831b3324ff7 100644 (file)
--- a/config.py
+++ b/config.py
@@ -22,6 +22,7 @@ class Root(Handler):
     firewall = FirewallHandler(
         pickle_dir = join(pickle_path, 'firewall'),
         config_dir = join(config_path, 'firewall'))
     firewall = FirewallHandler(
         pickle_dir = join(pickle_path, 'firewall'),
         config_dir = join(config_path, 'firewall'))
+    nat = NatHandler(pickle_dir = join(pickle_path, 'nat'))
     ip = IpHandler(
         pickle_dir = join(pickle_path, 'ip'),
         config_dir = join(config_path, 'ip'))
     ip = IpHandler(
         pickle_dir = join(pickle_path, 'ip'),
         config_dir = join(config_path, 'ip'))
index 982c18151841ec9fa4ac7ec1a655d4dd630e759e..67d526b27521bae430705ed7d2eb3b5b5a5315fe 100644 (file)
@@ -22,6 +22,10 @@ Donde:
 - ${protocol} es udp, tcp, icmp o all
 - ${target} es DROP, ACCEPT, REJECT
 
 - ${protocol} es udp, tcp, icmp o all
 - ${target} es DROP, ACCEPT, REJECT
 
+-----------------------------------------------------------------------------------------------
+En realidad esto que sigue es generico
+-----------------------------------------------------------------------------------------------
+
 2.1) Eliminar una regla de una tabla
 
 # iptables -t ${table} -D ${chain} ${num}
 2.1) Eliminar una regla de una tabla
 
 # iptables -t ${table} -D ${chain} ${num}
@@ -33,3 +37,27 @@ Donde:
 2.3) Eliminar todas las reglas dentro de una cadena de una tabla
 
 # iptables -t ${table} -F ${chain}
 2.3) Eliminar todas las reglas dentro de una cadena de una tabla
 
 # iptables -t ${table} -F ${chain}
+
+------------------------------------------------------------------------------------------------
+
+3)  Para list reglas de NAT
+
+# XXX Luca: importa el orden? digo, si agrego una regla de fw, y luego una de
+# masq y luego otra de fw, importa si ejecuto fw 1, fw2, masq, en vez de fw1,
+# masq, fw2?
+
+# iptables -t nat -L -v
+
+3.1) Forward de puertos
+
+# iptables -t nat -I PREROUTING ${pos} -i ${dev} -j DNAT --to ${nat_dst_ip}[:${nat_dst_port}] -p <tcp|udp> --dport ${port} [-s ${src_ip}/${src_ip_prefix_length}] [-d ${dest_ip}/${dst_ip_prefix_length}]
+
+3.2) Masquerading/SNAT
+
+3.2.1) snat
+
+# iptables -t nat -I POSTROUTING ${pos} -o ${dev} -j SNAT --to ${nat_src_ip} -s ${src_ip}/${src_ip_prefix_length}
+
+3.2.2) masq
+
+# iptables -t nat -I POSTROUTING ${pos} -o ${dev} -j MASQUERADE -s ${src_ip}/${src_ip_prefix_length}
index 018633d42568bdce19a0b01c7e10db8a82e4dfc4..fabf2644c4121f67d395b99f4147648b27ed96c0 100644 (file)
@@ -3,6 +3,7 @@
 from pymin.services.dhcp import DhcpHandler
 from pymin.services.dns import DnsHandler
 from pymin.services.firewall import FirewallHandler
 from pymin.services.dhcp import DhcpHandler
 from pymin.services.dns import DnsHandler
 from pymin.services.firewall import FirewallHandler
+from pymin.services.nat import NatHandler
 from pymin.services.ip import IpHandler
 from pymin.services.proxy import ProxyHandler
 from pymin.services.vrrp import VrrpHandler
 from pymin.services.ip import IpHandler
 from pymin.services.proxy import ProxyHandler
 from pymin.services.vrrp import VrrpHandler
diff --git a/pymin/services/nat/__init__.py b/pymin/services/nat/__init__.py
new file mode 100644 (file)
index 0000000..63fdd32
--- /dev/null
@@ -0,0 +1,281 @@
+# vim: set encoding=utf-8 et sw=4 sts=4 :
+
+from os import path
+
+from pymin.seqtools import Sequence
+from pymin.dispatcher import Handler, handler, HandlerError
+from pymin.services.util import Restorable, ConfigWriter, RestartHandler, \
+                                ReloadHandler, TransactionalHandler, \
+                                ListSubHandler, call
+
+__ALL__ = ('NatHandler', 'Error')
+
+class Error(HandlerError):
+    r"""
+    Error(command) -> Error instance :: Base NatHandler exception class.
+
+    All exceptions raised by the NatHandler inherits from this one, so you can
+    easily catch any NatHandler exception.
+
+    message - A descriptive error message.
+    """
+    pass
+
+class PortForward(Sequence):
+    r"""PortForward(dev, protocol, port, dst[, dst_port[, ...]]) -> PortForward.
+
+    dev - Netword device to use.
+    protocol - TCP or UDP.
+    port - Port to forward.
+    dst - Destination IP address.
+    dst_port - Destination port (at dst).
+    src_net - Source network to apply the forward (as IP/mask).
+    dst_net - Source network to apply the forward (as IP/mask).
+    """
+
+    def __init__(self, dev, protocol, port, dst, dst_port=None, src_net=None,
+                       dst_net=None):
+        r"Initialize object, see class documentation for details."
+        # TODO Validate
+        self.dev = dev
+        self.protocol = protocol
+        self.port = port
+        self.dst = dst
+        self.dst_port = dst_port
+        self.src_net = src_net
+        self.dst_net = dst_net
+
+    def update(self, dev=None, protocol=None, port=None, dst=None,
+                    dst_port=None, src_net=None, dst_net=None):
+        r"update([dev[, ...]]) -> Update the values of a port (see class doc)."
+        # TODO Validate
+        if dev is not None: self.dev = dev
+        if protocol is not None: self.protocol = protocol
+        if port is not None: self.port = port
+        if dst is not None: self.dst = dst
+        if dst_port is not None: self.dst_port = dst_port
+        if src_net is not None: self.src_net = src_net
+        if dst_net is not None: self.dst_net = dst_net
+
+    def __cmp__(self, other):
+        r"Compares two PortForward objects."
+        return cmp(self.as_tuple(), other.as_tuple())
+
+    def as_tuple(self):
+        r"Return a tuple representing the port forward."
+        return (self.dev, self.protocol, self.port, self.dst, self.dst_port,
+                    self.src_net, self.dst_net)
+
+    def as_call_list(self, index=None):
+        if self.dst_port is not None:
+            self.dst = self.dst + ':' + self.dst_port
+        cmd = ['-t', 'nat', '-I', 'PREROUTING']
+        if index is not None:
+            cmd.append(str(index))
+        cmd.extend(('-i', self.dev, '-j', 'DNAT', '--to', self.dst,
+                '-p', self.protocol, '--dport', self.port))
+        if self.src_net is not None:
+            cmd.extend(('-s', self.src_net))
+        if self.dst_net is not None:
+            cmd.extend(('-d', self.dst_net))
+        return cmd
+
+class PortForwardHandler(ListSubHandler):
+    r"""PortForwardHandler(parent) -> PortForwardHandler instance.
+
+    This class is a helper for NatHandler to do all the work related to port
+    forwarding.
+
+    parent - The parent service handler.
+    """
+
+    handler_help = u"Manage NAT port forwarding."
+
+    _cont_subhandler_attr = 'ports'
+    _cont_subhandler_class = PortForward
+
+class SNat(Sequence):
+    r"""SNat(dev, src[, src_net]) -> SNat instance.
+
+    dev - Netword device to use.
+    src - Source IP address.
+    src_net - Source network to apply the NAT (as IP/mask).
+    """
+
+    def __init__(self, dev, src, src_net=None):
+        r"Initialize object, see class documentation for details."
+        # TODO Validate
+        self.dev = dev
+        self.src = src
+        self.src_net = src_net
+
+    def update(self, dev=None, src=None, src_net=None):
+        r"update([dev[, ...]]) -> Update the values of a snat (see class doc)."
+        # TODO Validate
+        if dev is not None: self.dev = dev
+        if src is not None: self.src = src
+        if src_net is not None: self.src_net = src_net
+
+    def __cmp__(self, other):
+        r"Compares two SNat objects."
+        return cmp(self.as_tuple(), other.as_tuple())
+
+    def as_tuple(self):
+        r"Return a tuple representing the snat."
+        return (self.dev, self.src, self.src_net)
+
+    def as_call_list(self, index=None):
+        cmd = ['-t', 'nat', '-I', 'POSTROUTING']
+        if index is not None:
+            cmd.append(str(index))
+        cmd.extend(('-o', self.dev, '-j', 'SNAT', '--to', self.src))
+        if self.src_net is not None:
+            cmd.extend(('-s', self.src_net))
+        return cmd
+
+class SNatHandler(ListSubHandler):
+    r"""SNatHandler(parent) -> SNatHandler instance.
+
+    This class is a helper for NatHandler to do all the work related to
+    Source NAT.
+
+    parent - The parent service handler.
+    """
+
+    handler_help = u"Manage source NAT."
+
+    _cont_subhandler_attr = 'snats'
+    _cont_subhandler_class = SNat
+
+class Masq(Sequence):
+    r"""Masq(dev, src_net) -> Masq instance.
+
+    dev - Netword device to use.
+    src_net - Source network to apply the masquerade (as IP/mask).
+    """
+
+    def __init__(self, dev, src_net):
+        r"Initialize object, see class documentation for details."
+        # TODO Validate
+        self.dev = dev
+        self.src_net = src_net
+
+    def update(self, dev=None, src_net=None):
+        r"update([dev[, ...]]) -> Update the values of a masq (see class doc)."
+        # TODO Validate
+        if dev is not None: self.dev = dev
+        if src_net is not None: self.src_net = src_net
+
+    def __cmp__(self, other):
+        r"Compares two Masq objects."
+        return cmp(self.as_tuple(), other.as_tuple())
+
+    def as_tuple(self):
+        r"Return a tuple representing the masquerade."
+        return (self.dev, self.src_net)
+
+    def as_call_list(self, index=None):
+        cmd = ['-t', 'nat', '-I', 'POSTROUTING']
+        if index is not None:
+            cmd.append(str(index))
+        cmd.extend(('-o', self.dev, '-j', 'MASQUERADE', '-s', self.src_net))
+        return cmd
+
+class MasqHandler(ListSubHandler):
+    r"""MasqHandler(parent) -> MasqHandler instance.
+
+    This class is a helper for NatHandler to do all the work related to
+    masquerading.
+
+    parent - The parent service handler.
+    """
+
+    handler_help = u"Manage NAT masquerading."
+
+    _cont_subhandler_attr = 'masqs'
+    _cont_subhandler_class = Masq
+
+class NatHandler(Restorable, ConfigWriter, RestartHandler, ReloadHandler,
+                      TransactionalHandler):
+    r"""NatHandler([pickle_dir[, config_dir]]) -> NatHandler instance.
+
+    Handles NAT commands using iptables.
+
+    pickle_dir - Directory where to write the persistent configuration data.
+
+    config_dir - Directory where to store de generated configuration files.
+
+    Both defaults to the current working directory.
+    """
+
+    handler_help = u"Manage NAT (Network Address Translation) service."
+
+    _persistent_attrs = ('ports', 'snats', 'masqs')
+
+    _restorable_defaults = dict(
+        ports=list(),
+        snats=list(),
+        masqs=list(),
+    )
+
+    @handler(u'Start the service.')
+    def start(self):
+        for (index, port) in enumerate(self.ports):
+            call(['iptables'] + port.as_call_list(index+1))
+        for (index, snat) in enumerate(self.snats):
+            call(['iptables'] + snat.as_call_list(index+1))
+        for (index, masq) in enumerate(self.masqs):
+            call(['iptables'] + masq.as_call_list(index+1))
+
+    @handler(u'Stop the service.')
+    def stop(self):
+        call(('iptables', '-t', 'nat', '-F'))
+
+    def __init__(self, pickle_dir='.'):
+        r"Initialize the object, see class documentation for details."
+        self._persistent_dir = pickle_dir
+        self._restore()
+        self.forward = PortForwardHandler(self)
+        self.snat = SNatHandler(self)
+        self.masq = MasqHandler(self)
+
+
+if __name__ == '__main__':
+
+    import os
+
+    handler = NatHandler()
+
+    def dump():
+        print '-' * 80
+        print 'Forwarded ports:'
+        print handler.forward.show()
+        print '-' * 10
+        print 'SNat:'
+        print handler.snat.show()
+        print '-' * 10
+        print 'Masq:'
+        print handler.masq.show()
+        print '-' * 80
+
+    dump()
+    handler.forward.add('eth0','tcp','80', '192.168.0.9', '8080')
+    handler.forward.update(0, dst_net='192.168.0.188/32')
+    handler.forward.add('eth0', 'udp', '53', '192.168.1.0')
+    handler.commit()
+    handler.stop()
+    dump()
+    handler.snat.add('eth0', '192.168.0.9')
+    handler.snat.update(0, src_net='192.168.0.188/32')
+    handler.snat.add('eth0', '192.168.1.0')
+    handler.commit()
+    dump()
+    dump()
+    handler.masq.add('eth0', '192.168.0.9/24')
+    handler.masq.update(0, src_net='192.168.0.188/30')
+    handler.masq.add('eth1', '192.168.1.0/24')
+    handler.commit()
+    dump()
+
+    os.system('rm -f *.pkl')
+