blob: d3ec17de576158b4be02f34f98a827eaf3e87906 [file] [log] [blame]
# Copyright 2017-present Adtran, Inc.
#
# 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.
from enum import IntEnum
from pyvoltha.common.openflow.utils import *
from pyvoltha.protos.openflow_13_pb2 import OFPP_MAX
log = structlog.get_logger()
# IP Protocol numbers
_supported_ip_protocols = [
1, # ICMP
2, # IGMP
6, # TCP
17, # UDP
]
class FlowEntry(object):
"""
Provide a class that wraps the flow rule and also provides state/status for a FlowEntry.
When a new flow is sent, it is first decoded to check for any potential errors. If None are
found, the entry is created and it is analyzed to see if it can be combined to with any other flows
to create or modify an existing EVC.
Note: Since only E-LINE is supported, modification of an existing EVC is not performed.
"""
class FlowDirection(IntEnum):
UPSTREAM = 0 # UNI port to ANI Port
DOWNSTREAM = 1 # ANI port to UNI Port
ANI = 2 # ANI port to ANI Port
UNI = 3 # UNI port to UNI Port
OTHER = 4 # Unable to determine
_flow_dir_map = {
(FlowDirection.UNI, FlowDirection.ANI): FlowDirection.UPSTREAM,
(FlowDirection.ANI, FlowDirection.UNI): FlowDirection.DOWNSTREAM
}
upstream_flow_types = {FlowDirection.UPSTREAM}
downstream_flow_types = {FlowDirection.DOWNSTREAM}
# Well known EtherTypes
class EtherType(IntEnum):
EAPOL = 0x888E
IPv4 = 0x0800
IPv6 = 0x86DD
ARP = 0x0806
LLDP = 0x88CC
# Well known IP Protocols
class IpProtocol(IntEnum):
IGMP = 2
UDP = 17
def __init__(self, flow, handler):
self._handler = handler
self.flow_id = flow.id
self._flow_direction = FlowEntry.FlowDirection.OTHER
self._is_multicast = False
self.tech_profile_id = None
# Selection properties
self.in_port = None
self.vlan_vid = None
self.vlan_pcp = None
self.etype = None
self.proto = None
self.ipv4_dst = None
self.udp_dst = None # UDP Port #
self.udp_src = None # UDP Port #
self.inner_vid = None
# Actions
self.out_port = None
self.pop_vlan = False
self.push_vlan_tpid = None
self.set_vlan_vid = None
self._name = self.create_flow_name()
def __str__(self):
return 'flow_entry: {}, in: {}, out: {}, vid: {}, inner:{}, eth: {}, IP: {}'.format(
self.name, self.in_port, self.out_port, self.vlan_vid, self.inner_vid,
self.etype, self.proto)
def __repr__(self):
return str(self)
@property
def name(self):
return self._name # TODO: Is a name really needed in production?
def create_flow_name(self):
return 'flow-{}-{}'.format(self.device_id, self.flow_id)
@property
def handler(self):
return self._handler
@property
def device_id(self):
return self.handler.device_id
@property
def flow_direction(self):
return self._flow_direction
@property
def is_multicast_flow(self):
return self._is_multicast
@staticmethod
def create(flow, handler):
"""
Create the appropriate FlowEntry wrapper for the flow. This method returns a two
results.
The first result is the flow entry that was created. This could be a match to an
existing flow since it is a bulk update. None is returned only if no match to
an existing entry is found and decode failed (unsupported field)
:param flow: (Flow) Flow entry passed to VOLTHA adapter
:param handler: (DeviceHandler) handler for the device
:return: (FlowEntry) Created flow entry, None on decode failure
"""
# Exit early if it already exists
try:
flow_entry = FlowEntry(flow, handler)
if not flow_entry.decode(flow):
return None
# TODO: Do we want to do the OMCI here ?
return flow_entry
except Exception as e:
log.exception('flow-entry-processing', e=e)
return None
def decode(self, flow):
"""
Examine flow rules and extract appropriate settings
"""
log.debug('start-decode')
status = self._decode_traffic_selector(flow) and self._decode_traffic_treatment(flow)
if status:
ani_ports = [pon.port_number for pon in self._handler.pon_ports]
uni_ports = [uni.port_number for uni in self._handler.uni_ports]
# Determine direction of the flow
def port_type(port_number):
if port_number in ani_ports:
return FlowEntry.FlowDirection.ANI
elif port_number in uni_ports:
return FlowEntry.FlowDirection.UNI
return FlowEntry.FlowDirection.OTHER
self._flow_direction = FlowEntry._flow_dir_map.get((port_type(self.in_port),
port_type(self.out_port)),
FlowEntry.FlowDirection.OTHER)
return status
def _decode_traffic_selector(self, flow):
"""
Extract traffic selection settings
"""
self.in_port = get_in_port(flow)
if self.in_port > OFPP_MAX:
log.warn('logical-input-ports-not-supported')
return False
for field in get_ofb_fields(flow):
if field.type == IN_PORT:
assert self.in_port == field.port, 'Multiple Input Ports found in flow rule'
elif field.type == VLAN_VID:
self.vlan_vid = field.vlan_vid & 0xfff
log.debug('*** field.type == VLAN_VID', value=field.vlan_vid, vlan_id=self.vlan_vid)
self._is_multicast = False # TODO: self.vlan_id in self._handler.multicast_vlans
elif field.type == VLAN_PCP:
log.debug('*** field.type == VLAN_PCP', value=field.vlan_pcp)
self.vlan_pcp = field.vlan_pcp
elif field.type == ETH_TYPE:
log.debug('*** field.type == ETH_TYPE', value=field.eth_type)
self.etype = field.eth_type
elif field.type == IP_PROTO:
log.debug('*** field.type == IP_PROTO', value=field.ip_proto)
self.proto = field.ip_proto
if self.proto not in _supported_ip_protocols:
log.error('Unsupported IP Protocol', ip_proto=self.proto)
return False
elif field.type == IPV4_DST:
log.debug('*** field.type == IPV4_DST', value=field.ipv4_dst)
self.ipv4_dst = field.ipv4_dst
elif field.type == UDP_DST:
log.debug('*** field.type == UDP_DST', value=field.udp_dst)
self.udp_dst = field.udp_dst
elif field.type == UDP_SRC:
log.debug('*** field.type == UDP_SRC', value=field.udp_src)
self.udp_src = field.udp_src
elif field.type == METADATA:
log.debug('*** field.type == METADATA', value=field.table_metadata)
self.inner_vid = field.table_metadata
log.debug('*** field.type == METADATA', value=field.table_metadata,
inner_vid=self.inner_vid)
else:
log.warn('unsupported-selection-field', type=field.type)
self._status_message = 'Unsupported field.type={}'.format(field.type)
return False
return True
def _decode_traffic_treatment(self, flow):
self.out_port = get_out_port(flow)
if self.out_port > OFPP_MAX:
log.warn('logical-output-ports-not-supported')
return False
for act in get_actions(flow):
if act.type == OUTPUT:
assert self.out_port == act.output.port, 'Multiple Output Ports found in flow rule'
pass # Handled earlier
elif act.type == POP_VLAN:
log.debug('*** action.type == POP_VLAN')
self.pop_vlan = True
elif act.type == PUSH_VLAN:
log.debug('*** action.type == PUSH_VLAN', value=act.push)
tpid = act.push.ethertype
self.push_tpid = tpid
assert tpid == 0x8100, 'Only TPID 0x8100 is currently supported'
elif act.type == SET_FIELD:
log.debug('*** action.type == SET_FIELD', value=act.set_field.field)
assert (act.set_field.field.oxm_class == ofp.OFPXMC_OPENFLOW_BASIC)
field = act.set_field.field.ofb_field
if field.type == VLAN_VID:
self.set_vlan_vid = field.vlan_vid & 0xfff
else:
log.warn('unsupported-action', action=act)
self._status_message = 'Unsupported action.type={}'.format(act.type)
return False
return True