From e64ef2834756a1a4e3f8d0dc843eb144f528f11e Mon Sep 17 00:00:00 2001 From: Daniel Hokka Zakrisson Date: Tue, 4 Dec 2007 22:41:31 +0000 Subject: [PATCH] Support NAT/port forwards. --- iptables.py | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++ net.py | 48 ++++++++++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 iptables.py diff --git a/iptables.py b/iptables.py new file mode 100644 index 0000000..fa83ee6 --- /dev/null +++ b/iptables.py @@ -0,0 +1,99 @@ +#!/usr/bin/python -tt + +# Author: Daniel Hokka Zakrisson +# $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 --- 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 -- 2.43.0