blob: d3ec17de576158b4be02f34f98a827eaf3e87906 [file] [log] [blame]
Chip Boling8e042f62019-02-12 16:14:34 -06001# 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
15from enum import IntEnum
16from pyvoltha.common.openflow.utils import *
17from pyvoltha.protos.openflow_13_pb2 import OFPP_MAX
18
19log = 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
30class 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