Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame^] | 1 | # Copyright 2017-present Adtran, Inc. |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
| 14 | |
| 15 | from enum import IntEnum |
| 16 | from pyvoltha.common.openflow.utils import * |
| 17 | from pyvoltha.protos.openflow_13_pb2 import OFPP_MAX |
| 18 | |
| 19 | log = structlog.get_logger() |
| 20 | |
| 21 | # IP Protocol numbers |
| 22 | _supported_ip_protocols = [ |
| 23 | 1, # ICMP |
| 24 | 2, # IGMP |
| 25 | 6, # TCP |
| 26 | 17, # UDP |
| 27 | ] |
| 28 | |
| 29 | |
| 30 | class FlowEntry(object): |
| 31 | """ |
| 32 | Provide a class that wraps the flow rule and also provides state/status for a FlowEntry. |
| 33 | |
| 34 | When a new flow is sent, it is first decoded to check for any potential errors. If None are |
| 35 | found, the entry is created and it is analyzed to see if it can be combined to with any other flows |
| 36 | to create or modify an existing EVC. |
| 37 | |
| 38 | Note: Since only E-LINE is supported, modification of an existing EVC is not performed. |
| 39 | """ |
| 40 | class FlowDirection(IntEnum): |
| 41 | UPSTREAM = 0 # UNI port to ANI Port |
| 42 | DOWNSTREAM = 1 # ANI port to UNI Port |
| 43 | ANI = 2 # ANI port to ANI Port |
| 44 | UNI = 3 # UNI port to UNI Port |
| 45 | OTHER = 4 # Unable to determine |
| 46 | |
| 47 | _flow_dir_map = { |
| 48 | (FlowDirection.UNI, FlowDirection.ANI): FlowDirection.UPSTREAM, |
| 49 | (FlowDirection.ANI, FlowDirection.UNI): FlowDirection.DOWNSTREAM |
| 50 | } |
| 51 | |
| 52 | upstream_flow_types = {FlowDirection.UPSTREAM} |
| 53 | downstream_flow_types = {FlowDirection.DOWNSTREAM} |
| 54 | |
| 55 | # Well known EtherTypes |
| 56 | class EtherType(IntEnum): |
| 57 | EAPOL = 0x888E |
| 58 | IPv4 = 0x0800 |
| 59 | IPv6 = 0x86DD |
| 60 | ARP = 0x0806 |
| 61 | LLDP = 0x88CC |
| 62 | |
| 63 | # Well known IP Protocols |
| 64 | class IpProtocol(IntEnum): |
| 65 | IGMP = 2 |
| 66 | UDP = 17 |
| 67 | |
| 68 | def __init__(self, flow, handler): |
| 69 | self._handler = handler |
| 70 | self.flow_id = flow.id |
| 71 | self._flow_direction = FlowEntry.FlowDirection.OTHER |
| 72 | self._is_multicast = False |
| 73 | self.tech_profile_id = None |
| 74 | |
| 75 | # Selection properties |
| 76 | self.in_port = None |
| 77 | self.vlan_vid = None |
| 78 | self.vlan_pcp = None |
| 79 | self.etype = None |
| 80 | self.proto = None |
| 81 | self.ipv4_dst = None |
| 82 | self.udp_dst = None # UDP Port # |
| 83 | self.udp_src = None # UDP Port # |
| 84 | self.inner_vid = None |
| 85 | |
| 86 | # Actions |
| 87 | self.out_port = None |
| 88 | self.pop_vlan = False |
| 89 | self.push_vlan_tpid = None |
| 90 | self.set_vlan_vid = None |
| 91 | self._name = self.create_flow_name() |
| 92 | |
| 93 | def __str__(self): |
| 94 | return 'flow_entry: {}, in: {}, out: {}, vid: {}, inner:{}, eth: {}, IP: {}'.format( |
| 95 | self.name, self.in_port, self.out_port, self.vlan_vid, self.inner_vid, |
| 96 | self.etype, self.proto) |
| 97 | |
| 98 | def __repr__(self): |
| 99 | return str(self) |
| 100 | |
| 101 | @property |
| 102 | def name(self): |
| 103 | return self._name # TODO: Is a name really needed in production? |
| 104 | |
| 105 | def create_flow_name(self): |
| 106 | return 'flow-{}-{}'.format(self.device_id, self.flow_id) |
| 107 | |
| 108 | @property |
| 109 | def handler(self): |
| 110 | return self._handler |
| 111 | |
| 112 | @property |
| 113 | def device_id(self): |
| 114 | return self.handler.device_id |
| 115 | |
| 116 | @property |
| 117 | def flow_direction(self): |
| 118 | return self._flow_direction |
| 119 | |
| 120 | @property |
| 121 | def is_multicast_flow(self): |
| 122 | return self._is_multicast |
| 123 | |
| 124 | @staticmethod |
| 125 | def create(flow, handler): |
| 126 | """ |
| 127 | Create the appropriate FlowEntry wrapper for the flow. This method returns a two |
| 128 | results. |
| 129 | |
| 130 | The first result is the flow entry that was created. This could be a match to an |
| 131 | existing flow since it is a bulk update. None is returned only if no match to |
| 132 | an existing entry is found and decode failed (unsupported field) |
| 133 | |
| 134 | :param flow: (Flow) Flow entry passed to VOLTHA adapter |
| 135 | :param handler: (DeviceHandler) handler for the device |
| 136 | :return: (FlowEntry) Created flow entry, None on decode failure |
| 137 | """ |
| 138 | # Exit early if it already exists |
| 139 | try: |
| 140 | flow_entry = FlowEntry(flow, handler) |
| 141 | |
| 142 | if not flow_entry.decode(flow): |
| 143 | return None |
| 144 | |
| 145 | # TODO: Do we want to do the OMCI here ? |
| 146 | |
| 147 | return flow_entry |
| 148 | |
| 149 | except Exception as e: |
| 150 | log.exception('flow-entry-processing', e=e) |
| 151 | return None |
| 152 | |
| 153 | def decode(self, flow): |
| 154 | """ |
| 155 | Examine flow rules and extract appropriate settings |
| 156 | """ |
| 157 | log.debug('start-decode') |
| 158 | status = self._decode_traffic_selector(flow) and self._decode_traffic_treatment(flow) |
| 159 | |
| 160 | if status: |
| 161 | ani_ports = [pon.port_number for pon in self._handler.pon_ports] |
| 162 | uni_ports = [uni.port_number for uni in self._handler.uni_ports] |
| 163 | |
| 164 | # Determine direction of the flow |
| 165 | def port_type(port_number): |
| 166 | if port_number in ani_ports: |
| 167 | return FlowEntry.FlowDirection.ANI |
| 168 | |
| 169 | elif port_number in uni_ports: |
| 170 | return FlowEntry.FlowDirection.UNI |
| 171 | |
| 172 | return FlowEntry.FlowDirection.OTHER |
| 173 | |
| 174 | self._flow_direction = FlowEntry._flow_dir_map.get((port_type(self.in_port), |
| 175 | port_type(self.out_port)), |
| 176 | FlowEntry.FlowDirection.OTHER) |
| 177 | return status |
| 178 | |
| 179 | def _decode_traffic_selector(self, flow): |
| 180 | """ |
| 181 | Extract traffic selection settings |
| 182 | """ |
| 183 | self.in_port = get_in_port(flow) |
| 184 | |
| 185 | if self.in_port > OFPP_MAX: |
| 186 | log.warn('logical-input-ports-not-supported') |
| 187 | return False |
| 188 | |
| 189 | for field in get_ofb_fields(flow): |
| 190 | if field.type == IN_PORT: |
| 191 | assert self.in_port == field.port, 'Multiple Input Ports found in flow rule' |
| 192 | |
| 193 | elif field.type == VLAN_VID: |
| 194 | self.vlan_vid = field.vlan_vid & 0xfff |
| 195 | log.debug('*** field.type == VLAN_VID', value=field.vlan_vid, vlan_id=self.vlan_vid) |
| 196 | self._is_multicast = False # TODO: self.vlan_id in self._handler.multicast_vlans |
| 197 | |
| 198 | elif field.type == VLAN_PCP: |
| 199 | log.debug('*** field.type == VLAN_PCP', value=field.vlan_pcp) |
| 200 | self.vlan_pcp = field.vlan_pcp |
| 201 | |
| 202 | elif field.type == ETH_TYPE: |
| 203 | log.debug('*** field.type == ETH_TYPE', value=field.eth_type) |
| 204 | self.etype = field.eth_type |
| 205 | |
| 206 | elif field.type == IP_PROTO: |
| 207 | log.debug('*** field.type == IP_PROTO', value=field.ip_proto) |
| 208 | self.proto = field.ip_proto |
| 209 | |
| 210 | if self.proto not in _supported_ip_protocols: |
| 211 | log.error('Unsupported IP Protocol', ip_proto=self.proto) |
| 212 | return False |
| 213 | |
| 214 | elif field.type == IPV4_DST: |
| 215 | log.debug('*** field.type == IPV4_DST', value=field.ipv4_dst) |
| 216 | self.ipv4_dst = field.ipv4_dst |
| 217 | |
| 218 | elif field.type == UDP_DST: |
| 219 | log.debug('*** field.type == UDP_DST', value=field.udp_dst) |
| 220 | self.udp_dst = field.udp_dst |
| 221 | |
| 222 | elif field.type == UDP_SRC: |
| 223 | log.debug('*** field.type == UDP_SRC', value=field.udp_src) |
| 224 | self.udp_src = field.udp_src |
| 225 | |
| 226 | elif field.type == METADATA: |
| 227 | log.debug('*** field.type == METADATA', value=field.table_metadata) |
| 228 | self.inner_vid = field.table_metadata |
| 229 | log.debug('*** field.type == METADATA', value=field.table_metadata, |
| 230 | inner_vid=self.inner_vid) |
| 231 | else: |
| 232 | log.warn('unsupported-selection-field', type=field.type) |
| 233 | self._status_message = 'Unsupported field.type={}'.format(field.type) |
| 234 | return False |
| 235 | |
| 236 | return True |
| 237 | |
| 238 | def _decode_traffic_treatment(self, flow): |
| 239 | self.out_port = get_out_port(flow) |
| 240 | |
| 241 | if self.out_port > OFPP_MAX: |
| 242 | log.warn('logical-output-ports-not-supported') |
| 243 | return False |
| 244 | |
| 245 | for act in get_actions(flow): |
| 246 | if act.type == OUTPUT: |
| 247 | assert self.out_port == act.output.port, 'Multiple Output Ports found in flow rule' |
| 248 | pass # Handled earlier |
| 249 | |
| 250 | elif act.type == POP_VLAN: |
| 251 | log.debug('*** action.type == POP_VLAN') |
| 252 | self.pop_vlan = True |
| 253 | |
| 254 | elif act.type == PUSH_VLAN: |
| 255 | log.debug('*** action.type == PUSH_VLAN', value=act.push) |
| 256 | tpid = act.push.ethertype |
| 257 | self.push_tpid = tpid |
| 258 | assert tpid == 0x8100, 'Only TPID 0x8100 is currently supported' |
| 259 | |
| 260 | elif act.type == SET_FIELD: |
| 261 | log.debug('*** action.type == SET_FIELD', value=act.set_field.field) |
| 262 | assert (act.set_field.field.oxm_class == ofp.OFPXMC_OPENFLOW_BASIC) |
| 263 | field = act.set_field.field.ofb_field |
| 264 | if field.type == VLAN_VID: |
| 265 | self.set_vlan_vid = field.vlan_vid & 0xfff |
| 266 | |
| 267 | else: |
| 268 | log.warn('unsupported-action', action=act) |
| 269 | self._status_message = 'Unsupported action.type={}'.format(act.type) |
| 270 | return False |
| 271 | |
| 272 | return True |