2892N/A# vim: tabstop=4 shiftwidth=4 softtabstop=4
2892N/A
2892N/A# Copyright 2012 OpenStack Foundation
2892N/A# All Rights Reserved.
2892N/A#
6029N/A# Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved.
2521N/A#
2521N/A# Licensed under the Apache License, Version 2.0 (the "License"); you may
2521N/A# not use this file except in compliance with the License. You may obtain
2521N/A# a copy of the License at
2521N/A#
2521N/A# http://www.apache.org/licenses/LICENSE-2.0
2521N/A#
2521N/A# Unless required by applicable law or agreed to in writing, software
2521N/A# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
2521N/A# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
2521N/A# License for the specific language governing permissions and limitations
2521N/A# under the License.
2521N/A
2521N/Aimport abc
5403N/Aimport netaddr
6846N/Aimport os
2521N/A
6846N/Afrom oslo_config import cfg
5403N/Afrom oslo_log import log as logging
6846N/Afrom oslo_utils import excutils
2521N/A
6846N/Afrom neutron._i18n import _, _LI, _LW, _LE
6846N/Afrom neutron.agent.linux import dhcp
2892N/Afrom neutron.agent.linux import utils
2892N/Afrom neutron.agent.solaris import net_lib
3524N/Afrom neutron.common import constants
2892N/Afrom neutron.common import exceptions
4973N/Afrom neutron.common import ipv6_utils
5403N/A
2521N/ALOG = logging.getLogger(__name__)
2521N/A
2521N/A
5403N/Aclass Dnsmasq(dhcp.Dnsmasq):
5403N/A """ Wrapper around Linux implementation of Dnsmasq."""
2521N/A
5403N/A def __init__(self, conf, network, process_monitor, version=None,
5403N/A plugin=None):
5403N/A super(Dnsmasq, self).__init__(conf, network, process_monitor,
5403N/A version, plugin)
5403N/A self.device_manager = DeviceManager(self.conf, plugin)
2521N/A
6846N/A # overrides method in DhcpLocalProcess due to no namespace support
6846N/A def _destroy_namespace_and_port(self):
6846N/A try:
6846N/A self.device_manager.destroy(self.network, self.interface_name)
6846N/A except RuntimeError:
6846N/A LOG.warning(_LW('Failed trying to delete interface: %s'),
6846N/A self.interface_name)
6846N/A
5403N/A def _build_cmdline_callback(self, pid_file):
6846N/A # We ignore local resolv.conf if dns servers are specified
6846N/A # or if local resolution is explicitly disabled.
6846N/A _no_resolv = (
6846N/A '--no-resolv' if self.conf.dnsmasq_dns_servers or
6846N/A not self.conf.dnsmasq_local_resolv else '')
2521N/A cmd = [
2521N/A '/usr/lib/inet/dnsmasq',
2521N/A '--no-hosts',
6846N/A _no_resolv,
2521N/A '--strict-order',
2521N/A '--bind-interfaces',
2521N/A '--interface=%s' % self.interface_name,
2521N/A '--except-interface=lo0',
5403N/A '--pid-file=%s' % pid_file,
5403N/A '--dhcp-hostsfile=%s' % self.get_conf_file_name('host'),
5403N/A '--addn-hosts=%s' % self.get_conf_file_name('addn_hosts'),
5403N/A '--dhcp-optsfile=%s' % self.get_conf_file_name('opts'),
6846N/A '--dhcp-leasefile=%s' % self.get_conf_file_name('leases'),
6846N/A '--dhcp-match=set:ipxe,175',
2521N/A ]
2521N/A
2892N/A possible_leases = 0
2521N/A for i, subnet in enumerate(self.network.subnets):
3998N/A mode = None
2521N/A # if a subnet is specified to have dhcp disabled
2521N/A if not subnet.enable_dhcp:
2521N/A continue
2521N/A if subnet.ip_version == 4:
2521N/A mode = 'static'
2521N/A else:
2892N/A # We need to also set the DUID for the DHCPv6 server to use
2763N/A macaddr_cmd = ['/usr/sbin/dladm', 'show-linkprop',
2763N/A '-co', 'value', '-p', 'mac-address',
2763N/A self.interface_name]
2763N/A stdout = utils.execute(macaddr_cmd)
2763N/A uid = stdout.splitlines()[0].strip()
3998N/A # format the MAC address
3998N/A uid = ':'.join(['%.2x' % w for w in netaddr.EUI(uid).words])
2763N/A # IANA assigned ID for Oracle
2763N/A enterprise_id = '111'
2763N/A cmd.append('--dhcp-duid=%s,%s' % (enterprise_id, uid))
3998N/A
3998N/A # Note(scollins) If the IPv6 attributes are not set, set it as
3998N/A # static to preserve previous behavior
3998N/A addr_mode = getattr(subnet, 'ipv6_address_mode', None)
3998N/A ra_mode = getattr(subnet, 'ipv6_ra_mode', None)
3998N/A if (addr_mode in [constants.DHCPV6_STATEFUL,
3998N/A constants.DHCPV6_STATELESS] or
3998N/A not addr_mode and not ra_mode):
3998N/A mode = 'static'
2892N/A
2892N/A cidr = netaddr.IPNetwork(subnet.cidr)
2892N/A
3998N/A if self.conf.dhcp_lease_duration == -1:
3998N/A lease = 'infinite'
3998N/A else:
3998N/A lease = '%ss' % self.conf.dhcp_lease_duration
3998N/A
3998N/A # mode is optional and is not set - skip it
3998N/A if mode:
3998N/A if subnet.ip_version == 4:
3998N/A cmd.append('--dhcp-range=%s%s,%s,%s,%s' %
3998N/A ('set:', self._TAG_PREFIX % i,
3998N/A cidr.network, mode, lease))
3998N/A else:
3998N/A cmd.append('--dhcp-range=%s%s,%s,%s,%d,%s' %
3998N/A ('set:', self._TAG_PREFIX % i,
3998N/A cidr.network, mode,
3998N/A cidr.prefixlen, lease))
3998N/A possible_leases += cidr.size
2892N/A
5403N/A if cfg.CONF.advertise_mtu:
6846N/A mtu = getattr(self.network, 'mtu', 0)
5403N/A # Do not advertise unknown mtu
5403N/A if mtu > 0:
5403N/A cmd.append('--dhcp-option-force=option:mtu,%d' % mtu)
5403N/A
2892N/A # Cap the limit because creating lots of subnets can inflate
2892N/A # this possible lease cap.
2892N/A cmd.append('--dhcp-lease-max=%d' %
2892N/A min(possible_leases, self.conf.dnsmasq_lease_max))
2521N/A
2521N/A cmd.append('--conf-file=%s' % self.conf.dnsmasq_config_file)
3998N/A if self.conf.dnsmasq_dns_servers:
3998N/A cmd.extend(
3998N/A '--server=%s' % server
3998N/A for server in self.conf.dnsmasq_dns_servers)
2521N/A
2521N/A if self.conf.dhcp_domain:
2521N/A cmd.append('--domain=%s' % self.conf.dhcp_domain)
2892N/A
5403N/A if self.conf.dhcp_broadcast_reply:
5403N/A cmd.append('--dhcp-broadcast')
5403N/A
6846N/A if self.conf.dnsmasq_base_log_dir:
6846N/A log_dir = os.path.join(
6846N/A self.conf.dnsmasq_base_log_dir,
6846N/A self.network.id)
6846N/A try:
6846N/A if not os.path.exists(log_dir):
6846N/A os.makedirs(log_dir)
6846N/A except OSError:
6846N/A LOG.error(_LE('Error while create dnsmasq log dir: %s'),
6846N/A log_dir)
6846N/A else:
6846N/A log_filename = os.path.join(log_dir, 'dhcp_dns_log')
6846N/A cmd.append('--log-queries')
6846N/A cmd.append('--log-dhcp')
6846N/A cmd.append('--log-facility=%s' % log_filename)
6846N/A
5403N/A return cmd
2892N/A
6846N/A def _release_lease(self, mac_address, ip, client_id):
2959N/A """Release a DHCP lease."""
6846N/A if netaddr.IPAddress(ip).version == constants.IP_VERSION_6:
6846N/A # Note(SridharG) dhcp_release is only supported for IPv4
6846N/A # addresses. For more details, please refer to man page.
6846N/A return
6846N/A
3998N/A cmd = ['/usr/lib/inet/dhcp_release', self.interface_name,
3998N/A ip, mac_address]
6846N/A if client_id:
6846N/A cmd.append(client_id)
5403N/A utils.execute(cmd)
2521N/A
2892N/A def _make_subnet_interface_ip_map(self):
2892N/A # TODO(gmoodalb): need to complete this when we support metadata
5403N/A # in neutron-dhcp-agent as-well for isolated subnets
2892N/A pass
2892N/A
3998N/A @classmethod
3998N/A def should_enable_metadata(cls, conf, network):
5403N/A # TODO(gmoodalb): need to complete this when we support metadata
5403N/A # in neutron-dhcp-agent as-well for isolated subnets
3998N/A return False
2521N/A
2892N/A
5403N/Aclass DeviceManager(dhcp.DeviceManager):
2892N/A
5403N/A def __init__(self, conf, plugin):
5403N/A super(DeviceManager, self).__init__(conf, plugin)
3998N/A
6846N/A def _set_default_route(self, network, device_name):
6846N/A """Sets the default gateway for this dhcp namespace.
6846N/A
6846N/A This method is idempotent and will only adjust the route if adjusting
6846N/A it would change it from what it already is. This makes it safe to call
6846N/A and avoids unnecessary perturbation of the system.
6846N/A """
6846N/A pass
2892N/A
6846N/A def _setup_existing_dhcp_port(self, network, device_id, dhcp_subnets):
6846N/A """Set up the existing DHCP port, if there is one."""
2892N/A
6846N/A # To avoid pylint thinking that port might be undefined after
6846N/A # the following loop...
6846N/A port = None
6846N/A
6846N/A # Look for an existing DHCP port for this network.
2892N/A for port in network.ports:
2892N/A port_device_id = getattr(port, 'device_id', None)
3524N/A port_device_owner = getattr(port, 'device_owner', None)
3524N/A if (port_device_id == device_id or
6846N/A (port_device_owner == constants.DEVICE_OWNER_DHCP and
6846N/A port_device_id.startswith('dhcp'))):
6846N/A # If using gateway IPs on this port, we can skip the
6846N/A # following code, whose purpose is just to review and
6846N/A # update the Neutron-allocated IP addresses for the
6846N/A # port.
6846N/A if self.driver.use_gateway_ips:
6846N/A return port
6846N/A # Otherwise break out, as we now have the DHCP port
6846N/A # whose subnets and addresses we need to review.
6846N/A break
6846N/A else:
6846N/A return None
2892N/A
6846N/A # Compare what the subnets should be against what is already
6846N/A # on the port.
6846N/A dhcp_enabled_subnet_ids = set(dhcp_subnets)
6846N/A port_subnet_ids = set(ip.subnet_id for ip in port.fixed_ips)
2892N/A
6846N/A # If those differ, we need to call update.
6846N/A if dhcp_enabled_subnet_ids != port_subnet_ids:
6846N/A # Collect the subnets and fixed IPs that the port already
6846N/A # has, for subnets that are still in the DHCP-enabled set.
6846N/A wanted_fixed_ips = []
6846N/A for fixed_ip in port.fixed_ips:
6846N/A if fixed_ip.subnet_id in dhcp_enabled_subnet_ids:
6846N/A wanted_fixed_ips.append(
6846N/A {'subnet_id': fixed_ip.subnet_id,
6846N/A 'ip_address': fixed_ip.ip_address})
3998N/A
6846N/A # Add subnet IDs for new DHCP-enabled subnets.
6846N/A wanted_fixed_ips.extend(
6846N/A dict(subnet_id=s)
6846N/A for s in dhcp_enabled_subnet_ids - port_subnet_ids)
2892N/A
6846N/A # Update the port to have the calculated subnets and fixed
6846N/A # IPs. The Neutron server will allocate a fresh IP for
6846N/A # each subnet that doesn't already have one.
6846N/A port = self.plugin.update_dhcp_port(
6846N/A port.id,
6846N/A {'port': {'network_id': network.id,
6846N/A 'fixed_ips': wanted_fixed_ips}})
6846N/A if not port:
6846N/A raise exceptions.Conflict()
3998N/A
6846N/A return port
2892N/A
3998N/A def setup(self, network):
2892N/A """Create and initialize a device for network's DHCP on this host."""
2892N/A port = self.setup_dhcp_port(network)
6846N/A self._update_dhcp_port(network, port)
2892N/A interface_name = self.get_interface_name(network, port)
2892N/A
2892N/A if net_lib.Datalink.datalink_exists(interface_name):
5403N/A LOG.debug('Reusing existing device: %s.', interface_name)
2892N/A else:
6846N/A try:
6846N/A self.driver.plug(network.tenant_id, network.id,
6846N/A port.id, interface_name, port.mac_address,
6846N/A network=network, mtu=network.get('mtu'),
6846N/A vif_type=getattr(port, 'binding:vif_type',
6846N/A None))
6846N/A except Exception:
6846N/A with excutils.save_and_reraise_exception():
6846N/A LOG.exception(_LE('Unable to plug DHCP port for '
6846N/A 'network %s. Releasing port.'),
6846N/A network.id)
6846N/A self.plugin.release_dhcp_port(network.id, port.device_id)
2892N/A ip_cidrs = []
4973N/A addrconf = False
2892N/A for fixed_ip in port.fixed_ips:
2892N/A subnet = fixed_ip.subnet
4973N/A if not ipv6_utils.is_auto_address_subnet(subnet):
4973N/A net = netaddr.IPNetwork(subnet.cidr)
4973N/A ip_cidr = '%s/%s' % (fixed_ip.ip_address, net.prefixlen)
4973N/A ip_cidrs.append(ip_cidr)
4973N/A else:
4973N/A addrconf = True
2892N/A
6846N/A if self.driver.use_gateway_ips:
6846N/A # For each DHCP-enabled subnet, add that subnet's gateway
6846N/A # IP address to the Linux device for the DHCP port.
6846N/A for subnet in network.subnets:
6846N/A if not subnet.enable_dhcp:
6846N/A continue
6846N/A gateway = subnet.gateway_ip
6846N/A if gateway:
6846N/A net = netaddr.IPNetwork(subnet.cidr)
6846N/A ip_cidrs.append('%s/%s' % (gateway, net.prefixlen))
6846N/A
6128N/A self.driver.init_l3(interface_name, ip_cidrs, addrconf=addrconf)
3998N/A
2892N/A return interface_name