Support NAT/port forwards.
authorDaniel Hokka Zakrisson <dhokka@cs.princeton.edu>
Tue, 4 Dec 2007 22:41:31 +0000 (22:41 +0000)
committerDaniel Hokka Zakrisson <dhokka@cs.princeton.edu>
Tue, 4 Dec 2007 22:41:31 +0000 (22:41 +0000)
iptables.py [new file with mode: 0644]
net.py

diff --git a/iptables.py b/iptables.py
new file mode 100644 (file)
index 0000000..fa83ee6
--- /dev/null
@@ -0,0 +1,99 @@
+#!/usr/bin/python -tt
+
+# Author: Daniel Hokka Zakrisson <daniel@hozac.com>
+# $Id$
+
+import os
+import logger
+import subprocess
+
+class IPTables:
+    IPTABLES_RESTORE = "/sbin/iptables-restore"
+
+    def __init__(self):
+        self.extifs = []
+        self.intifs = []
+        self.pfs = []
+
+    def add(self, table, chain, rule):
+        self.rules[table][chain].append(rule)
+
+    def add_ext(self, interface):
+        self.extifs.append(interface)
+
+    def add_int(self, interface):
+        self.intifs.append(interface)
+
+    def add_pf(self, pf):
+        # XXX Should make sure the required fields are there
+        self.pfs.append(pf)
+
+    def commit(self):
+        # XXX This should check for errors
+        #     and make sure the new ruleset differs from the current one
+
+        if (len(self.extifs) + len(self.intifs) + len(self.pfs)) == 0:
+            return True
+
+        restore = subprocess.Popen([self.IPTABLES_RESTORE], stdin=subprocess.PIPE)
+        restore.stdin.write("""*filter
+:INPUT ACCEPT [0:0]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:BLACKLIST - [0:0]
+:LOGDROP - [0:0]
+:SLICESPRE - [0:0]
+:SLICES - [0:0]
+:PORTFW - [0:0]
+
+-A LOGDROP -j LOG
+-A LOGDROP -j DROP
+-A OUTPUT -j BLACKLIST
+-A OUTPUT -m mark ! --mark 0/65535 -j SLICESPRE
+-A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
+""")
+
+        for int in self.intifs:
+            for ext in self.extifs:
+                restore.stdin.write("-A FORWARD -i %s -o %s -j ACCEPT\n" % (int, ext))
+            restore.stdin.write("-A SLICESPRE -o %s -j SLICES\n" % int)
+
+        restore.stdin.write("-A FORWARD -m state --state NEW -j PORTFW\n")
+        for pf in self.pfs:
+            rule = "-A PORTFW -p %s -d %s " % (pf['protocol'], pf['destination'])
+            if 'interface' in pf:
+                rule += "-i %s " % pf['interface']
+            if 'source' in pf:
+                rule += "-s %s " % pf['source']
+            rule += "--dport %s" % pf['new_dport']
+            restore.stdin.write(rule + "\n")
+
+        restore.stdin.write("-A FORWARD -j LOGDROP\n")
+
+        # This should have a way to add rules
+        restore.stdin.write("-A SLICES -j LOGDROP\n")
+        restore.stdin.write("""COMMIT
+*nat
+:PREROUTING ACCEPT [0:0]
+:POSTROUTING ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:PORTFW - [0:0]
+:MASQ - [0:0]
+""")
+
+        for ext in self.extifs:
+            restore.stdin.write("-A MASQ -o %s -j MASQUERADE\n")
+
+        for pf in self.pfs:
+            rule = "-A PORTFW -p %s " % pf['protocol']
+            if 'interface' in pf:
+                rule += "-i %s " % pf['interface']
+            if 'source' in pf:
+                rule += "-s %s " % pf['source']
+            rule += "--dport %s -j DNAT --to %s:%s" % (pf['dport'], pf['destination'],
+                    pf['new_dport'])
+            restore.stdin.write(rule + "\n")
+
+        restore.stdin.write("COMMIT\n")
+        restore.stdin.close()
+        return restore.wait() == 0
diff --git a/net.py b/net.py
index f333e83..b0874a6 100644 (file)
--- a/net.py
+++ b/net.py
@@ -6,10 +6,12 @@ import sioc
 import bwlimit
 import logger
 import string
+import iptables
 
 def GetSlivers(plc, data):
     InitNodeLimit(data)
     InitI2(plc, data)
+    InitNAT(plc, data)
 
 def InitNodeLimit(data):
     # query running network interfaces
@@ -61,5 +63,51 @@ def InitI2(plc, data):
             i2nodes.append(node['ip'])
         bwlimit.exempt_init('Internet2', i2nodes)
 
+def InitNAT(plc, data):
+    # query running network interfaces
+    devs = sioc.gifconf()
+    ips = dict(zip(devs.values(), devs.keys()))
+    macs = {}
+    for dev in devs:
+        macs[sioc.gifhwaddr(dev).lower()] = dev
+
+    ipt = iptables.IPTables()
+    for network in data['networks']:
+        # Get interface name preferably from MAC address, falling
+        # back on IP address.
+        if macs.has_key(network['mac']):
+            dev = macs[network['mac'].lower()]
+        elif ips.has_key(network['ip']):
+            dev = ips[network['ip']]
+        else:
+            logger.log('%s: no such interface with address %s/%s' % (network['hostname'], network['ip'], network['mac']))
+            continue
+
+        try:
+            settings = plc.GetNodeNetworkSettings({'nodenetwork_setting_id': network['nodenetwork_setting_ids']})
+        except:
+            continue
+        # XXX arbitrary names
+        for setting in settings:
+            if setting['category'] != 'firewall':
+                continue
+            if setting['name'] == 'external':
+                # Enable NAT for this interface
+                ipt.add_ext(dev)
+            elif setting['name'] == 'internal':
+                ipt.add_int(dev)
+            elif setting['name'] == 'pf': # XXX Uglier code is hard to find...
+                for pf in setting['value'].split("\n"):
+                    fields = {}
+                    for field in pf.split(","):
+                        (key, val) = field.split("=", 2)
+                        fields[key] = val
+                    if 'new_dport' not in fields:
+                        fields['new_dport'] = fields['dport']
+                    if 'source' not in fields:
+                        fields['source'] = "0.0.0.0/0"
+                    ipt.add_pf(fields)
+    ipt.commit()
+
 def start(options, config):
     pass