# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright (C) 2012 Midokura Japan K.K. # Copyright (C) 2013 Midokura PTE LTD # 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: Tomoe Sugihara, Midokura Japan KK # @author: Ryu Ishimoto, Midokura Japan KK from quantum.openstack.common import log as logging LOG = logging.getLogger(__name__) PREFIX = 'OS_SG_' SUFFIX_IN = '_IN' SUFFIX_OUT = '_OUT' OS_ROUTER_IN_CHAIN_NAME_FORMAT = 'OS_ROUTER_IN_%s' OS_ROUTER_OUT_CHAIN_NAME_FORMAT = 'OS_ROUTER_OUT_%s' NAME_IDENTIFIABLE_PREFIX_LEN = len(PREFIX) + 36 # 36 = length of uuid def sg_label(sg_id, sg_name): """Construct the security group ID used as chain identifier in MidoNet.""" return PREFIX + str(sg_id) + '_' + sg_name port_group_name = sg_label def chain_names(sg_id, sg_name): """Get inbound and outbound chain names.""" prefix = sg_label(sg_id, sg_name) in_chain_name = prefix + SUFFIX_IN out_chain_name = prefix + SUFFIX_OUT return {'in': in_chain_name, 'out': out_chain_name} class ChainManager: def __init__(self, mido_api): self.mido_api = mido_api def create_for_sg(self, tenant_id, sg_id, sg_name): """Create a new chain for security group. Creating a security group creates a pair of chains in MidoNet, one for inbound and the other for outbound. """ LOG.debug(_("ChainManager.create_for_sg called: " "tenant_id=%(tenant_id)s sg_id=%(sg_id)s " "sg_name=%(sg_name)s "), {'tenant_id': tenant_id, 'sg_id': sg_id, 'sg_name': sg_name}) cnames = chain_names(sg_id, sg_name) self.mido_api.add_chain().tenant_id(tenant_id).name( cnames['in']).create() self.mido_api.add_chain().tenant_id(tenant_id).name( cnames['out']).create() def delete_for_sg(self, tenant_id, sg_id, sg_name): """Delete a chain mapped to a security group. Delete a SG means deleting all the chains (inbound and outbound) associated with the SG in MidoNet. """ LOG.debug(_("ChainManager.delete_for_sg called: " "tenant_id=%(tenant_id)s sg_id=%(sg_id)s " "sg_name=%(sg_name)s "), {'tenant_id': tenant_id, 'sg_id': sg_id, 'sg_name': sg_name}) cnames = chain_names(sg_id, sg_name) chains = self.mido_api.get_chains({'tenant_id': tenant_id}) for c in chains: if c.get_name() == cnames['in'] or c.get_name() == cnames['out']: LOG.debug(_('ChainManager.delete_for_sg: deleting chain=%r'), c) c.delete() def get_router_chains(self, tenant_id, router_id): """Get router chains. Returns a dictionary that has in/out chain resources key'ed with 'in' and 'out' respectively, given the tenant_id and the router_id passed in in the arguments. """ LOG.debug(_("ChainManager.get_router_chains called: " "tenant_id=%(tenant_id)s router_id=%(router_id)s"), {'tenant_id': tenant_id, 'router_id': router_id}) router_chain_names = self._get_router_chain_names(router_id) chains = {} for c in self.mido_api.get_chains({'tenant_id': tenant_id}): if c.get_name() == router_chain_names['in']: chains['in'] = c elif c.get_name() == router_chain_names['out']: chains['out'] = c return chains def create_router_chains(self, tenant_id, router_id): """Create a new chain on a router. Creates chains for the router and returns the same dictionary as get_router_chains() returns. """ LOG.debug(_("ChainManager.create_router_chains called: " "tenant_id=%(tenant_id)s router_id=%(router_id)s"), {'tenant_id': tenant_id, 'router_id': router_id}) chains = {} router_chain_names = self._get_router_chain_names(router_id) chains['in'] = self.mido_api.add_chain().tenant_id(tenant_id).name( router_chain_names['in']).create() chains['out'] = self.mido_api.add_chain().tenant_id(tenant_id).name( router_chain_names['out']).create() return chains def get_sg_chains(self, tenant_id, sg_id): """Get a list of chains mapped to a security group.""" LOG.debug(_("ChainManager.get_sg_chains called: " "tenant_id=%(tenant_id)s sg_id=%(sg_id)s"), {'tenant_id': tenant_id, 'sg_id': sg_id}) cnames = chain_names(sg_id, sg_name='') chain_name_prefix_for_id = cnames['in'][:NAME_IDENTIFIABLE_PREFIX_LEN] chains = {} for c in self.mido_api.get_chains({'tenant_id': tenant_id}): if c.get_name().startswith(chain_name_prefix_for_id): if c.get_name().endswith(SUFFIX_IN): chains['in'] = c if c.get_name().endswith(SUFFIX_OUT): chains['out'] = c assert 'in' in chains assert 'out' in chains return chains def _get_router_chain_names(self, router_id): LOG.debug(_("ChainManager.get_router_chain_names called: " "router_id=%(router_id)s"), {'router_id': router_id}) in_name = OS_ROUTER_IN_CHAIN_NAME_FORMAT % router_id out_name = OS_ROUTER_OUT_CHAIN_NAME_FORMAT % router_id router_chain_names = {'in': in_name, 'out': out_name} return router_chain_names class PortGroupManager: def __init__(self, mido_api): self.mido_api = mido_api def create(self, tenant_id, sg_id, sg_name): LOG.debug(_("PortGroupManager.create called: " "tenant_id=%(tenant_id)s sg_id=%(sg_id)s " "sg_name=%(sg_name)s"), {'tenant_id': tenant_id, 'sg_id': sg_id, 'sg_name': sg_name}) pg_name = port_group_name(sg_id, sg_name) self.mido_api.add_port_group().tenant_id(tenant_id).name( pg_name).create() def delete(self, tenant_id, sg_id, sg_name): LOG.debug(_("PortGroupManager.delete called: " "tenant_id=%(tenant_id)s sg_id=%(sg_id)s " "sg_name=%(sg_name)s"), {'tenant_id': tenant_id, 'sg_id': sg_id, 'sg_name': sg_name}) pg_name = port_group_name(sg_id, sg_name) pgs = self.mido_api.get_port_groups({'tenant_id': tenant_id}) for pg in pgs: if pg.get_name() == pg_name: LOG.debug(_("PortGroupManager.delete: deleting pg=%r"), pg) pg.delete() def get_for_sg(self, tenant_id, sg_id): LOG.debug(_("PortGroupManager.get_for_sg called: " "tenant_id=%(tenant_id)s sg_id=%(sg_id)s"), {'tenant_id': tenant_id, 'sg_id': sg_id}) pg_name_prefix = port_group_name( sg_id, sg_name='')[:NAME_IDENTIFIABLE_PREFIX_LEN] port_groups = self.mido_api.get_port_groups({'tenant_id': tenant_id}) for pg in port_groups: if pg.get_name().startswith(pg_name_prefix): LOG.debug(_("PortGroupManager.get_for_sg exiting: pg=%r"), pg) return pg return None class RuleManager: OS_SG_KEY = 'os_sg_rule_id' def __init__(self, mido_api): self.mido_api = mido_api self.chain_manager = ChainManager(mido_api) self.pg_manager = PortGroupManager(mido_api) def _properties(self, os_sg_rule_id): return {self.OS_SG_KEY: str(os_sg_rule_id)} def create_for_sg_rule(self, rule): LOG.debug(_("RuleManager.create_for_sg_rule called: rule=%r"), rule) direction = rule['direction'] protocol = rule['protocol'] port_range_max = rule['port_range_max'] rule_id = rule['id'] ethertype = rule['ethertype'] security_group_id = rule['security_group_id'] remote_group_id = rule['remote_group_id'] remote_ip_prefix = rule['remote_ip_prefix'] # watch out. not validated tenant_id = rule['tenant_id'] port_range_min = rule['port_range_min'] external_id = rule['external_id'] # construct a corresponding rule tp_src_start = tp_src_end = None tp_dst_start = tp_dst_end = None nw_src_address = None nw_src_length = None port_group_id = None # handle source if not remote_ip_prefix is None: nw_src_address, nw_src_length = remote_ip_prefix.split('/') elif not remote_group_id is None: # security group as a srouce source_pg = self.pg_manager.get_for_sg(tenant_id, remote_group_id) port_group_id = source_pg.get_id() else: raise Exception(_("Don't know what to do with rule=%r"), rule) # dst ports tp_dst_start, tp_dst_end = port_range_min, port_range_max # protocol if rule['protocol'] == 'tcp': nw_proto = 6 elif rule['protocol'] == 'udp': nw_proto = 17 elif rule['protocol'] == 'icmp': nw_proto = 1 # extract type and code from reporposed fields icmp_type = rule['from_port'] icmp_code = rule['to_port'] # translate -1(wildcard in OS) to midonet wildcard if icmp_type == -1: icmp_type = None if icmp_code == -1: icmp_code = None # set data for midonet rule tp_src_start = tp_src_end = icmp_type tp_dst_start = tp_dst_end = icmp_code chains = self.chain_manager.get_sg_chains(tenant_id, security_group_id) chain = None if direction == 'egress': chain = chains['in'] elif direction == 'ingress': chain = chains['out'] else: raise Exception(_("Don't know what to do with rule=%r"), rule) # create an accept rule properties = self._properties(rule_id) LOG.debug(_("RuleManager.create_for_sg_rule: adding accept rule " "%(rule_id) in portgroup %(port_group_id)s"), {'rule_id': rule_id, 'port_group_id': port_group_id}) chain.add_rule().port_group(port_group_id).type('accept').nw_proto( nw_proto).nw_src_address(nw_src_address).nw_src_length( nw_src_length).tp_src_start(tp_src_start).tp_src_end( tp_src_end).tp_dst_start(tp_dst_start).tp_dst_end( tp_dst_end).properties(properties).create() def delete_for_sg_rule(self, rule): LOG.debug(_("RuleManager.delete_for_sg_rule called: rule=%r"), rule) tenant_id = rule['tenant_id'] security_group_id = rule['security_group_id'] rule_id = rule['id'] properties = self._properties(rule_id) # search for the chains to find the rule to delete chains = self.chain_manager.get_sg_chains(tenant_id, security_group_id) for c in chains['in'], chains['out']: rules = c.get_rules() for r in rules: if r.get_properties() == properties: LOG.debug(_("RuleManager.delete_for_sg_rule: deleting " "rule %r"), r) r.delete()