From bf0d65b210c3c1924ccde50839a71f7e2c66aecd Mon Sep 17 00:00:00 2001 From: Andy Bavier Date: Fri, 20 Sep 2013 16:09:22 -0400 Subject: [PATCH] Refactored, added support for port forwarding --- plugins/planetstack-net.py | 93 +++++++++++++++++++++++++++++--------- 1 file changed, 71 insertions(+), 22 deletions(-) diff --git a/plugins/planetstack-net.py b/plugins/planetstack-net.py index b567674..ded009d 100644 --- a/plugins/planetstack-net.py +++ b/plugins/planetstack-net.py @@ -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) -- 2.47.0