Refactored, added support for port forwarding
authorAndy Bavier <acb@cs.princeton.edu>
Fri, 20 Sep 2013 20:09:22 +0000 (16:09 -0400)
committerAndy Bavier <acb@cs.princeton.edu>
Fri, 20 Sep 2013 20:09:22 +0000 (16:09 -0400)
plugins/planetstack-net.py

index b567674..ded009d 100644 (file)
@@ -1,3 +1,19 @@
+"""
+This plugin sets up the NAT interfaces for PlanetStack. It processes
+each interface that has a 'nat' tag set.
+
+It communicates with OvS on the local node and Quantum to gather
+information about devices.  It uses this information to:
+* add the Quantum-assigned IP address to the interface via dnsmasq
+* set up port forwarding rules through the NAT using iptables
+
+The iptables configuration uses a chain called 'planetstack-net' to
+hold the port forwarding rules.  This is called from the PREROUTING
+chain of the nat table. The chain is flushed and rebuilt every time
+the plugin runs to avoid stale rules.  This plugin also sets up the
+MASQ rule in the POSTROUTING chain.
+"""
+
 # system provided modules
 import os, string, time, socket
 from socket import inet_aton
@@ -44,6 +60,33 @@ def ipaddr_range(network, broadcast):
 
     return '.'.join(start) + ',' + '.'.join(end)
 
+# Should possibly be using python-iptables for this stuff
+def run_iptables_cmd(args):
+    cmd = ['/sbin/iptables'] + args
+    logger.log('%s: %s' % (plugin, ' '.join(cmd)))
+    subprocess.check_call(cmd)
+    
+def add_iptables_rule(table, chain, args):
+    args = ['-t', table, '-C',  chain] + args
+    try:
+        run_iptables_cmd(args)
+    except:
+        args[2] = '-A'
+        try:
+            run_iptables_cmd(args)
+        except:
+            logger.log('%s: FAILED to add iptables rule' % plugin)
+
+def reset_iptables_chain():
+    try:
+        # Flush the planetstack-nat chain
+        run_iptables_cmd(['-t', 'nat', '-F', plugin])
+    except:
+        # Probably the chain doesn't exist, try creating it
+        run_iptables_cmd(['-t', 'nat', '-N', plugin]) 
+
+    add_iptables_rule('nat', 'PREROUTING', ['-j', plugin]) 
+
 # Enable iptables MASQ for a device
 def add_iptables_masq(dev, interface):
     ipaddr = interface['ip']
@@ -57,18 +100,8 @@ def add_iptables_masq(dev, interface):
                    % (plugin, ipaddr, netmask))
         return
     
-    cmd = ['/sbin/iptables', '-t', 'nat', '-C',  'POSTROUTING', '-s',  cidr, 
-           '!',  '-d',  cidr, '-j', 'MASQUERADE']
-    try:
-        logger.log('%s: checking if NAT iptables rule present for device %s' % (plugin, dev))
-        subprocess.check_call(cmd)
-    except:
-        logger.log('%s: adding NAT iptables NAT for device %s' % (plugin, dev))
-        cmd[3] = '-A'
-        try:
-            subprocess.check_call(cmd)
-        except:
-            logger.log('%s: FAILED to add NAT iptables rule for device %s' % (plugin, dev))
+    args = ['-s',  cidr, '!',  '-d',  cidr, '-j', 'MASQUERADE']
+    add_iptables_rule('nat', 'POSTROUTING', args)
 
 def get_pidfile(dev):
     return '/var/run/dnsmasq-%s.pid' % dev
@@ -154,17 +187,19 @@ def convert_ovs_output_to_dict(out):
 
     return records
 
-# Generate a dhcp-hostsfile for dnsmasq for all the local interfaces
-# on the NAT network.  The purpose is to make sure that the IP address
-# assigned by Quantum appears on NAT interfaces.
+# Do all processing associated with Quantum ports.  It first gets a
+# list of local VM interfaces and then queries Quantum to get Port
+# records for these interfaces.  Then for all interfaces on the NAT
+# network it does the following:
+#
+# 1) Generates a dhcp-hostsfile for dnsmasq.  The purpose is to make
+# sure that the IP address assigned by Quantum appears on NAT
+# interface.
 #
-# Workflow:
-# - Get list of local VM interfaces
-# - Query Quantum to get port information for these interfaces
-# - Throw away all those not belonging to nat-net network
-# - Write the MAC addr and IPs to the dhcp-hostsfile file (and log it)
+# 2) Sets up iptables rules in the 'planetstack-net' chain based on 
+# the nat:forward_ports field in the Port record.  
 
-def write_hostsfile(dev):
+def process_quantum_ports(dev):
     # Get local information for VM interfaces from OvS
     ovs_out = subprocess.check_output(['/usr/bin/ovs-vsctl', '-f', 'json', 'find', 
                                        'Interface', 'external_ids:iface-id!="absent"'])
@@ -181,6 +216,7 @@ def write_hostsfile(dev):
                             tenant_name=quantum_tenant_name,
                             auth_url=quantum_auth_url)
     ports = quantum.list_ports(id=port_ids)
+    # logger.log("%s: %s" % (plugin, ports))
 
     # Write relevant entries to dnsmasq hostsfile
     logger.log("%s: Writing hostsfile for %s" % (plugin, dev))
@@ -195,6 +231,18 @@ def write_hostsfile(dev):
     # Send SIGHUP to dnsmasq to make it re-read hostsfile
     dnsmasq_sighup(dev)
 
+    # Set up iptables rules for port forwarding
+    for port in ports['ports']:
+        if port['network_id'] == nat_net_id:
+            for fw in port['nat:forward_ports']:
+                ipaddr = port['fixed_ips'][0]['ip_address']
+                protocol = fw['l4_protocol']
+                fwport = fw['l4_port']
+                # logger.log("%s: fwd port %s/%s to %s" % (plugin, protocol, fwport, ipaddr))
+                add_iptables_rule('nat', plugin, ['-i', 'br-eth0', 
+                                                  '-p', protocol, '--dport', fwport,
+                                                  '-j', 'DNAT', '--to-destination', ipaddr])
+            
 def start():
     global quantum_username
     global quantum_password
@@ -220,6 +268,7 @@ def start():
     logger.log("%s: %s id is %s..." % (plugin, nat_net_name, nat_net_id))
 
 def GetSlivers(data, config=None, plc=None):
+    reset_iptables_chain()
     for interface in data['interfaces']:
         try:
             settings = plc.GetInterfaceTags({'interface_tag_id': interface['interface_tag_ids']})
@@ -241,5 +290,5 @@ def GetSlivers(data, config=None, plc=None):
         
         if 'NAT' in tags:
             add_iptables_masq(dev, interface)
-            write_hostsfile(dev)
+            process_quantum_ports(dev)
             start_dnsmasq(dev, interface)