components/openstack/neutron/files/agent/solaris/net_lib.py
author Girish Moodalbail <Girish.Moodalbail@oracle.COM>
Thu, 26 Jun 2014 19:18:52 -0700
changeset 1977 12e9c20eef5a
parent 1944 56ac2df1785b
child 2083 87196737f09f
permissions -rw-r--r--
19034270 IPv6 filter rules are not properly handled

# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright (c) 2014, Oracle and/or its affiliates. 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.
#
# @author: Girish Moodalbail, Oracle, Inc.
#

import netaddr

from neutron.agent.linux import utils


class CommandBase(object):
    @classmethod
    def execute_with_pfexec(cls, cmd, **kwargs):
        # uses pfexec
        cmd.insert(0, '/usr/bin/pfexec')
        return utils.execute(cmd, **kwargs)

    @classmethod
    def execute(cls, cmd, **kwargs):
        return utils.execute(cmd, **kwargs)


class IPInterface(CommandBase):
    '''Wrapper around Solaris ipadm(1m) command.'''

    def __init__(self, ifname):
        self._ifname = ifname

    @classmethod
    def ifname_exists(cls, ifname):

        cmd = ['/usr/sbin/ipadm', 'show-if', '-po', 'ifname']
        stdout = cls.execute(cmd)

        return ifname in stdout

    @classmethod
    def ipaddr_exists(cls, ifname, ipaddr):

        if not cls.ifname_exists(ifname):
            return False

        cmd = ['/usr/sbin/ipadm', 'show-addr', '-po', 'addr', ifname]
        stdout = cls.execute(cmd)

        return ipaddr in stdout

    def ipaddr_list(self, filters=None):
        cmd = ['/usr/sbin/ipadm', 'show-addr', '-po', 'type,addr',
               self._ifname]
        stdout = self.execute(cmd)
        atype_addrs = stdout.strip().split('\n')
        result = {}
        for atype_addr in atype_addrs:
            atype, addr = atype_addr.split(':', 1)
            val = result.get(atype)
            if val is None:
                result[atype] = []
                val = result.get(atype)
            # in the case of IPv6 addresses remove any escape '\' character
            val.append(addr.replace("\\", ""))
        return result

    def create_address(self, ipaddr, addrobjname=None, temp=True):
        if not self.ifname_exists(self._ifname):
            # create ip interface
            cmd = ['/usr/sbin/ipadm', 'create-ip', self._ifname]
            if temp:
                cmd.append('-t')
            self.execute_with_pfexec(cmd)

        if self.ipaddr_exists(self._ifname, ipaddr):
            return

        # If an address is IPv6, then to create a static IPv6 address
        # we need to create link-local address first
        if netaddr.IPNetwork(ipaddr).version == 6:
            # check if link-local address already exists
            cmd = ['/usr/sbin/dladm', 'show-linkprop', '-co', 'value',
                   '-p', 'mac-address', self._ifname]
            stdout = self.execute(cmd)
            mac_addr = stdout.splitlines()[0].strip()
            ll_addr = netaddr.EUI(mac_addr).ipv6_link_local()

            if not self.ipaddr_exists(self._ifname, str(ll_addr)):
                # create a link-local address
                cmd = ['/usr/sbin/ipadm', 'create-addr', '-T', 'static', '-a',
                       str(ll_addr), self._ifname]
                if temp:
                    cmd.append('-t')
                self.execute_with_pfexec(cmd)

        cmd = ['/usr/sbin/ipadm', 'create-addr', '-T', 'static', '-a',
               ipaddr, self._ifname]
        if temp:
            cmd.append('-t')

        return self.execute_with_pfexec(cmd)

    def delete_address(self, ipaddr):
        if not self.ipaddr_exists(self._ifname, ipaddr):
            return

        cmd = ['/usr/sbin/ipadm', 'show-addr', '-po', 'addrobj,addr',
               self._ifname]
        stdout = self.execute(cmd)
        aobj_addrs = stdout.strip().split('\n')
        for aobj_addr in aobj_addrs:
            if ipaddr not in aobj_addr:
                continue
            aobj = aobj_addr.split(':')[0]
            cmd = ['/usr/sbin/ipadm', 'delete-addr', aobj]
            self.execute_with_pfexec(cmd)
            break

        isV6 = netaddr.IPNetwork(ipaddr).version == 6
        if len(aobj_addrs) == 1 or (isV6 and len(aobj_addrs) == 2):
            # delete the interface as well
            cmd = ['/usr/sbin/ipadm', 'delete-ip', self._ifname]
            self.execute_with_pfexec(cmd)

    def delete_ip(self):
        if not self.ifname_exists(self._ifname):
            return

        cmd = ['/usr/sbin/ipadm', 'delete-ip', self._ifname]
        self.execute_with_pfexec(cmd)


class Datalink(CommandBase):
    '''Wrapper around Solaris dladm(1m) command.'''

    def __init__(self, dlname):
        self._dlname = dlname

    @classmethod
    def datalink_exists(cls, dlname):

        cmd = ['/usr/sbin/dladm', 'show-link', '-po', 'link']
        stdout = utils.execute(cmd)

        return dlname in stdout

    def connect_vnic(self, evsvport, tenantname=None, temp=True):
        if self.datalink_exists(self._dlname):
            return

        cmd = ['/usr/sbin/dladm', 'create-vnic', '-c', evsvport, self._dlname]
        if temp:
            cmd.append('-t')
        if tenantname:
            cmd.append('-T')
            cmd.append(tenantname)

        return self.execute_with_pfexec(cmd)

    def create_vnic(self, lower_link, mac_address=None, vid=None, temp=True):
        if self.datalink_exists(self._dlname):
            return

        if vid:
            # If the default_tag of lower_link is same as vid, then there
            # is no need to set vid
            cmd = ['/usr/sbin/dladm', 'show-linkprop', '-co', 'value',
                   '-p', 'default_tag', lower_link]
            stdout = utils.execute(cmd)
            if stdout.splitlines()[0].strip() == vid:
                vid = '0'
        else:
            vid = '0'
        cmd = ['/usr/sbin/dladm', 'create-vnic', '-l', lower_link,
               '-m', mac_address, '-v', vid, self._dlname]
        if temp:
            cmd.append('-t')

        return self.execute_with_pfexec(cmd)

    def delete_vnic(self):
        if not self.datalink_exists(self._dlname):
            return

        cmd = ['/usr/sbin/dladm', 'delete-vnic', self._dlname]
        return self.execute_with_pfexec(cmd)


class IPpoolCommand(CommandBase):
    '''Wrapper around Solaris ippool(1m) command'''

    def __init__(self, pool_name, role='ipf', pool_type='tree'):
        self._pool_name = pool_name
        self._role = role
        self._pool_type = pool_type

    def pool_exists(self):
        cmd = ['/usr/sbin/ippool', '-l', '-m', self._pool_name,
               '-t', self._pool_type]
        stdout = self.execute_with_pfexec(cmd)
        return str(self._pool_name) in stdout

    def pool_split_nodes(self, ip_cidrs):
        cmd = ['/usr/sbin/ippool', '-l', '-m', self._pool_name,
               '-t', self._pool_type]
        stdout = self.execute_with_pfexec(cmd)
        existing_nodes = []
        non_existing_nodes = []
        for ip_cidr in ip_cidrs:
            if ip_cidr in stdout:
                existing_nodes.append(ip_cidr)
            else:
                non_existing_nodes.append(ip_cidr)
        return existing_nodes, non_existing_nodes

    def add_pool_nodes(self, ip_cidrs):
        ip_cidrs = self.pool_split_nodes(ip_cidrs)[1]

        for ip_cidr in ip_cidrs:
            cmd = ['/usr/sbin/ippool', '-a', '-m', self._pool_name,
                   '-i', ip_cidr]
            self.execute_with_pfexec(cmd)

    def remove_pool_nodes(self, ip_cidrs):
        ip_cidrs = self.pool_split_nodes(ip_cidrs)[0]

        for ip_cidr in ip_cidrs:
            cmd = ['/usr/sbin/ippool', '-r', '-m', self._pool_name,
                   '-i', ip_cidr]
            self.execute_with_pfexec(cmd)

    def add_pool(self):
        if self.pool_exists():
            return

        cmd = ['/usr/sbin/ippool', '-A', '-m', self._pool_name,
               '-o', self._role, '-t', self._pool_type]
        self.execute_with_pfexec(cmd)

    def remove_pool(self):
        if not self.pool_exists():
            return

        # This command will fail if ippool is in use by ipf, so the
        # caller has to ensure that it's not being used in an ipf rule
        cmd = ['/usr/sbin/ippool', '-R', '-m', self._pool_name,
               '-o', self._role, '-t', self._pool_type]
        self.execute_with_pfexec(cmd)


class IPfilterCommand(CommandBase):
    '''Wrapper around Solaris ipf(1m) command'''

    def _split_rules(self, rules, version):
        # assumes that rules are inbound!
        cmd = ['/usr/sbin/ipfstat', '-i']
        if version == 6:
            cmd.insert(1, '-6')
        stdout = self.execute_with_pfexec(cmd)
        existing_rules = []
        non_existing_rules = []
        for rule in rules:
            if rule in stdout:
                existing_rules.append(rule)
            else:
                non_existing_rules.append(rule)

        return existing_rules, non_existing_rules

    def add_rules(self, rules, version=4):
        rules = self._split_rules(rules, version)[1]
        process_input = '\n'.join(rules)
        cmd = ['/usr/sbin/ipf', '-f', '-']
        if version == 6:
            cmd.insert(1, '-6')
        return self.execute_with_pfexec(cmd, process_input=process_input)

    def remove_rules(self, rules, version=4):
        rules = self._split_rules(rules, version)[0]
        process_input = '\n'.join(rules)
        cmd = ['/usr/sbin/ipf', '-r', '-f', '-']
        if version == 6:
            cmd.insert(1, '-6')
        return self.execute_with_pfexec(cmd, process_input=process_input)


class IPnatCommand(CommandBase):
    '''Wrapper around Solaris ipnat(1m) command'''

    def add_rules(self, rules):
        process_input = '\n'.join(rules)
        cmd = ['/usr/sbin/ipnat', '-f', '-']
        return self.execute_with_pfexec(cmd, process_input=process_input)

    def remove_rules(self, rules):
        process_input = '\n'.join(rules)
        cmd = ['/usr/sbin/ipnat', '-r', '-f', '-']
        return self.execute_with_pfexec(cmd, process_input=process_input)