dragonflow/dragonflow/controller/l3_openflow_app.py

1825 lines
66 KiB
Python
Executable File

# Copyright (c) 2015 OpenStack Foundation.
# 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.
import netaddr
import struct
import threading
from ryu.controller.handler import CONFIG_DISPATCHER
from ryu.controller.handler import MAIN_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.controller import ofp_event
from ryu.ofproto import ether
from ryu.ofproto import ofproto_v1_3
from ryu.lib.packet import arp
from ryu.lib.packet import ethernet
from ryu.lib.packet import packet
from ryu.lib.mac import haddr_to_bin
from ryu.lib.packet import icmp
from ryu.lib.packet import ipv4
from ryu.lib.packet import ipv6
from ryu.lib.packet import tcp
from ryu.lib.packet import udp
from ryu.lib import addrconv
from neutron import context
from neutron.common import constants as const
from neutron.i18n import _LE, _LI, _LW
from oslo_log import log
from dragonflow.controller.df_base_app import DFlowApp
from dragonflow.utils.bloomfilter import BloomFilter
LOG = log.getLogger(__name__)
ETHERNET = ethernet.ethernet.__name__
IPV4 = ipv4.ipv4.__name__
ICMP = icmp.icmp.__name__
TCP = tcp.tcp.__name__
UDP = udp.udp.__name__
VLANID_NONE = 0
VLANID_MIN = 2
VLANID_MAX = 4094
COOKIE_SHIFT_VLANID = 32
UINT16_MAX = 0xffff
UINT32_MAX = 0xffffffff
UINT64_MAX = 0xffffffffffffffff
OFPFW_NW_PROTO = 1 << 5
REG_32BIT_ON_MASK = 0x80000000
HIGH_PRIORITY_FLOW = 1000
MEDIUM_PRIORITY_FLOW = 100
NORMAL_PRIORITY_FLOW = 10
LOW_PRIORITY_FLOW = 1
LOWEST_PRIORITY_FLOW = 0
CONTROLLER_L3_CONFIGURED_FLOW_PRIORITY = 50
ROUTER_INTERFACE_FLOW_PRIORITY = 40
LOCAL_SUBNET_TRAFFIC_FLOW_PRIORITY = 30
EAST_WEST_TRAFFIC_TO_CONTROLLER_FLOW_PRIORITY = 20
SNAT_RULES_PRIORITY_FLOW = 10
class CookieFilter(BloomFilter):
"""Bloom Filter in a cookie
Useful and delicious!
To get a valid int cookie:
cf = CookieFilter(keys)
cookie = cf.to_cookie()
When adding a flow:
OFPModFlow(..., cookie=cookie)
When matching a flow (cookie can also just be 0xFFFFFFFF):
OFPModFlow(..., cookie=cookie, cookie_mask=cookie)
"""
def __init__(self, keys=()):
super(CookieFilter, self).__init__(
num_bytes=8,
num_probes=2,
iterable=keys,
)
def to_cookie(self):
return CookieFilter._array_to_int64(self.array)
@staticmethod
def from_route(route):
"""Create a filter for a route
:type route: list of PortData objects
:rtype: CookieFilter
"""
# TODO(saggi) memoize
return CookieFilter(port.cookie_hash for port in route)
@staticmethod
def from_port_data(port_data):
"""Create a CookieFilter from a single PortData object
:type port_data: PortData
:rtype: CookieFilter
"""
# TODO(saggi) memoize
return CookieFilter((port_data.cookie_hash,))
@staticmethod
def _array_to_int64(array):
"""Converts bloom filter mask or state to int64
Only works if array length is 8
:param array: Array of a bloom filter or mask
:return: The resulting array encoded as a signed int
:rtype: int
"""
assert(len(array) == 8)
return struct.unpack("Q", str(array))[0]
class AgentDatapath(object):
"""Represents a forwarding element switch local state"""
def __init__(self):
self.local_ports = None
self.datapath = 0
self.patch_port_num = 0
# Dictionary used to hold port information received from OVS
# each port data structure has a link to an entry in this dictionary
# in 'switch_port_desc'
self.switch_port_desc_dict = {}
class TenantTopology(object):
"""Represents a tenant topology"""
def __init__(self, tenant_id):
self.routers = {}
self.mac_to_port_data = {}
self.subnets = {}
self.id = tenant_id
def add_router(self, router):
self.routers[router.id] = router
def del_router(self, id):
try:
del self.routers[id]
except KeyError:
return -1
def find_port_data_by_ip_address(self, ip_address):
for port_data in self.mac_to_port_data.values():
for fixed_ip in port_data.fixed_ips:
if ip_address == fixed_ip['ip_address']:
return port_data, self.subnets[fixed_ip['subnet_id']]
else:
return 0, 0
def find_port_data_by_local_name(self, local_port_name):
"""
:param local_port_name: local name of the port on the switch
(eg. "qvo2dcdg-as")
:type: local_port_name: str
:return: A PortData object if one was found or None if no matching
object exists
:type: PortData
"""
name_prefix_len = 3
partial_id = local_port_name[name_prefix_len:]
for port_data in self.mac_to_port_data.values():
if port_data.id.startswith(partial_id):
return port_data
else:
return None
def get_route(self, pkt):
"""Get a possible route the packet can take to reach it's destination
The are no guarantees that the returned route is the fastest. Only that
it's possible.
Currently doesn't support extra routes.
:param pkt: The packet to trace
:return: A list of port_data objects if a route was found or None if no
route is available.
"""
pkt_ipv4 = pkt.get_protocol(ipv4.ipv4)
pkt_ethernet = pkt.get_protocol(ethernet.ethernet)
in_port_data = self.mac_to_port_data.get(pkt_ethernet.src)
out_port_data = self.mac_to_port_data.get(pkt_ethernet.dst)
if in_port_data is None or out_port_data is None:
LOG.error(
_LE("No data for packet ports %(src_mac)s %(dst_mac)s"),
{"src_mac": pkt_ethernet.src,
"dst_mac": pkt_ethernet.dst}
)
return None
(dst_port_data, dst_subnet) = self.find_port_data_by_ip_address(
pkt_ipv4.dst
)
if not dst_port_data:
LOG.error(
_LE("No data for destination port %(dst_ip)s"),
{"dst_ip": pkt_ipv4.dst}
)
return None
is_same_port = out_port_data.id == dst_port_data.id
if is_same_port:
return [in_port_data, out_port_data]
# In order to hop the target has to be a router
if not out_port_data.is_router_interface:
LOG.error(
_LE("The gateway port is not a router %(dst_mac)s"),
{"dst_mac": pkt_ethernet.dst}
)
return None
gateway_router = self.routers.get(out_port_data.device_id)
if gateway_router is None:
return None
if dst_subnet.id in gateway_router.subnets:
# TODO(saggi) add second leg to route
return [in_port_data, out_port_data, dst_port_data]
# route not found
return None
@property
def unused_subnets(self):
unused_subnets = self.subnets.copy()
for port in self.mac_to_port_data.values():
for fixed_ip in port.fixed_ips:
unused_subnets.pop(fixed_ip['subnet_id'], None)
# Optimization, no need to keep filtering if it's empty
if len(unused_subnets) == 0:
break
return unused_subnets.values()
def get_routers_ports_by_subnet(self, subnet):
ports = []
try:
for router in self.routers.values():
for subnet_id in router.subnets:
for interface in router.interfaces:
for subnet_info in interface['subnets']:
if subnet.id == subnet_info['id']:
ports.append(interface)
except KeyError:
LOG.error(_LE("AttributeError: for %s"), subnet.id)
return ports
class Router(object):
def __init__(self, data):
self.data = data
self.subnets = []
def add_subnet(self, subnet):
self.subnets.append(subnet.id)
def remove_subnet(self, subnet):
self.subnets.remove(subnet.id)
@property
def id(self):
return self.data['id']
@property
def interfaces(self):
return self.data.get('_interfaces', ())
class Subnet(object):
def __init__(self, data, segmentation_id):
self.data = data
self.segmentation_id = segmentation_id
def set_data(self, data):
self.data = data
@property
def id(self):
try:
return self.data['id']
except TypeError:
return -1
@property
def cidr(self):
"""Return the CIDR information for this subnet
:return: CIDR information for subnet
:rtype: netaddr.IPNetwork
"""
return netaddr.IPNetwork(self.data['cidr'])
@property
def gateway_ip(self):
return self.data.get('gateway_ip')
def is_ipv4(self):
try:
return (netaddr.IPNetwork(self.cidr).ip).version == 4
except TypeError:
return False
def __repr__(self):
return ("<Subnet id='%s' cidr='%s' gateway_ip='%s' " +
"segmentation_id='%s'>") % (
self.id,
self.cidr,
self.gateway_ip,
self.segmentation_id,
)
class SnatBinding(object):
def __init__(self, subnet, port):
self.subnet_id = subnet
self.sn_port = port
self.segmentation_id = 0
class PortData(object):
def __init__(self, port_data):
self._port_data = port_data
@property
def is_router_interface(self):
if self._port_data['device_owner'] in const.ROUTER_INTERFACE_OWNERS:
return True
else:
return False
@property
def id(self):
return self._port_data['id']
@property
def device_id(self):
return self._port_data['device_id']
@property
def fixed_ips(self):
try:
return tuple(self._port_data.get('fixed_ips'))
except KeyError:
return tuple()
@property
def subnets(self):
subnets = []
for fixed_ip in self.fixed_ips:
try:
subnets.append(fixed_ip['subnet_id'])
except KeyError:
LOG.error(_LE("No subnet info for %s"), fixed_ip)
continue
return subnets
@property
def segmentation_id(self):
return self._port_data['segmentation_id']
@property
def local_port_number(self):
try:
return self._port_data['switch_port_desc']['local_port_num']
except KeyError:
return -1
@property
def local_datapath_id(self):
try:
return self._port_data['switch_port_desc']['local_dpid_switch']
except KeyError:
return -1
@property
def mac_address(self):
return self._port_data['mac_address']
@property
def cookie_hash(self):
"""Create an int64 to be used for ovs cookie needs.
:return:
:rtype: int
"""
uuid_prefix = self.id.split('-', 1)[0]
return int(uuid_prefix, 16)
def get_subnet_from_ip_address(self, ip_address):
for fixed_ip in self.fixed_ips:
if ip_address == fixed_ip['ip_address']:
return fixed_ip
else:
return None
@property
def is_gateway(self):
for subnet in self._port_data['subnets']:
for fixed_ip in self.fixed_ips:
if subnet['id'] == fixed_ip['subnet_id']:
if subnet['gateway_ip'] == fixed_ip['ip_address']:
return True
else:
return False
def __getitem__(self, item):
return self._port_data.__getitem__(item)
def __setitem__(self, key, value):
return self._port_data.__setitem__(key, value)
class L3ReactiveApp(DFlowApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
BASE_RPC_API_VERSION = '1.0'
BASE_TABLE = 0
CLASSIFIER_TABLE = 40
METADATA_TABLE_ID = 50
ARP_AND_BR_TABLE = 51
L3_VROUTER_TABLE = 52
L3_PUBLIC_TABLE = 53
TUN_TRANSLATE_TABLE = 60
def __init__(self, *args, **kwargs):
super(L3ReactiveApp, self).__init__(*args, **kwargs)
self.mac_to_port = {}
self.ctx = context.get_admin_context()
self.lock = threading.Lock()
self._tenants = {}
self.need_sync = True
self.dp_list = {}
self.snat_bindings = {}
self.idle_timeout = kwargs['idle_timeout']
self.hard_timeout = kwargs['hard_timeout']
def get_tenant_by_id(self, tenant_id):
if tenant_id in self._tenants:
return self._tenants[tenant_id]
else:
return self._tenants.setdefault(tenant_id,
TenantTopology(tenant_id))
def start(self):
LOG.info(_LI("Starting Virtual L3 Reactive OpenFlow APP "))
super(L3ReactiveApp, self).start()
return 1
def delete_router(self, router_id):
for tenant in self._tenants.values():
try:
router = tenant.routers.pop(router_id)
except KeyError:
pass
else:
for interface in router.interfaces:
for subnet_info in interface['subnets']:
subnet = tenant.subnets[subnet_info['id']]
if subnet.segmentation_id == 0:
continue
if subnet.is_ipv4():
self._remove_vrouter_arp_responder_cast(
subnet.segmentation_id,
interface['mac_address'],
self.get_ip_from_interface(interface))
def sync_router(self, router_info):
LOG.info(_LI("sync_router --> %s"), router_info)
tenant_topology = self.get_tenant_by_id(router_info['tenant_id'])
router = Router(router_info)
router_old = tenant_topology.routers.get(router.id)
tenant_topology.add_router(router)
subnets = tenant_topology.subnets
for interface in router.interfaces:
for subnet_info in interface['subnets']:
subnet = subnets.setdefault(
subnet_info['id'],
Subnet(subnet_info, 0),
)
if subnet.data is None:
subnet.set_data(subnet_info)
router.add_subnet(subnet)
if subnet.segmentation_id != 0:
self.subnet_added_binding_cast(subnet, interface)
self.bootstrap_network_classifiers(
subnet=subnet)
# If previous definition of the router is known
if router_old:
# Handle removed subnets
for interface in router_old.interfaces:
for subnet_info in interface['subnets']:
subnet = subnets[subnet_info['id']]
if subnet.segmentation_id == 0:
continue
# if subnet was not deleted
if subnet.id in router.subnets:
continue
if subnet.is_ipv4():
self._remove_vrouter_arp_responder_cast(
subnet.segmentation_id,
interface['mac_address'],
self.get_ip_from_interface(interface))
if PortData(interface).is_gateway:
self._handle_remove_subnet(subnet)
self._handle_remove_port(PortData(interface))
def _handle_remove_subnet(self, subnet):
"""Remove all the flow relating to a specific subnet
:param subnet:
:type subnet: Subnet
"""
for dp in self.dp_list.values():
datapath = dp.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
match = parser.OFPMatch()
match.set_dl_type(ether.ETH_TYPE_IP)
match.set_ipv4_dst_masked(subnet.cidr.ip.value,
subnet.cidr.netmask.value)
msg = parser.OFPFlowMod(datapath=datapath,
cookie=0,
cookie_mask=0,
table_id=L3ReactiveApp.L3_VROUTER_TABLE,
command=ofproto.OFPFC_DELETE,
priority=MEDIUM_PRIORITY_FLOW,
out_port=ofproto.OFPP_ANY,
out_group=ofproto.OFPG_ANY,
match=match)
datapath.send_msg(msg)
match = parser.OFPMatch()
match.set_dl_type(ether.ETH_TYPE_IP)
match.set_metadata(subnet.segmentation_id)
msg = parser.OFPFlowMod(datapath=datapath,
cookie=0,
cookie_mask=0,
table_id=L3ReactiveApp.L3_VROUTER_TABLE,
command=ofproto.OFPFC_DELETE,
priority=MEDIUM_PRIORITY_FLOW,
out_port=ofproto.OFPP_ANY,
out_group=ofproto.OFPG_ANY,
match=match)
datapath.send_msg(msg)
def attach_switch_port_desc_to_port_data(self, port_data):
if 'id' in port_data:
port_id = port_data['id']
sub_str_port_id = str(port_id[0:11])
# Only true if we already received port_desc from OVS
for switch in self.dp_list.values():
switch_port_desc_dict = switch.switch_port_desc_dict
if sub_str_port_id in switch_port_desc_dict:
port_data['switch_port_desc'] = (
switch_port_desc_dict[sub_str_port_id])
port_desc = port_data['switch_port_desc']
self.add_flow_metadata_by_port_num(
port_desc['datapath'],
0,
HIGH_PRIORITY_FLOW,
port_desc['local_port_num'],
port_data['segmentation_id'],
0xffff,
self.CLASSIFIER_TABLE)
def delete_port(self, port):
"""
:param port:
:type port: PortData
"""
self._handle_remove_port(PortData(port))
def _remove_flow_local_subnet(self, subnet):
"""
:param subnet:
:type subnet: Subnet
"""
for dp in self.dp_list.values():
datapath = dp.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
match = parser.OFPMatch()
match.set_dl_type(ether.ETH_TYPE_IP)
match.set_metadata(subnet.segmentation_id)
msg = parser.OFPFlowMod(datapath=datapath,
cookie=0,
cookie_mask=0,
table_id=L3ReactiveApp.CLASSIFIER_TABLE,
command=ofproto.OFPFC_DELETE,
priority=MEDIUM_PRIORITY_FLOW,
out_port=ofproto.OFPP_ANY,
out_group=ofproto.OFPG_ANY,
match=match)
datapath.send_msg(msg)
def _find_tenant_for_port_data(self, port_data):
"""
:param port_data:
:type port_data: PortData
:return:
:rtype: TenantTopology or None
"""
for tenant in self._tenants.values():
if port_data.mac_address in tenant.mac_to_port_data:
return tenant
else:
return None
def sync_port(self, port):
LOG.info(_LI("sync_port--> %s\n"), port)
segmentation_id = port.get('segmentation_id')
if segmentation_id is None:
LOG.info(_LI("no segmentation data in port --> %s"), port)
return
tenant_topo = self.get_tenant_by_id(port['tenant_id'])
for subnet_dict in port.get('subnets', []):
subnet = tenant_topo.subnets.get(subnet_dict['id'])
if subnet:
subnet.set_data(subnet_dict)
subnet.segmentation_id = segmentation_id
else:
tenant_topo.subnets[subnet_dict['id']] = subnet = Subnet(
subnet_dict,
segmentation_id)
if port['device_owner'] == const.DEVICE_OWNER_ROUTER_INTF:
self.subnet_added_binding_cast(subnet, port)
self.bootstrap_network_classifiers(subnet=subnet)
self._add_flow_normal_local_subnet_cast(
LOCAL_SUBNET_TRAFFIC_FLOW_PRIORITY,
subnet)
tenant_topo.mac_to_port_data[port['mac_address']] = PortData(port)
self.attach_switch_port_desc_to_port_data(port)
def _get_input_packet_handler(self, pkt):
is_ipv4_packet = pkt.get_protocol(ipv4.ipv4) is not None
is_ipv6_packet = pkt.get_protocol(ipv6.ipv6) is not None
packet_handler = None
if is_ipv4_packet:
packet_handler = self.handle_ipv4_packet_in
elif is_ipv6_packet:
packet_handler = self.handle_ipv6_packet_in
return packet_handler
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def OF_packet_in_handler(self, event):
msg = event.msg
datapath = msg.datapath
ofproto = datapath.ofproto
if msg.reason == ofproto.OFPR_NO_MATCH:
reason = 'NO MATCH'
elif msg.reason == ofproto.OFPR_ACTION:
reason = 'ACTION'
elif msg.reason == ofproto.OFPR_INVALID_TTL:
reason = 'INVALID TTL'
else:
reason = 'unknown'
LOG.debug('OFPPacketIn received: '
'buffer_id=%x total_len=%d reason=%s '
'table_id=%d cookie=%d match=%s',
msg.buffer_id, msg.total_len, reason,
msg.table_id, msg.cookie, msg.match)
pkt = packet.Packet(msg.data)
packet_handler = self._get_input_packet_handler(pkt)
if packet_handler is None:
LOG.error(_LE("Unable to find appropriate packet "
"handler for packet: %s"), pkt)
else:
try:
packet_handler(datapath, msg, pkt)
except Exception as exception:
LOG.debug(
"Unable to handle packet %(msg)s: %(e)s",
{'msg': msg, 'e': exception}
)
def handle_ipv6_packet_in(self, datapath, msg, pkt):
# TODO(gampel)(gampel) add ipv6 support
LOG.error(_LE("No handle for ipv6 yet should be offload to the"
"NORMAL path %s"), pkt)
return
def is_known_datapath(self, datapath):
"""Check if datapath is known to this openflow appliaction"""
return self.dp_list.get(datapath.id) is not None
def _handle_router_packet(self, datapath, pkt, route):
"""Handle packets intended for routers
Specifically OAM (Operations, administration and management) packets
Does nothing if pkt is not a supported protocol.
The function assumes the route is valid and the packet is meant for the
last port in the route.
Currently only handles ping
:param datapath: The datapath to send through
:param pkt: The packet to handle
:param route: The resolved route the packet is it take
"""
is_icmp_packet = pkt.get_protocol(icmp.icmp) is not None
if is_icmp_packet:
# send ping response
self._handle_icmp(
datapath,
pkt,
route[0],
)
else:
pkt_ipv4 = pkt.get_protocol(ipv4.ipv4) is not None
LOG.error(_LE("any communication to a router that "
"is not ping should be dropped from "
"ip '%s'"),
pkt_ipv4.src)
def _handle_vm_packet(self, datapath, msg, pkt, route):
"""Handle packets intended for VMs
"""
pkt_ipv4 = pkt.get_protocol(ipv4.ipv4)
pkt_ethernet = pkt.get_protocol(ethernet.ethernet)
LOG.debug(
"Installing flow Route %s-> %s",
pkt_ipv4.src,
pkt_ipv4.dst)
self.install_l3_forwarding_flows(
datapath,
msg,
route[0],
pkt_ethernet,
pkt_ipv4,
route[1],
route[-1],
CookieFilter.from_route(route),
)
def _get_tenant_for_msg(self, msg, pkt):
segmentation_id = msg.match.get('metadata')
if segmentation_id is None:
return None
pkt_eth = pkt.get_protocol(ethernet.ethernet)
for tenant in self._tenants.values():
port_data = tenant.mac_to_port_data.get(pkt_eth)
if port_data is None:
# target is router
for subnet in tenant.subnets.values():
if subnet.segmentation_id == segmentation_id:
return tenant
else:
for fixed_ip in port_data.fixed_ips:
subnet = tenant.subnets.get(fixed_ip['subnet_id'])
if subnet is None:
continue
if subnet.segmentation_id == segmentation_id:
return tenant
def handle_ipv4_packet_in(self, datapath, msg, pkt):
if not self.is_known_datapath(datapath):
LOG.warning(_LW("Received packet from unknown datapath '%s'"),
datapath.id)
return
segmentation_id = msg.match.get('metadata')
if segmentation_id is None:
# send request for local switch data
self.send_port_desc_stats_request(datapath)
LOG.error(_LE("No metadata on packet from %s"),
pkt.get_protocol(ethernet.ethernet).src)
return
LOG.debug("packet segmentation_id %s ", segmentation_id)
tenant = self._get_tenant_for_msg(msg, pkt)
if tenant is None:
LOG.warning(_LW("No available tenant for packet %s"),
pkt.get_protocol(ethernet.ethernet).src)
route = tenant.get_route(pkt)
if route is None:
LOG.debug(
"No route is available for packet %(src_ip)s->%(dst_ip)s",
{"src_ip": pkt.get_protocol(ethernet.ethernet).src,
"dst_ip": pkt.get_protocol(ethernet.ethernet).dst})
return
final_port = route[-1]
if final_port.is_router_interface:
self._handle_router_packet(datapath, pkt, route)
else:
self._handle_vm_packet(datapath, msg, pkt, route)
def install_l3_forwarding_flows(
self,
datapath,
msg,
in_port_data,
pkt_eth,
pkt_ipv4,
gateway_port_data,
dst_port_data,
cookie_filter,
):
"""Install the l3 forwarding flows.
:param datapath: Datapath to install into
:param msg: Message to act upon
:param in_port_data: The port that the message arrived in
:type in_port_data: PortData
:param pkt_eth: The ethernet part of the packet
:param pkt_ipv4: The ipv4 part of the packet
:param gateway_port_data: The gateway port through which the packet
would have been routed
:type gateway_port_data: PortData
:param dst_port_data: The destination port.
:type dst_port_data: PortData
:param cookie_filter: The cookie to attach to all flows
:type cookie_filter: CookieFilter
"""
dst_port_dp_id = dst_port_data.local_datapath_id
dst_seg_id = dst_port_data.segmentation_id
in_port = in_port_data.local_port_number
dst_port = dst_port_data.local_port_number
src_seg_id = in_port_data.segmentation_id
cookie = cookie_filter.to_cookie()
if dst_port_dp_id == datapath.id:
# The dst VM and the source VM are on the same compute Node
# Send output flow directly to port, use the same datapath
actions = self.add_flow_subnet_traffic(
datapath,
self.L3_VROUTER_TABLE,
MEDIUM_PRIORITY_FLOW,
in_port,
src_seg_id,
pkt_eth.src,
pkt_eth.dst,
pkt_ipv4.dst,
pkt_ipv4.src,
gateway_port_data.mac_address,
dst_port_data.mac_address,
dst_port,
cookie=cookie,
)
# Install the reverse flow return traffic
self.add_flow_subnet_traffic(
datapath,
self.L3_VROUTER_TABLE,
MEDIUM_PRIORITY_FLOW,
dst_port,
dst_seg_id,
dst_port_data.mac_address,
gateway_port_data.mac_address,
pkt_ipv4.src,
pkt_ipv4.dst,
pkt_eth.dst,
in_port_data.mac_address,
in_port,
cookie=cookie,
)
self.handle_packet_out_l3(datapath, msg, dst_port, actions)
else:
# The dst VM and the source VM are NOT on the same compute node
# Send output to br-tun patch port and install reverse flow on the
# dst compute node
remote_switch = self.dp_list.get(dst_port_dp_id)
local_switch = self.dp_list.get(datapath.id)
actions = self.add_flow_subnet_traffic(
datapath,
self.L3_VROUTER_TABLE,
MEDIUM_PRIORITY_FLOW,
in_port,
src_seg_id,
pkt_eth.src,
pkt_eth.dst,
pkt_ipv4.dst,
pkt_ipv4.src,
gateway_port_data.mac_address,
dst_port_data.mac_address,
local_switch.patch_port_num,
dst_seg_id=dst_seg_id,
cookie=cookie,
dst_datapath=remote_switch.datapath,
)
# Remote reverse flow install
self.add_flow_subnet_traffic(
remote_switch.datapath,
self.L3_VROUTER_TABLE,
MEDIUM_PRIORITY_FLOW,
dst_port,
dst_seg_id,
dst_port_data.mac_address,
gateway_port_data.mac_address,
pkt_ipv4.src,
pkt_ipv4.dst,
pkt_eth.dst,
in_port_data.mac_address,
remote_switch.patch_port_num,
dst_seg_id=src_seg_id,
cookie=cookie,
dst_datapath=local_switch.datapath,
)
self.handle_packet_out_l3(remote_switch.datapath,
msg, dst_port, actions)
def handle_packet_out_l3(self, datapath, msg, in_port, actions):
data = None
parser = datapath.ofproto_parser
ofproto = datapath.ofproto
if msg.buffer_id == ofproto.OFP_NO_BUFFER:
data = msg.data
out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
in_port=in_port, actions=actions, data=data)
datapath.send_msg(out)
def add_flow_subnet_traffic(self, datapath, table, priority, in_port,
src_seg_id, match_src_mac, match_dst_mac,
match_dst_ip, match_src_ip, src_mac,
dst_mac, out_port_num, dst_seg_id=None,
cookie=0, dst_datapath=None):
parser = datapath.ofproto_parser
match = parser.OFPMatch()
match.set_dl_type(ether.ETH_TYPE_IP)
match.set_in_port(in_port)
match.set_metadata(src_seg_id)
match.set_dl_src(haddr_to_bin(match_src_mac))
match.set_dl_dst(haddr_to_bin(match_dst_mac))
match.set_ipv4_src(ipv4_text_to_int(str(match_src_ip)))
match.set_ipv4_dst(ipv4_text_to_int(str(match_dst_ip)))
actions = []
inst = []
ofproto = datapath.ofproto
actions.append(parser.OFPActionDecNwTtl())
actions.append(parser.OFPActionSetField(eth_src=src_mac))
actions.append(parser.OFPActionSetField(eth_dst=dst_mac))
if dst_datapath:
dst_ip_hex = self._get_dp_ip_as_int(dst_datapath)
actions.append(parser.OFPActionSetField(reg7=dst_ip_hex))
if dst_seg_id:
# The dest vm is on another compute machine so we must set the
# segmentation Id and set metadata for the tunnel bridge to
# for this flow
mask_dst_seg = int(dst_seg_id) | REG_32BIT_ON_MASK
field = parser.OFPActionSetField(pkt_mark=mask_dst_seg)
actions.append(field)
goto_inst = parser.OFPInstructionGotoTable(
self.TUN_TRANSLATE_TABLE)
inst.append(goto_inst)
else:
actions.append(parser.OFPActionOutput(out_port_num,
ofproto.OFPCML_NO_BUFFER))
inst.append(datapath.ofproto_parser.OFPInstructionActions(
ofproto.OFPIT_APPLY_ACTIONS, actions))
self.mod_flow(
datapath,
cookie=cookie,
inst=inst,
table_id=table,
priority=priority,
match=match,
out_port=out_port_num,
idle_timeout=self.idle_timeout,
hard_timeout=self.hard_timeout)
return actions
def _add_flow_normal_local_subnet_cast(self, priority, subnet):
if not subnet.is_ipv4():
LOG.info(_LI("No support for IPV6"))
return
cidr = subnet.cidr
for dp in self.dp_list.values():
self.add_flow_normal_local_subnet(
dp.datapath,
self.CLASSIFIER_TABLE,
priority,
cidr.network.format(),
str(cidr.prefixlen),
subnet.segmentation_id)
def add_flow_normal_local_subnet(self, datapath, table, priority,
dst_net, dst_mask, seg_id):
parser = datapath.ofproto_parser
ofproto = datapath.ofproto
match = parser.OFPMatch()
match.set_dl_type(ether.ETH_TYPE_IP)
match.set_metadata(seg_id)
match.set_ipv4_dst_masked(ipv4_text_to_int(str(dst_net)),
mask_ntob(int(dst_mask)))
actions = [
parser.OFPActionOutput(
ofproto.OFPP_NORMAL)]
inst = [datapath.ofproto_parser.OFPInstructionActions(
ofproto.OFPIT_APPLY_ACTIONS, actions)]
self.mod_flow(
datapath,
inst=inst,
table_id=table,
priority=priority,
match=match)
def add_flow_normal_by_port_num(self, datapath, table, priority, in_port):
parser = datapath.ofproto_parser
ofproto = datapath.ofproto
match = parser.OFPMatch(in_port=in_port)
actions = [parser.OFPActionOutput(ofproto.OFPP_NORMAL)]
inst = [datapath.ofproto_parser.OFPInstructionActions(
ofproto.OFPIT_APPLY_ACTIONS, actions)]
self.mod_flow(
datapath,
inst=inst,
table_id=table,
priority=priority,
match=match)
def add_flow_metadata_by_port_num(self, datapath, table, priority,
in_port, metadata,
metadata_mask, goto_table):
parser = datapath.ofproto_parser
match = parser.OFPMatch()
match.set_in_port(in_port)
goto_inst = parser.OFPInstructionGotoTable(goto_table)
write_metadata = parser.OFPInstructionWriteMetadata(metadata,
metadata_mask)
inst = [write_metadata, goto_inst]
self.mod_flow(
datapath,
inst=inst,
table_id=table,
priority=priority,
match=match)
def add_flow_normal(self, datapath, table, priority, match=None):
parser = datapath.ofproto_parser
ofproto = datapath.ofproto
actions = [
parser.OFPActionOutput(
ofproto.OFPP_NORMAL)]
inst = [datapath.ofproto_parser.OFPInstructionActions(
ofproto.OFPIT_APPLY_ACTIONS, actions)]
self.mod_flow(
datapath,
inst=inst,
table_id=table,
priority=priority,
match=match)
def add_flow_go_to_table2(self, datapath, table, priority,
goto_table_id, match=None):
inst = [datapath.ofproto_parser.OFPInstructionGotoTable(goto_table_id)]
self.mod_flow(datapath, inst=inst, table_id=table, priority=priority,
match=match)
def add_flow_goto_normal_on_ipv6(self, datapath, table, priority):
match = datapath.ofproto_parser.OFPMatch()
match.set_dl_type(ether.ETH_TYPE_IPV6)
self.add_flow_normal(datapath, table, priority, match)
def add_flow_goto_normal_on_broad(self, datapath, table, priority,
goto_table_id):
match = datapath.ofproto_parser.OFPMatch(eth_dst='ff:ff:ff:ff:ff:ff')
self.add_flow_normal(datapath, table, priority, match)
def add_flow_goto_normal_on_mcast(self, datapath, table, priority,
goto_table_id):
match = datapath.ofproto_parser.OFPMatch(eth_dst='01:00:00:00:00:00')
addint = haddr_to_bin('01:00:00:00:00:00')
match.set_dl_dst_masked(addint, addint)
self.add_flow_normal(datapath, table, priority, match)
def add_flow_go_to_table_on_arp(self, datapath, table, priority,
goto_table_id):
match = datapath.ofproto_parser.OFPMatch(eth_type=0x0806)
self.add_flow_go_to_table2(datapath, table, priority, goto_table_id,
match)
def add_flow_go_to_table(self, datapath, table, priority, goto_table_id):
parser = datapath.ofproto_parser
ofproto = datapath.ofproto
match = parser.OFPMatch()
actions = [parser.OFPInstructionGotoTable(goto_table_id)]
inst = [datapath.ofproto_parser.OFPInstructionActions(
ofproto.OFPIT_APPLY_ACTIONS, actions)]
mod = datapath.ofproto_parser.OFPFlowMod(
datapath=datapath, cookie=0, cookie_mask=0, table_id=table,
command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0,
priority=priority, buffer_id=ofproto.OFP_NO_BUFFER,
out_port=ofproto.OFPP_ANY,
out_group=ofproto.OFPG_ANY,
flags=0, match=match, instructions=inst)
datapath.send_msg(mod)
def _handle_remove_port(self, port_data):
"""Broadcast remove commands to all datapaths
:param port_data: port_data for the port that was removed
:type port_data: PortData
"""
owner_tenant = self._find_tenant_for_port_data(port_data)
if owner_tenant is not None:
owner_tenant.mac_to_port_data.pop(port_data.mac_address, None)
for unused_subnet in owner_tenant.unused_subnets:
self._remove_flow_local_subnet(unused_subnet)
del owner_tenant.subnets[unused_subnet.id]
cookie = CookieFilter.from_port_data(port_data).to_cookie()
for datapath in self.dp_list.values():
datapath = datapath.datapath
parser = datapath.ofproto_parser
ofproto = datapath.ofproto
match = parser.OFPMatch()
message = parser.OFPFlowMod(
datapath=datapath,
cookie=cookie,
cookie_mask=cookie,
table_id=L3ReactiveApp.L3_VROUTER_TABLE,
command=ofproto.OFPFC_DELETE,
priority=MEDIUM_PRIORITY_FLOW,
out_port=ofproto.OFPP_ANY,
out_group=ofproto.OFPG_ANY,
match=match,
)
datapath.send_msg(message)
@set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER)
def _port_status_handler(self, ev):
msg = ev.msg
reason = msg.reason
port_no = msg.desc.port_no
datapath = ev.msg.datapath
ofproto = msg.datapath.ofproto
if reason == ofproto.OFPPR_ADD:
LOG.info(_LI("port added %s"), port_no)
elif reason == ofproto.OFPPR_DELETE:
LOG.info(_LI("port deleted %s"), port_no)
elif reason == ofproto.OFPPR_MODIFY:
LOG.info(_LI("port modified %s"), port_no)
else:
LOG.info(_LI("Illeagal port state %(port_no)s %(reason)s")
% {'port_no': port_no, 'reason': reason})
# TODO(gampel) Currently we update all the agents on modification
LOG.info(_LI(" Updating flow table on agents got port update "))
switch = self.dp_list.get(datapath.id)
if switch:
self.send_port_desc_stats_request(datapath)
if reason == ofproto.OFPPR_DELETE:
for tenant in self._tenants.values():
port_data = tenant.find_port_data_by_local_name(
msg.desc.name)
if port_data is not None:
self._handle_remove_port(port_data)
def add_bootstrap_flows(self, datapath):
# Goto from main CLASSIFIER table
self.add_flow_go_to_table2(datapath, 0, 1, self.CLASSIFIER_TABLE)
#send L3 traffic unmatched to controller
self.add_flow_go_to_table2(datapath, self.CLASSIFIER_TABLE, 1,
self.L3_VROUTER_TABLE)
# send L3 traffic that is not east west to public network
self.add_flow_go_to_table2(datapath, self.L3_VROUTER_TABLE, 1,
self.L3_PUBLIC_TABLE)
# Update inner subnets and SNAT
self.bootstrap_network_classifiers()
#Goto from CLASSIFIER to ARP Table on ARP
self.add_flow_go_to_table_on_arp(
datapath,
self.CLASSIFIER_TABLE,
HIGH_PRIORITY_FLOW,
self.ARP_AND_BR_TABLE)
#Goto from CLASSIFIER to NORMAL on broadcast
self.add_flow_goto_normal_on_broad(
datapath,
self.CLASSIFIER_TABLE,
MEDIUM_PRIORITY_FLOW,
self.ARP_AND_BR_TABLE)
#Goto from CLASSIFIER to NORMAL on mcast
self.add_flow_goto_normal_on_mcast(
datapath,
self.CLASSIFIER_TABLE,
NORMAL_PRIORITY_FLOW,
self.ARP_AND_BR_TABLE)
#Goto from CLASSIFIER to NORMAL on IPV6 traffic
self.add_flow_goto_normal_on_ipv6(
datapath,
self.CLASSIFIER_TABLE,
NORMAL_PRIORITY_FLOW)
# Normal flow on arp table in low priority
self.add_flow_normal(datapath, self.ARP_AND_BR_TABLE, 1)
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
datapath = ev.msg.datapath
switch = self.dp_list.get(datapath.id)
if not switch:
self.dp_list[datapath.id] = AgentDatapath()
self.dp_list[datapath.id].datapath = datapath
# Normal flow with the lowest priority to send all traffic to NORMAL
# until the bootstrap is done
self.add_flow_normal(datapath, self.BASE_TABLE, 0)
self.send_port_desc_stats_request(datapath)
def send_port_desc_stats_request(self, datapath):
ofp_parser = datapath.ofproto_parser
req = ofp_parser.OFPPortDescStatsRequest(datapath, 0)
datapath.send_msg(req)
def append_port_data_to_ports(self, ports_list, port):
ports_list.append('port_no=%d hw_addr=%s name=%s config=0x%08x '
'state=0x%08x curr=0x%08x advertised=0x%08x '
'supported=0x%08x peer=0x%08x curr_speed=%d '
'max_speed=%d' %
(port.port_no, port.hw_addr,
port.name, port.config,
port.state, port.curr, port.advertised,
port.supported, port.peer, port.curr_speed,
port.max_speed))
@set_ev_cls(ofp_event.EventOFPPortDescStatsReply, MAIN_DISPATCHER)
def port_desc_stats_reply_handler(self, ev):
ports = []
datapath = ev.msg.datapath
switch = self.dp_list.get(datapath.id)
self.add_bootstrap_flows(datapath)
for port in ev.msg.body:
self.append_port_data_to_ports(ports, port)
if port.name.startswith('tap'):
self._port_desc_handler_dhcp_server_port(datapath, port)
elif port.name.startswith('qr'):
self._port_desc_handler_legacy_router_port(datapath, port)
elif port.name.startswith('qvo'):
self._port_desc_handler_vm_port(datapath, port)
elif "patch-tun" in port.name:
self._port_desc_handler_patch_tun_port(datapath, switch, port)
LOG.debug('OFPPortDescStatsReply received: %s', ports)
switch.local_ports = ports
def _port_desc_handler_patch_tun_port(self, datapath, switch, port):
"""Handle port description event for patch_tun
Handle port description event the vport connecting to the
tunnel bridge
:param datapath: The datapath to send through
:param switch: AgentDatapath object for this datapath
:type switch: AgentDatapath
:param port: The port to handle
"""
LOG.debug("Found br-tun patch port %s %s --> NORMAL path ",
port.name, port.hw_addr)
switch.patch_port_num = port.port_no
self.add_flow_normal_by_port_num(
datapath, 0, HIGH_PRIORITY_FLOW, port.port_no)
self._install_tunid_translation_to_mark(
datapath,
self.TUN_TRANSLATE_TABLE,
port.port_no,
HIGH_PRIORITY_FLOW)
def _port_desc_handler_dhcp_server_port(self, datapath, port):
"""Handle port description event for dhcp server vport
:param datapath: The datapath to send through
:param port: The port to handle
"""
LOG.debug(("Found DHCPD port %s using MAC %s "
"One machine install"),
port.name,
port.hw_addr)
self.add_flow_normal_by_port_num(
datapath, 0, HIGH_PRIORITY_FLOW, port.port_no)
def _port_desc_handler_legacy_router_port(self, datapath, port):
"""Handle port description event for the legacy router
the legacy router is used curently for SNAT,FIP and HA
:param datapath: The datapath to send through
:param port: The port to handle
"""
LOG.debug(("Found Legacy Router port %s using MAC %s "
"One machine setup"),
port.name,
port.hw_addr)
self.add_flow_normal_by_port_num(
datapath, 0, HIGH_PRIORITY_FLOW, port.port_no)
def _port_desc_handler_vm_port(self, datapath, port):
"""Handle port description event for VMS virtual port
:param datapath: The datapath to send through
:param port: The port to handle
"""
port_data, tenant_data = self._update_local_port_num(
port.name, port.port_no, datapath)
if not port_data or not tenant_data:
LOG.warning(_LW("No Port Data for port: <%s>"), port.name)
return
segmentation_id = port_data.segmentation_id
if segmentation_id != 0:
self.add_flow_metadata_by_port_num(datapath,
0,
HIGH_PRIORITY_FLOW,
port.port_no,
segmentation_id,
0xffff,
self.CLASSIFIER_TABLE)
for subnet_id in port_data.subnets:
subnet = tenant_data.subnets.get(subnet_id)
if not subnet:
continue
router_ports = tenant_data.get_routers_ports_by_subnet(
subnet)
self._install_arp_responders_for_routers_ports(datapath,
subnet, router_ports)
if subnet.is_ipv4():
cidr = subnet.cidr
self.add_flow_normal_local_subnet(
datapath,
self.CLASSIFIER_TABLE,
LOCAL_SUBNET_TRAFFIC_FLOW_PRIORITY,
cidr.network.format(),
str(cidr.prefixlen),
subnet.segmentation_id)
LOG.debug("Found VM/router port %s using MAC %s,"
" datapath: %d, port_no: %d, segmentation_id: %s",
port.name, port.hw_addr, datapath.id, port.port_no,
segmentation_id)
def _install_arp_responders_for_routers_ports(self, datapath,
subnet, router_ports):
for port in router_ports:
self.add_subnet_binding(datapath,
subnet,
port)
def send_features_request(self, datapath):
ofp_parser = datapath.ofproto_parser
req = ofp_parser.OFPFeaturesRequest(datapath)
datapath.send_msg(req)
def _update_local_port_num(self, port_name, port_num, datapath):
dpid = datapath.id
port_id_from_name = port_name[3:]
switch_port_desc_dict = self.dp_list[dpid].switch_port_desc_dict
switch_port_desc_dict[port_id_from_name] = {}
switch_port_desc = switch_port_desc_dict[port_id_from_name]
switch_port_desc['local_port_num'] = port_num
switch_port_desc['local_dpid_switch'] = dpid
switch_port_desc['datapath'] = datapath
# If we already received port sync, link between the structures
for tenantid in self._tenants:
tenant = self._tenants[tenantid]
for mac, port_data in tenant.mac_to_port_data.items():
port_id = port_data.id
sub_str_port_id = str(port_id[0:11])
if sub_str_port_id == port_id_from_name:
port_data['switch_port_desc'] = switch_port_desc
return port_data, tenant
# This can happen if we received port description from OVS but didn't
# yet received port_sync from the L3 service
LOG.debug("Port data not found %s num <%d> dpid <%d>", port_name,
port_num, dpid)
return None, None
def get_ip_from_interface(self, interface):
for fixed_ip in interface['fixed_ips']:
if "ip_address" in fixed_ip:
return fixed_ip['ip_address']
def send_flow_stats_request(self, datapath, table=None):
ofp = datapath.ofproto
ofp_parser = datapath.ofproto_parser
if table is None:
table = ofp.OFPTT_ALL
cookie = cookie_mask = 0
match = ofp_parser.OFPMatch()
req = ofp_parser.OFPFlowStatsRequest(datapath, 0,
table,
ofp.OFPP_ANY, ofp.OFPG_ANY,
cookie, cookie_mask,
match)
datapath.send_msg(req)
def _handle_icmp(self, datapath, pkt, in_port):
pkt_ethernet = pkt.get_protocol(ethernet.ethernet)
pkt_ipv4 = pkt.get_protocol(ipv4.ipv4)
pkt_icmp = pkt.get_protocol(icmp.icmp)
if pkt_icmp.type != icmp.ICMP_ECHO_REQUEST:
return
pkt = packet.Packet()
pkt.add_protocol(ethernet.ethernet(ethertype=ether.ETH_TYPE_IP,
dst=pkt_ethernet.src,
src=pkt_ethernet.dst))
pkt.add_protocol(ipv4.ipv4(dst=pkt_ipv4.src,
src=pkt_ipv4.dst,
proto=pkt_ipv4.proto))
pkt.add_protocol(icmp.icmp(type_=icmp.ICMP_ECHO_REPLY,
code=icmp.ICMP_ECHO_REPLY_CODE,
csum=0,
data=pkt_icmp.data))
self._send_packet(datapath, in_port.local_port_number, pkt)
LOG.debug("Sending ping echo -> ip %s ", pkt_ipv4.src)
def check_direct_routing(self, tenant, from_subnet_id, to_subnet_id):
return
def _get_dp_ip_as_int(self, datapath):
try:
return int(netaddr.IPAddress(datapath.address[0], version=4))
except Exception:
LOG.warning(_LW("Invalid remote IP: %s"), datapath.address)
return
def _get_match_vrouter_arp_responder(self, datapath, segmentation_id,
interface_ip):
parser = datapath.ofproto_parser
match = parser.OFPMatch()
match.set_dl_type(ether.ETH_TYPE_ARP)
match.set_arp_tpa(ipv4_text_to_int(str(interface_ip)))
match.set_arp_opcode(arp.ARP_REQUEST)
match.set_metadata(segmentation_id)
return match
def _get_inst_vrouter_arp_responder(self, datapath,
mac_address, interface_ip):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
actions = [parser.OFPActionSetField(arp_op=arp.ARP_REPLY),
parser.NXActionRegMove(src_field='arp_sha',
dst_field='arp_tha',
n_bits=48),
parser.NXActionRegMove(src_field='arp_spa',
dst_field='arp_tpa',
n_bits=32),
parser.OFPActionSetField(eth_src=mac_address),
parser.OFPActionSetField(arp_sha=mac_address),
parser.OFPActionSetField(arp_spa=interface_ip),
parser.OFPActionOutput(ofproto.OFPP_IN_PORT, 0)]
instructions = [parser.OFPInstructionActions(
ofproto.OFPIT_APPLY_ACTIONS, actions)]
return instructions
def add_subnet_binding(self, datapath, subnet, interface):
if not subnet.is_ipv4():
LOG.info(_LI("No support for IPV6"))
return
if subnet.segmentation_id == 0:
LOG.error(_LE("Segmentation id == 0 for subnet = %s"), subnet.id)
return
self._add_vrouter_arp_responder(
datapath,
subnet.segmentation_id,
interface['mac_address'],
self.get_ip_from_interface(interface))
def subnet_added_binding_cast(self, subnet, interface):
LOG.debug("adding %(segmentation_id)s, %(mac_address)s, "
"%(interface_ip)s",
{'segmentation_id': subnet.segmentation_id,
'mac_address': interface['mac_address'],
'interface_ip': self.get_ip_from_interface(interface)})
for switch in self.dp_list.values():
self.add_subnet_binding(switch.datapath, subnet, interface)
def _add_vrouter_arp_responder(self, datapath, segmentation_id,
mac_address, interface_ip):
match = self._get_match_vrouter_arp_responder(
datapath, segmentation_id, interface_ip)
instructions = self._get_inst_vrouter_arp_responder(
datapath, mac_address, interface_ip)
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
msg = parser.OFPFlowMod(datapath=datapath,
table_id=L3ReactiveApp.ARP_AND_BR_TABLE,
command=ofproto.OFPFC_ADD,
priority=MEDIUM_PRIORITY_FLOW,
match=match, instructions=instructions,
flags=ofproto.OFPFF_SEND_FLOW_REM)
datapath.send_msg(msg)
def _remove_vrouter_arp_responder_cast(self, segmentation_id, mac_address,
interface_ip):
LOG.debug("removing %(segmentation_id)s, %(mac_address)s, "
"%(interface_ip)s",
{'segmentation_id': segmentation_id,
'mac_address': mac_address,
'interface_ip': interface_ip})
for switch in self.dp_list.values():
datapath = switch.datapath
self._remove_vrouter_arp_responder(
datapath,
segmentation_id,
mac_address,
interface_ip)
def _remove_vrouter_arp_responder(self,
datapath,
segmentation_id,
mac_address,
interface_ip):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
match = self._get_match_vrouter_arp_responder(
datapath, segmentation_id, interface_ip)
msg = parser.OFPFlowMod(datapath=datapath,
cookie=0,
cookie_mask=0,
table_id=L3ReactiveApp.ARP_AND_BR_TABLE,
command=ofproto.OFPFC_DELETE,
priority=MEDIUM_PRIORITY_FLOW,
out_port=ofproto.OFPP_ANY,
out_group=ofproto.OFPG_ANY,
match=match)
datapath.send_msg(msg)
def add_snat_binding(self, subnet_id, sn_port):
snat_binding = SnatBinding(subnet_id, sn_port)
self.snat_bindings[subnet_id] = snat_binding
# Now find the segmentation ID for this subnet if it exists
for tenantid in self._tenants:
tenant = self._tenants[tenantid]
for subnet in tenant.subnets.values():
if subnet.id == subnet_id:
self.bootstrap_snat_subnet_flow(snat_binding, subnet)
def remove_snat_binding(self, subnet_id):
snat_binding = self.snat_bindings.get(subnet_id)
if snat_binding is None:
LOG.debug("subnet id %s not in snat_bindings", subnet_id)
return
for tenant in self._tenants.values():
for subnet in tenant.subnets.values():
if subnet.id == subnet_id:
self.remove_snat_binding_flows(snat_binding, subnet)
del self.snat_bindings[subnet_id]
def bootstrap_network_classifiers(self, subnet=None):
if subnet is None:
self.bootstrap_inner_subnets_connection()
self.bootstrap_snat_flows()
else:
self.bootstrap_inner_subnets_connection_for_subnet(subnet)
snat_binding = self.snat_bindings.get(subnet.id)
if snat_binding is not None:
self.bootstrap_snat_subnet_flow(snat_binding, subnet)
def bootstrap_snat_subnet_flow(self, snat_binding, subnet):
# TODO(gsagie) only iterate on DP's that implement the subnet
for dp in self.dp_list.values():
self.add_flow_snat_redirect(dp.datapath, snat_binding, subnet)
def bootstrap_snat_flows(self):
for tenant in self._tenants.values():
for subnet in tenant.subnets.values():
snat_binding = self.snat_bindings.get(subnet.id)
if snat_binding is not None:
for dp in self.dp_list.values():
self.add_flow_snat_redirect(dp.datapath,
snat_binding, subnet)
def remove_snat_binding_flows(self, snat_binding, subnet):
# TODO(gsagie) only iterate on DP's that implement the subnet
for dp in self.dp_list.values():
self.remove_flow_snat_redirect(dp.datapath, snat_binding, subnet)
def remove_flow_snat_redirect(self, datapath, snat_binding, subnet):
if subnet.segmentation_id == 0:
LOG.error(_LE("Segmentation id == 0 for subnet = %s"), subnet.id)
return
parser = datapath.ofproto_parser
ofproto = datapath.ofproto
match = parser.OFPMatch()
match.set_dl_type(ether.ETH_TYPE_IP)
match.set_metadata(subnet.segmentation_id)
msg = parser.OFPFlowMod(datapath=datapath,
cookie=0,
cookie_mask=0,
table_id=self.L3_PUBLIC_TABLE,
command=ofproto.OFPFC_DELETE,
priority=SNAT_RULES_PRIORITY_FLOW,
out_port=ofproto.OFPP_ANY,
out_group=ofproto.OFPG_ANY,
match=match)
datapath.send_msg(msg)
def add_flow_snat_redirect(self, datapath, snat_binding, subnet):
if not subnet.is_ipv4():
LOG.info(_LI("No support for IPV6"))
return
if subnet.segmentation_id == 0:
LOG.warning(_LW("Segmentation id == 0 for subnet = %s"), subnet.id)
return
parser = datapath.ofproto_parser
ofproto = datapath.ofproto
match = parser.OFPMatch()
match.set_dl_type(ether.ETH_TYPE_IP)
match.set_metadata(subnet.segmentation_id)
eth_dst_mac = snat_binding.sn_port['mac_address']
actions = [
parser.OFPActionDecNwTtl(),
parser.OFPActionSetField(eth_dst=eth_dst_mac),
parser.OFPActionOutput(ofproto.OFPP_NORMAL),
]
inst = [datapath.ofproto_parser.OFPInstructionActions(
ofproto.OFPIT_APPLY_ACTIONS, actions)]
self.mod_flow(
datapath,
inst=inst,
table_id=self.L3_PUBLIC_TABLE,
priority=SNAT_RULES_PRIORITY_FLOW,
match=match)
def bootstrap_inner_subnets_connection_for_subnet(self, subnet):
# First configure all flows from the subnet to all
# the other possible subnets
for dp in self.dp_list.values():
self.bootstrap_inner_subnet_flows(dp.datapath, subnet)
# For each other subnet, configure the opposite direction
for tenant in self._tenants.values():
for from_subnet in tenant.subnets.values():
if (from_subnet.segmentation_id !=
subnet.segmentation_id):
for dp in self.dp_list.values():
self.add_flow_inner_subnet(dp.datapath,
from_subnet, subnet)
def bootstrap_inner_subnets_connection(self):
for tenant in self._tenants.values():
for subnet in tenant.subnets.values():
for dp in self.dp_list.values():
self.bootstrap_inner_subnet_flows(dp.datapath,
subnet)
def bootstrap_inner_subnet_flows(self, datapath, from_subnet):
for tenant in self._tenants.values():
for to_subnet in tenant.subnets.values():
if (to_subnet.segmentation_id !=
from_subnet.segmentation_id):
self.add_flow_inner_subnet(datapath,
from_subnet, to_subnet)
def add_flow_inner_subnet(self, datapath, from_subnet, to_subnet):
parser = datapath.ofproto_parser
ofproto = datapath.ofproto
if not (from_subnet.is_ipv4() and to_subnet.is_ipv4()):
LOG.info(_LI("No support for IPV6"))
return
match = parser.OFPMatch()
match.set_dl_type(ether.ETH_TYPE_IP)
match.set_metadata(from_subnet.segmentation_id)
match.set_ipv4_dst_masked(to_subnet.cidr.network.value,
to_subnet.cidr.netmask.value)
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
ofproto.OFPCML_NO_BUFFER)]
inst = [datapath.ofproto_parser.OFPInstructionActions(
ofproto.OFPIT_APPLY_ACTIONS, actions)]
self.mod_flow(
datapath,
inst=inst,
table_id=self.L3_VROUTER_TABLE,
priority=EAST_WEST_TRAFFIC_TO_CONTROLLER_FLOW_PRIORITY,
match=match)
def _install_tunid_translation_to_mark(self, datapath, table_id,
port, priority=0):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
match = parser.OFPMatch()
match.set_dl_type(ether.ETH_TYPE_IP)
actions = [parser.OFPActionOutput(port=port)]
instructions = [parser.OFPInstructionActions(
ofproto.OFPIT_APPLY_ACTIONS, actions)]
self.mod_flow(
datapath,
inst=instructions,
table_id=table_id,
priority=priority,
match=match)
# Base static
def ipv4_apply_mask(address, prefix_len, err_msg=None):
# import itertools
assert isinstance(address, str)
address_int = ipv4_text_to_int(address)
return ipv4_int_to_text(address_int & mask_ntob(prefix_len, err_msg))
def ipv4_text_to_int(ip_text):
if ip_text == 0:
return ip_text
assert isinstance(ip_text, str)
return struct.unpack('!I', addrconv.ipv4.text_to_bin(ip_text))[0]
def ipv4_int_to_text(ip_int):
assert isinstance(ip_int, (int, long))
return addrconv.ipv4.bin_to_text(struct.pack('!I', ip_int))
def mask_ntob(mask, err_msg=None):
try:
return (UINT32_MAX << (32 - mask)) & UINT32_MAX
except ValueError:
msg = 'illegal netmask'
if err_msg is not None:
msg = '%s %s' % (err_msg, msg)
raise ValueError(msg)