mirror of
https://git.freebsd.org/ports.git
synced 2025-05-29 01:16:28 -04:00
Please note that this is a development version of nova. Many features are not available. Currently nova works on FreeBSD 11 and supports QEMU and Xen. Common issues: - Security groups are not implemented - ARP spoofing, DHCP isolation protection are not implemented - Nova services work from the root user - No IPv6 support QEMU issues: - Need to enable serialconsole (TCP) - Need to disable online CPU tracking - Cannot mount cinder volumes Xen issues: - Live snapshots don't work - No support for cinder volume hot-plugging - XENBUS delay (5 min) when using qemu driver and COW images - Some Linux images cannot be booted For further FreeBSD specific notes please refer to port's pkg-message. PR: 215151 Submitted by: Alexander Nusov (alexander.nusov@nfvexpress.com)
1245 lines
47 KiB
Diff
1245 lines
47 KiB
Diff
From 2dd71331d4d204466e7b066f62952990e55c2e24 Mon Sep 17 00:00:00 2001
|
|
From: Alexander Nusov <alexander.nusov@nfvexpress.com>
|
|
Date: Tue, 29 Nov 2016 14:21:41 +0300
|
|
Subject: [PATCH] add freebsd_net driver
|
|
|
|
---
|
|
nova/network/freebsd_net.py | 1226 +++++++++++++++++++++++++++++++++++++++++++
|
|
1 file changed, 1226 insertions(+)
|
|
create mode 100644 nova/network/freebsd_net.py
|
|
|
|
diff --git a/nova/network/freebsd_net.py b/nova/network/freebsd_net.py
|
|
new file mode 100644
|
|
index 0000000..b71fcf6
|
|
--- /dev/null
|
|
+++ b/nova/network/freebsd_net.py
|
|
@@ -0,0 +1,1226 @@
|
|
+# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
|
|
+# Copyright 2010 United States Government as represented by the
|
|
+# Administrator of the National Aeronautics and Space Administration.
|
|
+# All Rights Reserved.
|
|
+#
|
|
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
+# not use this file except in compliance with the License. You may obtain
|
|
+# a copy of the License at
|
|
+#
|
|
+# http://www.apache.org/licenses/LICENSE-2.0
|
|
+#
|
|
+# Unless required by applicable law or agreed to in writing, software
|
|
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
+# License for the specific language governing permissions and limitations
|
|
+# under the License.
|
|
+
|
|
+"""Implements vlans, bridges, and iptables rules using linux utilities."""
|
|
+
|
|
+import calendar
|
|
+import inspect
|
|
+import os
|
|
+import re
|
|
+import time
|
|
+import json
|
|
+
|
|
+import netaddr
|
|
+import netifaces
|
|
+import socket
|
|
+import struct
|
|
+
|
|
+from oslo_concurrency import processutils
|
|
+from oslo_log import log as logging
|
|
+from oslo_serialization import jsonutils
|
|
+from oslo_utils import excutils
|
|
+from oslo_utils import fileutils
|
|
+from oslo_utils import importutils
|
|
+from oslo_utils import timeutils
|
|
+import six
|
|
+
|
|
+import nova.conf
|
|
+from nova import exception
|
|
+from nova.i18n import _, _LE, _LW
|
|
+from nova.network import model as network_model
|
|
+from nova import objects
|
|
+from nova.pci import utils as pci_utils
|
|
+from nova import utils
|
|
+
|
|
+LOG = logging.getLogger(__name__)
|
|
+
|
|
+
|
|
+CONF = nova.conf.CONF
|
|
+
|
|
+
|
|
+# NOTE(vish): Iptables supports chain names of up to 28 characters, and we
|
|
+# add up to 12 characters to binary_name which is used as a prefix,
|
|
+# so we limit it to 16 characters.
|
|
+# (max_chain_name_length - len('-POSTROUTING') == 16)
|
|
+def get_binary_name():
|
|
+ """Grab the name of the binary we're running in."""
|
|
+ return os.path.basename(inspect.stack()[-1][1])[:16]
|
|
+
|
|
+binary_name = get_binary_name()
|
|
+
|
|
+
|
|
+# NOTE(jkoelker) This is just a nice little stub point since mocking
|
|
+# builtins with mox is a nightmare
|
|
+def write_to_file(file, data, mode='w'):
|
|
+ with open(file, mode) as f:
|
|
+ f.write(data)
|
|
+
|
|
+
|
|
+def is_pid_cmdline_correct(pid, match):
|
|
+ """Ensure that the cmdline for a pid seems sane
|
|
+
|
|
+ Because pids are recycled, blindly killing by pid is something to
|
|
+ avoid. This provides the ability to include a substring that is
|
|
+ expected in the cmdline as a safety check.
|
|
+ """
|
|
+ try:
|
|
+ with open('/proc/%d/cmdline' % pid) as f:
|
|
+ cmdline = f.read()
|
|
+ return match in cmdline
|
|
+ except EnvironmentError:
|
|
+ return False
|
|
+
|
|
+
|
|
+def metadata_forward():
|
|
+ """Create forwarding rule for metadata."""
|
|
+ firewall_manager.add_rule("rdr proto tcp from any to 169.254.169.254 "
|
|
+ "port 80 -> %s port %s" %
|
|
+ (CONF.metadata_host, CONF.metadata_port))
|
|
+ firewall_manager.add_rule("pass out route-to (lo0 127.0.0.1) proto tcp "
|
|
+ "from any to 169.254.169.254 port 80")
|
|
+ firewall_manager.apply()
|
|
+
|
|
+
|
|
+def metadata_accept():
|
|
+ """Create the filter accept rule for metadata."""
|
|
+ firewall_manager.add_rule("pass in inet proto tcp from any to "
|
|
+ "169.254.169.254 port = http "
|
|
+ "flags S/SA keep state")
|
|
+ firewall_manager.apply()
|
|
+
|
|
+
|
|
+def init_host(ip_range, is_external=False):
|
|
+ """Basic networking setup goes here."""
|
|
+ # NOTE(devcamcar): Cloud public SNAT entries and the default
|
|
+ # SNAT rule for outbound traffic.
|
|
+
|
|
+ firewall_manager.add_snat_rule(ip_range, is_external)
|
|
+ if is_external:
|
|
+ for snat_range in CONF.force_snat_range:
|
|
+ firewall_manager.add_rule("pass quick inet from %s to %s" %
|
|
+ (ip_range, snat_range))
|
|
+ firewall_manager.add_rule("pass quick inet from %s to %s/32" %
|
|
+ (ip_range, CONF.metadata_host))
|
|
+ for dmz in CONF.dmz_cidr:
|
|
+ firewall_manager.add_rule("pass quick inet from %s to %s" %
|
|
+ (ip_range, dmz))
|
|
+
|
|
+ """
|
|
+ iptables_manager.ipv4['nat'].add_rule('POSTROUTING',
|
|
+ '-s %(range)s -d %(range)s '
|
|
+ '-m conntrack ! --ctstate DNAT '
|
|
+ '-j ACCEPT' %
|
|
+ {'range': ip_range})
|
|
+ """
|
|
+ firewall_manager.apply()
|
|
+
|
|
+
|
|
+def send_arp_for_ip(ip, device, count):
|
|
+ out, err = _execute('arping', '-U', '-i', device, '-c', str(count), ip,
|
|
+ run_as_root=True, check_exit_code=False)
|
|
+
|
|
+ if err:
|
|
+ LOG.debug('arping error for IP %s', ip)
|
|
+
|
|
+
|
|
+def bind_floating_ip(floating_ip, device):
|
|
+ """Bind IP to public interface."""
|
|
+ _execute('ifconfig', device, str(floating_ip) + '/32', 'add',
|
|
+ run_as_root=True, check_exit_code=0)
|
|
+
|
|
+ if CONF.send_arp_for_ha and CONF.send_arp_for_ha_count > 0:
|
|
+ send_arp_for_ip(floating_ip, device, CONF.send_arp_for_ha_count)
|
|
+
|
|
+
|
|
+def unbind_floating_ip(floating_ip, device):
|
|
+ """Unbind a public IP from public interface."""
|
|
+ _execute('ifconfig', device, str(floating_ip) + '/32', 'delete',
|
|
+ run_as_root=True, check_exit_code=0)
|
|
+
|
|
+
|
|
+def ensure_metadata_ip():
|
|
+ """Sets up local metadata IP."""
|
|
+ _execute('ifconfig', 'lo0', 'alias', '169.254.169.254/32',
|
|
+ run_as_root=True, check_exit_code=0)
|
|
+
|
|
+
|
|
+def ensure_vpn_forward(public_ip, port, private_ip):
|
|
+ """Sets up forwarding rules for vlan."""
|
|
+ firewall_manager.add_rule("pass in proto udp "
|
|
+ "from any to %s port 1194 " %
|
|
+ (private_ip))
|
|
+ firewall_manager.add_rule("rdr proto udp from any to %s port %s -> "
|
|
+ "%s port 1194" %
|
|
+ (public_ip, port, private_ip))
|
|
+ firewall_manager.apply()
|
|
+
|
|
+
|
|
+def ensure_floating_forward(floating_ip, fixed_ip, device, network):
|
|
+ """Ensure floating IP forwarding rule."""
|
|
+ firewall_manager.ensure_floating_rules(floating_ip, fixed_ip, device)
|
|
+ if device != network['bridge']:
|
|
+ firewall_manager.ensure_in_network_traffic_rules(fixed_ip, network)
|
|
+ firewall_manager.apply()
|
|
+
|
|
+
|
|
+def remove_floating_forward(floating_ip, fixed_ip, device, network):
|
|
+ """Remove forwarding for floating IP."""
|
|
+ firewall_manager.remove_floating_rules(floating_ip, fixed_ip, device)
|
|
+ if device != network['bridge']:
|
|
+ firewall_manager.remove_in_network_traffic_rules(fixed_ip, network)
|
|
+ firewall_manager.apply()
|
|
+
|
|
+
|
|
+def clean_conntrack(fixed_ip):
|
|
+ pass
|
|
+
|
|
+
|
|
+def _enable_ipv4_forwarding():
|
|
+ sysctl_key = 'net.inet.ip.forwarding'
|
|
+ stdout, stderr = _execute('sysctl', '-n', sysctl_key)
|
|
+ if stdout.strip() is not '1':
|
|
+ _execute('sysctl', '%s=1' % sysctl_key, run_as_root=True)
|
|
+
|
|
+
|
|
+@utils.synchronized('lock_gateway', external=True)
|
|
+def initialize_gateway_device(dev, network_ref):
|
|
+ if not network_ref:
|
|
+ return
|
|
+
|
|
+ _enable_ipv4_forwarding()
|
|
+
|
|
+ # NOTE(vish): The ip for dnsmasq has to be the first address on the
|
|
+ # bridge for it to respond to requests properly
|
|
+ try:
|
|
+ prefix = network_ref.cidr.prefixlen
|
|
+ except AttributeError:
|
|
+ prefix = network_ref['cidr'].rpartition('/')[2]
|
|
+
|
|
+ full_ip = '%s/%s' % (network_ref['dhcp_server'], prefix)
|
|
+ new_ip_params = [['inet', full_ip, 'broadcast', network_ref['broadcast']]]
|
|
+ old_ip_params = []
|
|
+ out, err = _execute('ifconfig', dev)
|
|
+ for line in out.split('\n'):
|
|
+ fields = line.split()
|
|
+ if fields and fields[0] == 'inet':
|
|
+ old_ip_params.append(fields)
|
|
+ if _address_to_cidr(fields[1], fields[3]) != full_ip:
|
|
+ new_ip_params.append(fields)
|
|
+ if not old_ip_params or _address_to_cidr(old_ip_params[0][1], old_ip_params[0][3]) != full_ip:
|
|
+ old_routes = []
|
|
+ result = _execute('netstat', '-nrW', '-f', 'inet')
|
|
+ if result:
|
|
+ out, err = result
|
|
+ for line in out.split('\n'):
|
|
+ fields = line.split()
|
|
+ if len(fields) > 6 and (fields[6] == dev) and ('G' in fields[2]):
|
|
+ old_routes.append(fields)
|
|
+ _execute('route', '-q', 'delete', fields[0], fields[1],
|
|
+ run_as_root=True)
|
|
+ for ip_params in old_ip_params:
|
|
+ _execute(*_ifconfig_tail_cmd(dev, ip_params, 'delete'),
|
|
+ run_as_root=True)
|
|
+ for ip_params in new_ip_params:
|
|
+ _execute(*_ifconfig_tail_cmd(dev, ip_params, 'add'),
|
|
+ run_as_root=True)
|
|
+
|
|
+ for fields in old_routes:
|
|
+ _execute('route', '-q', 'add', fields[0], fields[1],
|
|
+ run_as_root=True)
|
|
+ if CONF.send_arp_for_ha and CONF.send_arp_for_ha_count > 0:
|
|
+ send_arp_for_ip(network_ref['dhcp_server'], dev,
|
|
+ CONF.send_arp_for_ha_count)
|
|
+ if CONF.use_ipv6:
|
|
+ _execute('ifconfig', dev, 'inet6', network_ref['cidr_v6'],
|
|
+ run_as_root=True)
|
|
+
|
|
+
|
|
+def get_dhcp_leases(context, network_ref):
|
|
+ """Return a network's hosts config in dnsmasq leasefile format."""
|
|
+ hosts = []
|
|
+ host = None
|
|
+ if network_ref['multi_host']:
|
|
+ host = CONF.host
|
|
+ for fixedip in objects.FixedIPList.get_by_network(context,
|
|
+ network_ref,
|
|
+ host=host):
|
|
+ # NOTE(cfb): Don't return a lease entry if the IP isn't
|
|
+ # already leased
|
|
+ if fixedip.leased:
|
|
+ hosts.append(_host_lease(fixedip))
|
|
+
|
|
+ return '\n'.join(hosts)
|
|
+
|
|
+
|
|
+def get_dhcp_hosts(context, network_ref, fixedips):
|
|
+ """Get network's hosts config in dhcp-host format."""
|
|
+ hosts = []
|
|
+ macs = set()
|
|
+ for fixedip in fixedips:
|
|
+ if fixedip.allocated:
|
|
+ if fixedip.virtual_interface.address not in macs:
|
|
+ hosts.append(_host_dhcp(fixedip))
|
|
+ macs.add(fixedip.virtual_interface.address)
|
|
+ return '\n'.join(hosts)
|
|
+
|
|
+
|
|
+def get_dns_hosts(context, network_ref):
|
|
+ """Get network's DNS hosts in hosts format."""
|
|
+ hosts = []
|
|
+ for fixedip in objects.FixedIPList.get_by_network(context, network_ref):
|
|
+ if fixedip.allocated:
|
|
+ hosts.append(_host_dns(fixedip))
|
|
+ return '\n'.join(hosts)
|
|
+
|
|
+
|
|
+def _add_dnsmasq_accept_rules(dev):
|
|
+ """Allow DHCP and DNS traffic through to dnsmasq."""
|
|
+ for port in [67, 53]:
|
|
+ for proto in ['udp', 'tcp']:
|
|
+ firewall_manager.add_rule("pass in on %s inet proto %s "
|
|
+ "from any to any port %s" %
|
|
+ (dev, proto, port))
|
|
+ firewall_manager.apply()
|
|
+
|
|
+
|
|
+def _remove_dnsmasq_accept_rules(dev):
|
|
+ """Remove DHCP and DNS traffic allowed through to dnsmasq."""
|
|
+ for port in [67, 53]:
|
|
+ for proto in ['udp', 'tcp']:
|
|
+ firewall_manager.remove_rule("pass in on %s inet proto %s "
|
|
+ "from any to any port %s" %
|
|
+ (dev, proto, port))
|
|
+ firewall_manager.apply()
|
|
+
|
|
+
|
|
+def get_dhcp_opts(context, network_ref, fixedips):
|
|
+ """Get network's hosts config in dhcp-opts format."""
|
|
+ gateway = network_ref['gateway']
|
|
+ # NOTE(vish): if we are in multi-host mode and we are not sharing
|
|
+ # addresses, then we actually need to hand out the
|
|
+ # dhcp server address as the gateway.
|
|
+ if network_ref['multi_host'] and not (network_ref['share_address'] or
|
|
+ CONF.share_dhcp_address):
|
|
+ gateway = network_ref['dhcp_server']
|
|
+ hosts = []
|
|
+ if CONF.use_single_default_gateway:
|
|
+ for fixedip in fixedips:
|
|
+ if fixedip.allocated:
|
|
+ vif_id = fixedip.virtual_interface_id
|
|
+ if fixedip.default_route:
|
|
+ hosts.append(_host_dhcp_opts(vif_id, gateway))
|
|
+ else:
|
|
+ hosts.append(_host_dhcp_opts(vif_id))
|
|
+ else:
|
|
+ hosts.append(_host_dhcp_opts(None, gateway))
|
|
+ return '\n'.join(hosts)
|
|
+
|
|
+
|
|
+def release_dhcp(dev, address, mac_address):
|
|
+ if device_exists(dev):
|
|
+ try:
|
|
+ utils.execute('dhcp_release', dev, address, mac_address,
|
|
+ run_as_root=True)
|
|
+ except processutils.ProcessExecutionError:
|
|
+ raise exception.NetworkDhcpReleaseFailed(address=address,
|
|
+ mac_address=mac_address)
|
|
+
|
|
+
|
|
+def update_dhcp(context, dev, network_ref):
|
|
+ conffile = _dhcp_file(dev, 'conf')
|
|
+ host = None
|
|
+ if network_ref['multi_host']:
|
|
+ host = CONF.host
|
|
+ fixedips = objects.FixedIPList.get_by_network(context,
|
|
+ network_ref,
|
|
+ host=host)
|
|
+ write_to_file(conffile, get_dhcp_hosts(context, network_ref, fixedips))
|
|
+ restart_dhcp(context, dev, network_ref, fixedips)
|
|
+
|
|
+
|
|
+def update_dns(context, dev, network_ref):
|
|
+ hostsfile = _dhcp_file(dev, 'hosts')
|
|
+ host = None
|
|
+ if network_ref['multi_host']:
|
|
+ host = CONF.host
|
|
+ fixedips = objects.FixedIPList.get_by_network(context,
|
|
+ network_ref,
|
|
+ host=host)
|
|
+ write_to_file(hostsfile, get_dns_hosts(context, network_ref))
|
|
+ restart_dhcp(context, dev, network_ref, fixedips)
|
|
+
|
|
+
|
|
+def kill_dhcp(dev):
|
|
+ pid = _dnsmasq_pid_for(dev)
|
|
+ if pid:
|
|
+ # Check that the process exists and looks like a dnsmasq process
|
|
+ conffile = _dhcp_file(dev, 'conf')
|
|
+ if is_pid_cmdline_correct(pid, conffile.split('/')[-1]):
|
|
+ _execute('kill', '-9', pid, run_as_root=True)
|
|
+ else:
|
|
+ LOG.debug('Pid %d is stale, skip killing dnsmasq', pid)
|
|
+ _remove_dnsmasq_accept_rules(dev)
|
|
+
|
|
+
|
|
+# NOTE(ja): Sending a HUP only reloads the hostfile, so any
|
|
+# configuration options (like dchp-range, vlan, ...)
|
|
+# aren't reloaded.
|
|
+@utils.synchronized('dnsmasq_start')
|
|
+def restart_dhcp(context, dev, network_ref, fixedips):
|
|
+ """(Re)starts a dnsmasq server for a given network.
|
|
+
|
|
+ If a dnsmasq instance is already running then send a HUP
|
|
+ signal causing it to reload, otherwise spawn a new instance.
|
|
+
|
|
+ """
|
|
+ conffile = _dhcp_file(dev, 'conf')
|
|
+
|
|
+ optsfile = _dhcp_file(dev, 'opts')
|
|
+ write_to_file(optsfile, get_dhcp_opts(context, network_ref, fixedips))
|
|
+ os.chmod(optsfile, 0o644)
|
|
+
|
|
+ # Make sure dnsmasq can actually read it (it setuid()s to "nobody")
|
|
+ os.chmod(conffile, 0o644)
|
|
+
|
|
+ pid = _dnsmasq_pid_for(dev)
|
|
+
|
|
+ # if dnsmasq is already running, then tell it to reload
|
|
+ if pid:
|
|
+ if is_pid_cmdline_correct(pid, conffile.split('/')[-1]):
|
|
+ try:
|
|
+ _execute('kill', '-HUP', pid, run_as_root=True)
|
|
+ _add_dnsmasq_accept_rules(dev)
|
|
+ return
|
|
+ except Exception as exc:
|
|
+ LOG.error(_LE('kill -HUP dnsmasq threw %s'), exc)
|
|
+ else:
|
|
+ LOG.debug('Pid %d is stale, relaunching dnsmasq', pid)
|
|
+
|
|
+ cmd = ['env',
|
|
+ 'CONFIG_FILE=%s' % jsonutils.dumps(CONF.dhcpbridge_flagfile),
|
|
+ 'NETWORK_ID=%s' % str(network_ref['id']),
|
|
+ 'dnsmasq',
|
|
+ '--strict-order',
|
|
+ '--bind-interfaces',
|
|
+ '--conf-file=%s' % CONF.dnsmasq_config_file,
|
|
+ '--pid-file=%s' % _dhcp_file(dev, 'pid'),
|
|
+ '--dhcp-optsfile=%s' % _dhcp_file(dev, 'opts'),
|
|
+ '--listen-address=%s' % network_ref['dhcp_server'],
|
|
+ '--except-interface=lo',
|
|
+ '--dhcp-range=set:%s,%s,static,%s,%ss' %
|
|
+ (network_ref['label'],
|
|
+ network_ref['dhcp_start'],
|
|
+ network_ref['netmask'],
|
|
+ CONF.dhcp_lease_time),
|
|
+ '--dhcp-lease-max=%s' % len(netaddr.IPNetwork(network_ref['cidr'])),
|
|
+ '--dhcp-hostsfile=%s' % _dhcp_file(dev, 'conf'),
|
|
+ '--dhcp-script=%s' % CONF.dhcpbridge,
|
|
+ '--no-hosts',
|
|
+ '--leasefile-ro']
|
|
+
|
|
+ # dnsmasq currently gives an error for an empty domain,
|
|
+ # rather than ignoring. So only specify it if defined.
|
|
+ if CONF.dhcp_domain:
|
|
+ cmd.append('--domain=%s' % CONF.dhcp_domain)
|
|
+
|
|
+ dns_servers = CONF.dns_server
|
|
+ if CONF.use_network_dns_servers:
|
|
+ if network_ref.get('dns1'):
|
|
+ dns_servers.append(network_ref.get('dns1'))
|
|
+ if network_ref.get('dns2'):
|
|
+ dns_servers.append(network_ref.get('dns2'))
|
|
+ if network_ref['multi_host']:
|
|
+ cmd.append('--addn-hosts=%s' % _dhcp_file(dev, 'hosts'))
|
|
+ if dns_servers:
|
|
+ cmd.append('--no-resolv')
|
|
+ for dns_server in dns_servers:
|
|
+ cmd.append('--server=%s' % dns_server)
|
|
+
|
|
+ _execute(*cmd, run_as_root=True)
|
|
+
|
|
+ _add_dnsmasq_accept_rules(dev)
|
|
+
|
|
+
|
|
+@utils.synchronized('radvd_start')
|
|
+def update_ra(context, dev, network_ref):
|
|
+ conffile = _ra_file(dev, 'conf')
|
|
+ conf_str = """
|
|
+interface %s
|
|
+{
|
|
+ AdvSendAdvert on;
|
|
+ MinRtrAdvInterval 3;
|
|
+ MaxRtrAdvInterval 10;
|
|
+ prefix %s
|
|
+ {
|
|
+ AdvOnLink on;
|
|
+ AdvAutonomous on;
|
|
+ };
|
|
+};
|
|
+""" % (dev, network_ref['cidr_v6'])
|
|
+ write_to_file(conffile, conf_str)
|
|
+
|
|
+ # Make sure radvd can actually read it (it setuid()s to "nobody")
|
|
+ os.chmod(conffile, 0o644)
|
|
+
|
|
+ pid = _ra_pid_for(dev)
|
|
+
|
|
+ # if radvd is already running, then tell it to reload
|
|
+ if pid:
|
|
+ if is_pid_cmdline_correct(pid, conffile):
|
|
+ try:
|
|
+ _execute('kill', pid, run_as_root=True)
|
|
+ except Exception as exc:
|
|
+ LOG.error(_LE('killing radvd threw %s'), exc)
|
|
+ else:
|
|
+ LOG.debug('Pid %d is stale, relaunching radvd', pid)
|
|
+
|
|
+ cmd = ['radvd',
|
|
+ '-C', '%s' % _ra_file(dev, 'conf'),
|
|
+ '-p', '%s' % _ra_file(dev, 'pid')]
|
|
+
|
|
+ _execute(*cmd, run_as_root=True)
|
|
+
|
|
+
|
|
+def _host_lease(fixedip):
|
|
+ """Return a host string for an address in leasefile format."""
|
|
+ timestamp = timeutils.utcnow()
|
|
+ seconds_since_epoch = calendar.timegm(timestamp.utctimetuple())
|
|
+ return '%d %s %s %s *' % (seconds_since_epoch + CONF.dhcp_lease_time,
|
|
+ fixedip.virtual_interface.address,
|
|
+ fixedip.address,
|
|
+ fixedip.instance.hostname or '*')
|
|
+
|
|
+
|
|
+def _host_dhcp_network(vif_id):
|
|
+ return 'NW-%s' % vif_id
|
|
+
|
|
+
|
|
+def _host_dhcp(fixedip):
|
|
+ """Return a host string for an address in dhcp-host format."""
|
|
+ # NOTE(cfb): dnsmasq on linux only supports 64 characters in the hostname
|
|
+ # field (LP #1238910). Since the . counts as a character we need
|
|
+ # to truncate the hostname to only 63 characters.
|
|
+ hostname = fixedip.instance.hostname
|
|
+ if len(hostname) > 63:
|
|
+ LOG.warning(_LW('hostname %s too long, truncating.'), hostname)
|
|
+ hostname = fixedip.instance.hostname[:2] + '-' +\
|
|
+ fixedip.instance.hostname[-60:]
|
|
+ if CONF.use_single_default_gateway:
|
|
+ net = _host_dhcp_network(fixedip.virtual_interface_id)
|
|
+ return '%s,%s.%s,%s,net:%s' % (fixedip.virtual_interface.address,
|
|
+ hostname,
|
|
+ CONF.dhcp_domain,
|
|
+ fixedip.address,
|
|
+ net)
|
|
+ else:
|
|
+ return '%s,%s.%s,%s' % (fixedip.virtual_interface.address,
|
|
+ hostname,
|
|
+ CONF.dhcp_domain,
|
|
+ fixedip.address)
|
|
+
|
|
+
|
|
+def _host_dns(fixedip):
|
|
+ return '%s\t%s.%s' % (fixedip.address,
|
|
+ fixedip.instance.hostname,
|
|
+ CONF.dhcp_domain)
|
|
+
|
|
+
|
|
+def _host_dhcp_opts(vif_id=None, gateway=None):
|
|
+ """Return an empty gateway option."""
|
|
+ values = []
|
|
+ if vif_id is not None:
|
|
+ values.append(_host_dhcp_network(vif_id))
|
|
+ # NOTE(vish): 3 is the dhcp option for gateway.
|
|
+ values.append('3')
|
|
+ if gateway:
|
|
+ values.append('%s' % gateway)
|
|
+ return ','.join(values)
|
|
+
|
|
+
|
|
+def _execute(*cmd, **kwargs):
|
|
+ """Wrapper around utils._execute for fake_network."""
|
|
+ if CONF.fake_network:
|
|
+ LOG.debug('FAKE NET: %s', ' '.join(map(str, cmd)))
|
|
+ return 'fake', 0
|
|
+ else:
|
|
+ return utils.execute(*cmd, **kwargs)
|
|
+
|
|
+
|
|
+def device_exists(device):
|
|
+ """Check if ethernet device exists."""
|
|
+ try:
|
|
+ _execute('ifconfig', device, run_as_root=True, check_exit_code=[0])
|
|
+ except processutils.ProcessExecutionError:
|
|
+ return False
|
|
+ else:
|
|
+ return True
|
|
+
|
|
+
|
|
+def _dhcp_file(dev, kind):
|
|
+ """Return path to a pid, leases, hosts or conf file for a bridge/device."""
|
|
+ fileutils.ensure_tree(CONF.networks_path)
|
|
+ return os.path.abspath('%s/nova-%s.%s' % (CONF.networks_path,
|
|
+ dev,
|
|
+ kind))
|
|
+
|
|
+
|
|
+def _ra_file(dev, kind):
|
|
+ """Return path to a pid or conf file for a bridge/device."""
|
|
+ fileutils.ensure_tree(CONF.networks_path)
|
|
+ return os.path.abspath('%s/nova-ra-%s.%s' % (CONF.networks_path,
|
|
+ dev,
|
|
+ kind))
|
|
+
|
|
+
|
|
+def _dnsmasq_pid_for(dev):
|
|
+ """Returns the pid for prior dnsmasq instance for a bridge/device.
|
|
+
|
|
+ Returns None if no pid file exists.
|
|
+
|
|
+ If machine has rebooted pid might be incorrect (caller should check).
|
|
+
|
|
+ """
|
|
+ pid_file = _dhcp_file(dev, 'pid')
|
|
+
|
|
+ if os.path.exists(pid_file):
|
|
+ try:
|
|
+ with open(pid_file, 'r') as f:
|
|
+ return int(f.read())
|
|
+ except (ValueError, IOError):
|
|
+ return None
|
|
+
|
|
+
|
|
+def _ra_pid_for(dev):
|
|
+ """Returns the pid for prior radvd instance for a bridge/device.
|
|
+
|
|
+ Returns None if no pid file exists.
|
|
+
|
|
+ If machine has rebooted pid might be incorrect (caller should check).
|
|
+
|
|
+ """
|
|
+ pid_file = _ra_file(dev, 'pid')
|
|
+
|
|
+ if os.path.exists(pid_file):
|
|
+ with open(pid_file, 'r') as f:
|
|
+ return int(f.read())
|
|
+
|
|
+
|
|
+def _address_to_cidr(address, hexmask):
|
|
+ """Produce a CIDR format address/netmask."""
|
|
+ netmask = socket.inet_ntoa(struct.pack(">I", int(hexmask, 16)))
|
|
+ ip_cidr = netaddr.IPNetwork("%s/%s" % (address, netmask))
|
|
+ return str(ip_cidr)
|
|
+
|
|
+
|
|
+def _ifconfig_tail_cmd(netif, params, action):
|
|
+ """Construct ifconfig command"""
|
|
+ cmd = ['ifconfig', netif]
|
|
+ cmd.extend(params)
|
|
+ cmd.extend([action])
|
|
+ return cmd
|
|
+
|
|
+
|
|
+def _set_device_mtu(dev, mtu=None):
|
|
+ """Set the device MTU."""
|
|
+ if mtu:
|
|
+ utils.execute('ifconfig', dev, 'mtu', mtu,
|
|
+ run_as_root=True, check_exit_code=0)
|
|
+
|
|
+
|
|
+def _ovs_vsctl(args):
|
|
+ full_args = ['ovs-vsctl', '--timeout=%s' % CONF.ovs_vsctl_timeout] + args
|
|
+ try:
|
|
+ return utils.execute(*full_args, run_as_root=True)
|
|
+ except Exception as e:
|
|
+ LOG.error(_LE("Unable to execute %(cmd)s. Exception: %(exception)s"),
|
|
+ {'cmd': full_args, 'exception': e})
|
|
+ raise exception.OvsConfigurationFailure(inner_exception=e)
|
|
+
|
|
+
|
|
+def _create_ovs_vif_cmd(bridge, dev, iface_id, mac,
|
|
+ instance_id, interface_type=None):
|
|
+ cmd = ['--', '--if-exists', 'del-port', dev, '--',
|
|
+ 'add-port', bridge, dev,
|
|
+ '--', 'set', 'Interface', dev,
|
|
+ 'external-ids:iface-id=%s' % iface_id,
|
|
+ 'external-ids:iface-status=active',
|
|
+ 'external-ids:attached-mac=%s' % mac,
|
|
+ 'external-ids:vm-uuid=%s' % instance_id]
|
|
+ if interface_type:
|
|
+ cmd += ['type=%s' % interface_type]
|
|
+ return cmd
|
|
+
|
|
+
|
|
+def create_ovs_vif_port(bridge, dev, iface_id, mac, instance_id,
|
|
+ mtu=None, interface_type=None):
|
|
+ _ovs_vsctl(_create_ovs_vif_cmd(bridge, dev, iface_id,
|
|
+ mac, instance_id,
|
|
+ interface_type))
|
|
+ # Note at present there is no support for setting the
|
|
+ # mtu for vhost-user type ports.
|
|
+ if interface_type != network_model.OVS_VHOSTUSER_INTERFACE_TYPE:
|
|
+ _set_device_mtu(dev, mtu)
|
|
+ else:
|
|
+ LOG.debug("MTU not set on %(interface_name)s interface "
|
|
+ "of type %(interface_type)s.",
|
|
+ {'interface_name': dev,
|
|
+ 'interface_type': interface_type})
|
|
+
|
|
+
|
|
+def delete_ovs_vif_port(bridge, dev, delete_dev=True):
|
|
+ _ovs_vsctl(['--', '--if-exists', 'del-port', bridge, dev])
|
|
+ if delete_dev:
|
|
+ delete_net_dev(dev)
|
|
+
|
|
+
|
|
+def create_tap_dev(dev, mac_address=None):
|
|
+ if not device_exists(dev):
|
|
+ utils.execute('ifconfig', 'tap', 'create', 'name', dev,
|
|
+ run_as_root=True, check_exit_code=[0])
|
|
+ if mac_address:
|
|
+ utils.execute('ifconfig', dev, 'ether', mac_address,
|
|
+ run_as_root=True, check_exit_code=[0])
|
|
+ utils.execute('ifconfig', dev, 'up',
|
|
+ run_as_root=True, check_exit_code=[0])
|
|
+
|
|
+
|
|
+def delete_net_dev(dev):
|
|
+ """Delete a network device only if it exists."""
|
|
+ if device_exists(dev):
|
|
+ try:
|
|
+ utils.execute('ifconfig', dev, 'destroy',
|
|
+ run_as_root=True, check_exit_code=0)
|
|
+ LOG.debug("Net device removed: '%s'", dev)
|
|
+ except processutils.ProcessExecutionError:
|
|
+ with excutils.save_and_reraise_exception():
|
|
+ LOG.error(_LE("Failed removing net device: '%s'"), dev)
|
|
+
|
|
+
|
|
+def delete_bridge_dev(dev):
|
|
+ """Delete a network bridge."""
|
|
+ if device_exists(dev):
|
|
+ try:
|
|
+ utils.execute('ifconfig', dev, 'down', run_as_root=True)
|
|
+ utils.execute('ifconfig', dev, 'destroy', run_as_root=True)
|
|
+ except processutils.ProcessExecutionError:
|
|
+ with excutils.save_and_reraise_exception():
|
|
+ LOG.error(_LE("Failed removing bridge device: '%s'"), dev)
|
|
+
|
|
+
|
|
+# Similar to compute virt layers, the FreeBSD network node
|
|
+# code uses a flexible driver model to support different ways
|
|
+# of creating ethernet interfaces and attaching them to the network.
|
|
+# In the case of a network host, these interfaces
|
|
+# act as gateway/dhcp/vpn/etc. endpoints not VM interfaces.
|
|
+interface_driver = None
|
|
+
|
|
+
|
|
+def _get_interface_driver():
|
|
+ global interface_driver
|
|
+ if not interface_driver:
|
|
+ interface_driver = importutils.import_object(
|
|
+ CONF.freebsdnet_interface_driver)
|
|
+ return interface_driver
|
|
+
|
|
+
|
|
+def plug(network, mac_address, gateway=True):
|
|
+ return _get_interface_driver().plug(network, mac_address, gateway)
|
|
+
|
|
+
|
|
+def unplug(network):
|
|
+ return _get_interface_driver().unplug(network)
|
|
+
|
|
+
|
|
+def get_dev(network):
|
|
+ return _get_interface_driver().get_dev(network)
|
|
+
|
|
+
|
|
+class FreeBSDNetInterfaceDriver(object):
|
|
+ """Abstract class that defines generic network host API
|
|
+ for all FreeBSD interface drivers.
|
|
+ """
|
|
+
|
|
+ def plug(self, network, mac_address):
|
|
+ """Create FreeBSD device, return device name."""
|
|
+ raise NotImplementedError()
|
|
+
|
|
+ def unplug(self, network):
|
|
+ """Destroy FreeBSD device, return device name."""
|
|
+ raise NotImplementedError()
|
|
+
|
|
+ def get_dev(self, network):
|
|
+ """Get device name."""
|
|
+ raise NotImplementedError()
|
|
+
|
|
+
|
|
+# plugs interfaces using FreeBSD Bridge
|
|
+class FreeBSDBridgeInterfaceDriver(FreeBSDNetInterfaceDriver):
|
|
+
|
|
+ def plug(self, network, mac_address, gateway=True):
|
|
+ vlan = network.get('vlan')
|
|
+ if vlan is not None:
|
|
+ iface = CONF.vlan_interface or network['bridge_interface']
|
|
+ FreeBSDBridgeInterfaceDriver.ensure_vlan_bridge(
|
|
+ vlan,
|
|
+ network['bridge'],
|
|
+ iface,
|
|
+ network,
|
|
+ mac_address,
|
|
+ network.get('mtu'))
|
|
+ iface = 'vlan%s' % vlan
|
|
+ else:
|
|
+ iface = CONF.flat_interface or network['bridge_interface']
|
|
+ FreeBSDBridgeInterfaceDriver.ensure_bridge(
|
|
+ network['bridge'],
|
|
+ iface,
|
|
+ network, gateway)
|
|
+
|
|
+ if network['share_address'] or CONF.share_dhcp_address:
|
|
+ isolate_dhcp_address(iface, network['dhcp_server'])
|
|
+ # NOTE(vish): applying here so we don't get a lock conflict
|
|
+ firewall_manager.apply()
|
|
+ return network['bridge']
|
|
+
|
|
+ def unplug(self, network, gateway=True):
|
|
+ vlan = network.get('vlan')
|
|
+ if vlan is not None:
|
|
+ iface = 'vlan%s' % vlan
|
|
+ FreeBSDBridgeInterfaceDriver.remove_vlan_bridge(vlan,
|
|
+ network['bridge'])
|
|
+ else:
|
|
+ iface = CONF.flat_interface or network['bridge_interface']
|
|
+ FreeBSDBridgeInterfaceDriver.remove_bridge(network['bridge'],
|
|
+ gateway)
|
|
+
|
|
+ if network['share_address'] or CONF.share_dhcp_address:
|
|
+ remove_isolate_dhcp_address(iface, network['dhcp_server'])
|
|
+
|
|
+ firewall_manager.apply()
|
|
+ return self.get_dev(network)
|
|
+
|
|
+ def get_dev(self, network):
|
|
+ return network['bridge']
|
|
+
|
|
+ @staticmethod
|
|
+ def ensure_vlan_bridge(vlan_num, bridge, bridge_interface,
|
|
+ net_attrs=None, mac_address=None,
|
|
+ mtu=None):
|
|
+ """Create a vlan and bridge unless they already exist."""
|
|
+ interface = FreeBSDBridgeInterfaceDriver.ensure_vlan(vlan_num,
|
|
+ bridge_interface, mac_address,
|
|
+ mtu)
|
|
+ FreeBSDBridgeInterfaceDriver.ensure_bridge(bridge, interface, net_attrs)
|
|
+ return interface
|
|
+
|
|
+ @staticmethod
|
|
+ def remove_vlan_bridge(vlan_num, bridge):
|
|
+ """Delete a bridge and vlan."""
|
|
+ FreeBSDBridgeInterfaceDriver.remove_bridge(bridge)
|
|
+ FreeBSDBridgeInterfaceDriver.remove_vlan(vlan_num)
|
|
+
|
|
+ @staticmethod
|
|
+ @utils.synchronized('lock_vlan', external=True)
|
|
+ def ensure_vlan(vlan_num, bridge_interface, mac_address=None, mtu=None,
|
|
+ interface=None):
|
|
+ """Create a vlan unless it already exists."""
|
|
+ if interface is None:
|
|
+ interface = 'vlan%s' % vlan_num
|
|
+ if not device_exists(interface):
|
|
+ LOG.debug('Starting VLAN interface %s', interface)
|
|
+ out, err = _execute('ifconfig', 'vlan', 'create',
|
|
+ 'vlan', vlan_num,
|
|
+ 'vlandev', bridge_interface,
|
|
+ 'name', interface,
|
|
+ run_as_root=True)
|
|
+ if err and 'File exists' not in err:
|
|
+ msg = _('Failed to add vlan: %s') % err
|
|
+ raise exception.NovaException(msg)
|
|
+ # (danwent) the bridge will inherit this address, so we want to
|
|
+ # make sure it is the value set from the NetworkManager
|
|
+ if mac_address:
|
|
+ _execute('ifconfig', interface, 'ether', mac_address,
|
|
+ run_as_root=True)
|
|
+ _execute('ifconfig',interface, 'up',
|
|
+ run_as_root=True)
|
|
+ # NOTE(vish): set mtu every time to ensure that changes to mtu get
|
|
+ # propagated
|
|
+ _set_device_mtu(interface, mtu)
|
|
+ return interface
|
|
+
|
|
+ @staticmethod
|
|
+ @utils.synchronized('lock_vlan', external=True)
|
|
+ def remove_vlan(vlan_num):
|
|
+ """Delete a vlan."""
|
|
+ vlan_interface = 'vlan%s' % vlan_num
|
|
+ delete_net_dev(vlan_interface)
|
|
+
|
|
+ @staticmethod
|
|
+ @utils.synchronized('lock_bridge', external=True)
|
|
+ def ensure_bridge(bridge, interface, net_attrs=None, gateway=True,
|
|
+ filtering=True):
|
|
+ """Create a bridge unless it already exists.
|
|
+
|
|
+ :param interface: the interface to create the bridge on.
|
|
+ :param net_attrs: dictionary with attributes used to create bridge.
|
|
+ :param gateway: whether or not the bridge is a gateway.
|
|
+ :param filtering: whether or not to create filters on the bridge.
|
|
+
|
|
+ If net_attrs is set, it will add the net_attrs['gateway'] to the bridge
|
|
+ using net_attrs['broadcast'] and net_attrs['cidr']. It will also add
|
|
+ the ip_v6 address specified in net_attrs['cidr_v6'] if use_ipv6 is set.
|
|
+
|
|
+ The code will attempt to move any IPs that already exist on the
|
|
+ interface onto the bridge and reset the default gateway if necessary.
|
|
+
|
|
+ """
|
|
+ if not device_exists(bridge):
|
|
+ LOG.debug('Starting Bridge %s', bridge)
|
|
+ out, err = _execute('ifconfig', 'bridge', 'create', 'name', bridge,
|
|
+ check_exit_code=False, run_as_root=True)
|
|
+ if err and 'File exists' not in err:
|
|
+ msg = _('Failed to add bridge: %s') % err
|
|
+ raise exception.NovaException(msg)
|
|
+
|
|
+ _execute('ifconfig', bridge, 'up', run_as_root=True)
|
|
+
|
|
+ if interface:
|
|
+ LOG.debug('Adding interface %(interface)s to bridge %(bridge)s',
|
|
+ {'interface': interface, 'bridge': bridge})
|
|
+ out, err = _execute('ifconfig', bridge, 'addm', interface,
|
|
+ check_exit_code=False, run_as_root=True)
|
|
+ if err and 'File exists' not in err:
|
|
+ msg = _('Failed to add interface: %s') % err
|
|
+ raise exception.NovaException(msg)
|
|
+
|
|
+ # NOTE(apmelton): Linux bridge's default behavior is to use the
|
|
+ # lowest mac of all plugged interfaces. This isn't a problem when
|
|
+ # it is first created and the only interface is the bridged
|
|
+ # interface. But, as instance interfaces are plugged, there is a
|
|
+ # chance for the mac to change. So, set it here so that it won't
|
|
+ # change in the future.
|
|
+ if not CONF.fake_network:
|
|
+ interface_addrs = netifaces.ifaddresses(interface)
|
|
+ interface_mac = interface_addrs[netifaces.AF_LINK][0]['addr']
|
|
+ _execute('ifconfig', bridge, 'ether', interface_mac,
|
|
+ run_as_root=True)
|
|
+
|
|
+ out, err = _execute('ifconfig', interface, 'up',
|
|
+ check_exit_code=False, run_as_root=True)
|
|
+
|
|
+ # NOTE(vish): This will break if there is already an ip on the
|
|
+ # interface, so we move any ips to the bridge
|
|
+ # NOTE(danms): We also need to copy routes to the bridge so as
|
|
+ # not to break existing connectivity on the interface
|
|
+ old_routes = []
|
|
+ out, err = _execute('netstat', '-nrW', '-f', 'inet')
|
|
+ for line in out.split('\n'):
|
|
+ fields = line.split()
|
|
+ if len(fields) > 6 and (fields[6] == interface) and ('G' in fields[2]):
|
|
+ old_routes.append(fields)
|
|
+ _execute('route', '-q', 'delete', fields[0], fields[1],
|
|
+ run_as_root=True)
|
|
+ out, err = _execute('ifconfig', interface)
|
|
+ for line in out.split('\n'):
|
|
+ fields = line.split()
|
|
+ if fields and fields[0] == 'inet':
|
|
+ _execute(*_ifconfig_tail_cmd(interface, fields, 'delete'),
|
|
+ run_as_root=True)
|
|
+ _execute(*_ifconfig_tail_cmd(bridge, fields, 'add'),
|
|
+ run_as_root=True)
|
|
+ for fields in old_routes:
|
|
+ _execute('route', '-q', 'add', fields[0], fields[1],
|
|
+ run_as_root=True)
|
|
+
|
|
+ if filtering:
|
|
+ # Don't forward traffic unless we were told to be a gateway
|
|
+ if gateway:
|
|
+ firewall_manager.ensure_gateway_rules(bridge)
|
|
+ else:
|
|
+ firewall_manager.ensure_bridge_rules(bridge)
|
|
+
|
|
+ @staticmethod
|
|
+ @utils.synchronized('lock_bridge', external=True)
|
|
+ def remove_bridge(bridge, gateway=True, filtering=True):
|
|
+ """Delete a bridge."""
|
|
+ if not device_exists(bridge):
|
|
+ return
|
|
+ else:
|
|
+ if filtering:
|
|
+ if gateway:
|
|
+ firewall_manager.remove_gateway_rules(bridge)
|
|
+ else:
|
|
+ firewall_manager.remove_bridge_rules(bridge)
|
|
+ delete_bridge_dev(bridge)
|
|
+
|
|
+
|
|
+def isolate_dhcp_address(interface, address):
|
|
+ # block arp traffic to address across the interface
|
|
+ firewall_manager.ensure_dhcp_isolation(interface, address)
|
|
+
|
|
+
|
|
+def remove_isolate_dhcp_address(interface, address):
|
|
+ # block arp traffic to address across the interface
|
|
+ firewall_manager.remove_dhcp_isolation(interface, address)
|
|
+
|
|
+
|
|
+# plugs interfaces using Open vSwitch
|
|
+class FreeBSDOVSInterfaceDriver(FreeBSDNetInterfaceDriver):
|
|
+
|
|
+ def plug(self, network, mac_address, gateway=True):
|
|
+ dev = self.get_dev(network)
|
|
+ if not device_exists(dev):
|
|
+ bridge = CONF.freebsdnet_ovs_integration_bridge
|
|
+ _ovs_vsctl(['--', '--may-exist', 'add-port', bridge, dev,
|
|
+ '--', 'set', 'Interface', dev, 'type=internal',
|
|
+ '--', 'set', 'Interface', dev,
|
|
+ 'external-ids:iface-id=%s' % dev,
|
|
+ '--', 'set', 'Interface', dev,
|
|
+ 'external-ids:iface-status=active',
|
|
+ '--', 'set', 'Interface', dev,
|
|
+ 'external-ids:attached-mac=%s' % mac_address])
|
|
+ _execute('ifconfig', dev, 'ether', mac_address, run_as_root=True)
|
|
+ _set_device_mtu(dev, network.get('mtu'))
|
|
+ _execute('ifconfig', dev, 'up', run_as_root=True)
|
|
+ if not gateway:
|
|
+ # If we weren't instructed to act as a gateway then add the
|
|
+ # appropriate flows to block all non-dhcp traffic.
|
|
+ _execute('ovs-ofctl',
|
|
+ 'add-flow', bridge, 'priority=1,actions=drop',
|
|
+ run_as_root=True)
|
|
+ _execute('ovs-ofctl', 'add-flow', bridge,
|
|
+ 'udp,tp_dst=67,dl_dst=%s,priority=2,actions=normal' %
|
|
+ mac_address, run_as_root=True)
|
|
+ # .. and make sure iptbles won't forward it as well.
|
|
+ firewall_manager.ensure_bridge_rules(bridge)
|
|
+ else:
|
|
+ firewall_manager.ensure_gateway_rules(bridge)
|
|
+
|
|
+ return dev
|
|
+
|
|
+ def unplug(self, network):
|
|
+ dev = self.get_dev(network)
|
|
+ bridge = CONF.freebsdnet_ovs_integration_bridge
|
|
+ _ovs_vsctl(['--', '--if-exists', 'del-port', bridge, dev])
|
|
+ return dev
|
|
+
|
|
+ def get_dev(self, network):
|
|
+ dev = 'gw-' + str(network['uuid'][0:11])
|
|
+ return dev
|
|
+
|
|
+
|
|
+# plugs interfaces using FreeBSD Bridge when using NeutronManager
|
|
+class NeutronFreeBSDBridgeInterfaceDriver(FreeBSDNetInterfaceDriver):
|
|
+
|
|
+ BRIDGE_NAME_PREFIX = 'brq'
|
|
+ GATEWAY_INTERFACE_PREFIX = 'gw-'
|
|
+
|
|
+ def plug(self, network, mac_address, gateway=True):
|
|
+ dev = self.get_dev(network)
|
|
+ bridge = self.get_bridge(network)
|
|
+ if not gateway:
|
|
+ # If we weren't instructed to act as a gateway then add the
|
|
+ # appropriate flows to block all non-dhcp traffic.
|
|
+ # .. and make sure iptbles won't forward it as well.
|
|
+ firewall_manager.ensure_bridge_rules(bridge)
|
|
+ return bridge
|
|
+ else:
|
|
+ firewall_manager.ensure_gateway_rules(bridge)
|
|
+
|
|
+ create_tap_dev(dev, mac_address)
|
|
+
|
|
+ if not device_exists(bridge):
|
|
+ LOG.debug("Starting bridge %s ", bridge)
|
|
+ utils.execute('ifconfig', 'bridge', 'create', 'name', bridge, run_as_root=True)
|
|
+ utils.execute('ifconfig', bridge, 'ether', mac_address, run_as_root=True)
|
|
+ utils.execute('ifconfig', bridge, 'up', run_as_root=True)
|
|
+ LOG.debug("Done starting bridge %s", bridge)
|
|
+
|
|
+ full_ip = '%s/%s' % (network['dhcp_server'],
|
|
+ network['cidr'].rpartition('/')[2])
|
|
+ utils.execute('ifconfig', bridge, full_ip, 'add', run_as_root=True)
|
|
+
|
|
+ return dev
|
|
+
|
|
+ def unplug(self, network):
|
|
+ dev = self.get_dev(network)
|
|
+ if not device_exists(dev):
|
|
+ return None
|
|
+ else:
|
|
+ delete_net_dev(dev)
|
|
+ return dev
|
|
+
|
|
+ def get_dev(self, network):
|
|
+ dev = self.GATEWAY_INTERFACE_PREFIX + str(network['uuid'][0:11])
|
|
+ return dev
|
|
+
|
|
+ def get_bridge(self, network):
|
|
+ bridge = self.BRIDGE_NAME_PREFIX + str(network['uuid'][0:11])
|
|
+ return bridge
|
|
+
|
|
+
|
|
+class FirewallManager(object):
|
|
+ def __init__(self, execute=_execute):
|
|
+ self.execute = execute
|
|
+ self.apply_deferred = False
|
|
+ self.anchor = 'org.openstack/%s' % get_binary_name()
|
|
+ self.rules = {
|
|
+ "translation": [],
|
|
+ "filtering": []
|
|
+ }
|
|
+ self.is_dirty = False
|
|
+
|
|
+ def _get_rule_section(self, rule):
|
|
+ LOG.warning("processing rule: %s" % rule)
|
|
+ head, tail = rule.split(' ', 1)
|
|
+ if head in ('nat', 'rdr'):
|
|
+ return 'translation'
|
|
+ elif head in ('pass', 'block'):
|
|
+ return 'filtering'
|
|
+ else:
|
|
+ return None
|
|
+
|
|
+ def add_rule(self, rule):
|
|
+ cleaned_rule = rule.strip()
|
|
+ section = self._get_rule_section(cleaned_rule)
|
|
+ if section:
|
|
+ if cleaned_rule not in self.rules[section]:
|
|
+ self.rules[section].append(cleaned_rule)
|
|
+ self.is_dirty = True
|
|
+ LOG.warning("Added rule to %s: %s", section, cleaned_rule)
|
|
+
|
|
+ def remove_rule(self, rule):
|
|
+ cleaned_rule = rule.strip()
|
|
+ section = self._get_rule_section(cleaned_rule)
|
|
+ LOG.warning("Removing rule from %s: %s", section, cleaned_rule)
|
|
+ if section:
|
|
+ try:
|
|
+ self.rules[section].remove(cleaned_rule)
|
|
+ self.is_dirty = True
|
|
+ except:
|
|
+ pass
|
|
+
|
|
+ def defer_apply_on(self):
|
|
+ self.apply_deferred = True
|
|
+
|
|
+ def defer_apply_off(self):
|
|
+ self.apply_deferred = False
|
|
+ self.apply()
|
|
+
|
|
+ def dirty(self):
|
|
+ return self.is_dirty
|
|
+
|
|
+ def apply(self):
|
|
+ if self.apply_deferred:
|
|
+ return
|
|
+ if self.dirty():
|
|
+ self._apply()
|
|
+ else:
|
|
+ LOG.debug("Skipping apply due to lack of new rules")
|
|
+
|
|
+ @utils.synchronized('pfctl', external=True)
|
|
+ def _apply(self):
|
|
+ all_lines = []
|
|
+ all_lines.extend(self.rules['translation'])
|
|
+ all_lines.extend(self.rules['filtering'])
|
|
+ all_lines.extend(["\n"])
|
|
+
|
|
+ self.is_dirty = False
|
|
+ self.execute("pfctl", "-a", self.anchor, "-f", "-",
|
|
+ process_input="\n".join(all_lines),
|
|
+ run_as_root=True)
|
|
+ LOG.warning("FirewallManager.apply completed with success")
|
|
+
|
|
+ def get_gateway_rules(self, bridge):
|
|
+ LOG.warning("FirewallManager.get_gateway_rules: "
|
|
+ "Please configure rules in pf.conf")
|
|
+ return []
|
|
+
|
|
+ def ensure_gateway_rules(self, bridge):
|
|
+ for rule in self.get_gateway_rules(bridge):
|
|
+ self.add_rule(rule)
|
|
+
|
|
+ def remove_gateway_rules(self, bridge):
|
|
+ for rule in self.get_gateway_rules(bridge):
|
|
+ self.remove_rule(rule)
|
|
+
|
|
+ def ensure_bridge_rules(self, bridge):
|
|
+ LOG.warning("FirewallManager.ensure_bridge_rules: "
|
|
+ "Please configure rules in pf.conf")
|
|
+
|
|
+ def remove_bridge_rules(self, bridge):
|
|
+ LOG.warning("FirewallManager.remove_bridge_rules: "
|
|
+ "Please configure rules in pf.conf")
|
|
+
|
|
+ def ensure_dhcp_isolation(self, interface, address):
|
|
+ LOG.warning("FirewallManager.ensure_dhcp_isolation: "
|
|
+ "DHCP isolation is not yet implemented")
|
|
+
|
|
+ def remove_dhcp_isolation(self, interface, address):
|
|
+ LOG.warning("FirewallManager.remove_dhcp_isolation: "
|
|
+ "DHCP isolation is not yet implemented")
|
|
+
|
|
+ def ensure_in_network_traffic_rules(self, fixed_ip, network):
|
|
+ LOG.warning("FirewallManager.ensure_in_network_traffic_rules: "
|
|
+ "Please configure rules in pf.conf")
|
|
+
|
|
+ def remove_in_network_traffic_rules(self, fixed_ip, network):
|
|
+ LOG.warning("FirewallManager.remove_in_network_traffic_rules: "
|
|
+ "Please configure rules in pf.conf")
|
|
+
|
|
+ def floating_forward_rules(self, floating_ip, fixed_ip, device):
|
|
+ rules = []
|
|
+ rules.append("rdr inet from any to %s -> %s" % (floating_ip, fixed_ip))
|
|
+
|
|
+ return rules
|
|
+
|
|
+ def ensure_floating_rules(self, floating_ip, fixed_ip, device):
|
|
+ for rule in self.floating_forward_rules(floating_ip, fixed_ip, device):
|
|
+ self.add_rule(rule)
|
|
+
|
|
+ def remove_floating_rules(self, floating_ip, fixed_ip, device):
|
|
+ for rule in self.floating_forward_rules(floating_ip, fixed_ip, device):
|
|
+ self.remove_rule(rule)
|
|
+
|
|
+ def add_snat_rule(self, ip_range, is_external=False):
|
|
+ if CONF.routing_source_ip:
|
|
+ if is_external:
|
|
+ if CONF.force_snat_range:
|
|
+ snat_range = CONF.force_snat_range
|
|
+ else:
|
|
+ snat_range = []
|
|
+ else:
|
|
+ snat_range = ['0.0.0.0/0']
|
|
+ for dest_range in snat_range:
|
|
+ if not is_external and CONF.public_interface:
|
|
+ firewall_manager.add_rule("nat on %s inet from %s to %s -> %s" %
|
|
+ (CONF.public_interface,
|
|
+ ip_range,
|
|
+ dest_range,
|
|
+ CONF.routing_source_ip))
|
|
+ else:
|
|
+ firewall_manager.add_rule("nat inet from %s to %s -> %s" %
|
|
+ (ip_range,
|
|
+ dest_range,
|
|
+ CONF.routing_source_ip))
|
|
+ firewall_manager.apply()
|
|
+
|
|
+
|
|
+firewall_manager = FirewallManager()
|
|
+
|
|
+
|
|
+def get_firewall_manager():
|
|
+ return firewall_manager
|
|
--
|
|
2.8.1
|
|
|