"""
-This plugin sets up the NAT interfaces for PlanetStack. It processes
-each interface that has a 'nat' tag set.
+This plugin sets up dnsmasq and iptables to support the "Private-Nat"
+and "Public" network models for OpenCloud. It communicates with OvS
+on the local node and Quantum to gather information about the virtual
+interfaces instantiated by Quantum. It uses this information to:
-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
+* add the Quantum-assigned IP address to the vif via dnsmasq
* set up port forwarding rules through the NAT using iptables
The iptables configuration uses a chain called 'planetstack-net' to
plugin = "planetstack-net"
+nat_net_name = "nat-net"
+nat_net_id = None
+site_net_id = None
+
+quantum_auth_url = None
+quantum_username = None
+quantum_password = None
+quantum_tenant_name = None
+
+
# Helper functions for converting to CIDR notation
def get_net_size(netmask):
binary_str = ''
logger.log('%s: %s' % (plugin, ' '.join(cmd)))
subprocess.check_call(cmd)
-def add_iptables_rule(table, chain, args):
- args = ['-t', table, '-C', chain] + args
+def add_iptables_rule(table, chain, args, pos = None):
+ iptargs = ['-t', table, '-C', chain] + args
try:
- run_iptables_cmd(args)
+ run_iptables_cmd(iptargs)
except:
- args[2] = '-A'
+ if pos:
+ iptargs = ['-t', table, '-I', chain, str(pos)] + args
+ else:
+ iptargs[2] = '-A'
try:
- run_iptables_cmd(args)
+ run_iptables_cmd(iptargs)
except:
logger.log('%s: FAILED to add iptables rule' % plugin)
add_iptables_rule('nat', 'PREROUTING', ['-j', plugin])
+# Nova blocks packets from external addresses by default.
+# This is hacky but it gets around the issue.
+def unfilter_ipaddr(dev, ipaddr):
+ add_iptables_rule(table = 'filter',
+ chain = 'nova-compute-sg-fallback',
+ args = ['-d', ipaddr, '-j', 'ACCEPT'],
+ pos = 1)
+
# Enable iptables MASQ for a device
def add_iptables_masq(dev, interface):
ipaddr = interface['ip']
except:
logger.log("%s: Sending SIGHUP to dnsmasq FAILED on dev %s" % (plugin, dev))
-# Enable dnsmasq for this interface
-def start_dnsmasq(dev, interface):
+# Enable dnsmasq for this interface.
+# It's possible that we could get by with a single instance of dnsmasq running on
+# all devices but I haven't tried it.
+def start_dnsmasq(dev, interface, forward_dns=True):
if not dnsmasq_running(dev):
+ # The '--dhcp-range=<IP addr>,static' argument to dnsmasq ensures that it only
+ # hands out IP addresses to clients listed in the hostsfile
+ cmd = ['/usr/sbin/dnsmasq',
+ '--strict-order',
+ '--bind-interfaces',
+ '--local=//',
+ '--domain-needed',
+ '--pid-file=%s' % get_pidfile(dev),
+ '--conf-file=',
+ '--interface=%s' % dev,
+ '--except-interface=lo',
+ '--dhcp-leasefile=%s' % get_leasefile(dev),
+ '--dhcp-hostsfile=%s' % get_hostsfile(dev),
+ '--dhcp-no-override',
+ '--dhcp-range=%s,static' % interface['ip']]
+
+ # Turn off forwarding DNS queries, only do DHCP
+ if forward_dns == False:
+ cmd.append('--port=0')
+
try:
logger.log('%s: starting dnsmasq on device %s' % (plugin, dev))
- iprange = ipaddr_range(interface['network'], interface['broadcast'])
- logger.log('%s: IP range: %s' % (plugin, iprange))
- subprocess.check_call(['/usr/sbin/dnsmasq',
- '--strict-order',
- '--bind-interfaces',
- '--local=//',
- '--domain-needed',
- '--pid-file=%s' % get_pidfile(dev),
- '--conf-file=',
- '--interface=%s' % dev,
- '--dhcp-range=%s,120' % iprange,
- '--dhcp-leasefile=%s' % get_leasefile(dev),
- '--dhcp-hostsfile=%s' % get_hostsfile(dev),
- '--dhcp-no-override'])
+ subprocess.check_call(cmd)
except:
logger.log('%s: FAILED to start dnsmasq for device %s' % (plugin, dev))
-
-nat_net_name = "nat-net"
-nat_net_id = None
-quantum_auth_url = "http://viccidev1:5000/v2.0/"
-quantum_username = None
-quantum_password = None
-quantum_tenant_name = None
+ logger.log(' '.join(cmd))
def convert_ovs_output_to_dict(out):
decoded = json.loads(out.strip())
return records
-# 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.
-#
-# 2) Sets up iptables rules in the 'planetstack-net' chain based on
-# the nat:forward_ports field in the Port record.
-
-def process_quantum_ports(dev):
+
+# Get a list of local VM interfaces and then query Quantum to get
+# Port records for these interfaces.
+def get_local_quantum_ports():
+ ports = []
+
# 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"'])
records = convert_ovs_output_to_dict(ovs_out)
- # Extract Quantum Port IDs from OvS records
- port_ids = []
- for rec in records:
- port_ids.append(rec['external_ids']['iface-id'])
+ if records:
+ # Extract Quantum Port IDs from OvS records
+ port_ids = []
+ for rec in records:
+ port_ids.append(rec['external_ids']['iface-id'])
- # Get the full info on these ports from Quantum
- quantum = client.Client(username=quantum_username,
- password=quantum_password,
- tenant_name=quantum_tenant_name,
- auth_url=quantum_auth_url)
- ports = quantum.list_ports(id=port_ids)
- # logger.log("%s: %s" % (plugin, ports))
+ # Get the full info on these ports from Quantum
+ quantum = client.Client(username=quantum_username,
+ password=quantum_password,
+ tenant_name=quantum_tenant_name,
+ auth_url=quantum_auth_url)
+ ports = quantum.list_ports(id=port_ids)['ports']
+
+ return ports
- # Write relevant entries to dnsmasq hostsfile
+
+# Generate a dhcp-hostsfile for dnsmasq. The purpose is to make sure
+# that the IP address assigned by Quantum appears on NAT interface.
+def write_dnsmasq_hostsfile(dev, ports, net_id):
logger.log("%s: Writing hostsfile for %s" % (plugin, dev))
f = open(get_hostsfile(dev), 'w')
- for port in ports['ports']:
- if port['network_id'] == nat_net_id:
+ for port in ports:
+ if port['network_id'] == net_id:
entry = "%s,%s\n" % (port['mac_address'], port['fixed_ips'][0]['ip_address'])
f.write(entry)
logger.log("%s: %s" % (plugin, entry))
# 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:
+# Set up iptables rules in the 'planetstack-net' chain based on
+# the nat:forward_ports field in the Port record.
+def set_up_port_forwarding(dev, ports):
+ for port in ports:
+ if port['network_id'] == nat_net_id and port['nat:forward_ports']:
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))
+ unfilter_ipaddr(dev, ipaddr)
# Shouldn't hardcode br-eth0 here. Maybe use '-d <node IP address>'?
add_iptables_rule('nat', plugin, ['-i', 'br-eth0',
'-p', protocol, '--dport', fwport,
'-j', 'DNAT', '--to-destination', ipaddr])
-
+
+def get_net_id_by_name(name):
+ quantum = client.Client(username=quantum_username,
+ password=quantum_password,
+ tenant_name=quantum_tenant_name,
+ auth_url=quantum_auth_url)
+
+ net = quantum.list_networks(name=name)
+ return net['networks'][0]['id']
+
def start():
global quantum_username
global quantum_password
global quantum_tenant_name
- global nat_net_id
+ global quantum_auth_url
logger.log("%s: plugin starting up..." % plugin)
quantum_username = parser.get("DEFAULT", "quantum_admin_username")
quantum_password = parser.get("DEFAULT", "quantum_admin_password")
quantum_tenant_name = parser.get("DEFAULT", "quantum_admin_tenant_name")
+ quantum_auth_url = parser.get("DEFAULT", "quantum_admin_auth_url")
- quantum = client.Client(username=quantum_username,
- password=quantum_password,
- tenant_name=quantum_tenant_name,
- auth_url=quantum_auth_url)
+def GetSlivers(data, config=None, plc=None):
+ global nat_net_id
+ global site_net_id
- net = quantum.list_networks(name=nat_net_name)
- nat_net_id = net['networks'][0]['id']
+ if not nat_net_id:
+ try:
+ nat_net_id = get_net_id_by_name(nat_net_name)
+ logger.log("%s: %s id is %s..." % (plugin, nat_net_name, nat_net_id))
+ except:
+ logger.log("%s: no network called %s..." % (plugin, nat_net_name))
- logger.log("%s: %s id is %s..." % (plugin, nat_net_name, nat_net_id))
-def GetSlivers(data, config=None, plc=None):
+ # Fix me
+ # site_net_name = "devel"
+ site_net_name = "sharednet1"
+ if not site_net_id:
+ try:
+ site_net_id = get_net_id_by_name(site_net_name)
+ logger.log("%s: %s id is %s..." % (plugin, site_net_name, site_net_id))
+ except:
+ logger.log("%s: no network called %s..." % (plugin, site_net_name))
+
reset_iptables_chain()
+ ports = get_local_quantum_ports()
+
for interface in data['interfaces']:
try:
settings = plc.GetInterfaceTags({'interface_tag_id': interface['interface_tag_ids']})
continue
logger.log('%s: Processing device %s' % (plugin, dev))
-
- if 'NAT' in tags:
+
+ # Process Private-Nat networks
+ if 'NAT' in tags and nat_net_id:
add_iptables_masq(dev, interface)
- process_quantum_ports(dev)
+ write_dnsmasq_hostsfile(dev, ports, nat_net_id)
+ set_up_port_forwarding(dev, ports)
start_dnsmasq(dev, interface)
+
+ # Process Public networks
+ if interface['is_primary'] and site_net_id:
+ if 'OVS_BRIDGE' in tags:
+ dev = tags['OVS_BRIDGE']
+ write_dnsmasq_hostsfile(dev, ports, site_net_id)
+ start_dnsmasq(dev, interface, forward_dns=False)
+