blob: ec11d0a27b3f8f9e9b535410274ea6ca0748e557 [file] [log] [blame]
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -07001#
Zsolt Haraszti3eb27a52017-01-03 21:56:48 -08002# Copyright 2017 the original author or authors.
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -07003#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16import structlog
Zsolt Haraszti2bdb6b32016-11-03 16:56:17 -070017from twisted.internet.defer import inlineCallbacks, returnValue
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -070018
19import loxi.of13 as ofp
20from converter import to_loxi, pb2dict, to_grpc
21
22log = structlog.get_logger()
23
24
25class OpenFlowProtocolError(Exception): pass
26
27
28class OpenFlowProtocolHandler(object):
29
sathishgbbb90992017-06-23 09:52:29 +053030 ofp_version = [4] # OFAgent supported versions
31
Girish Gowdrue20da0e2019-01-28 20:22:05 -080032 MAX_METER_IDS = 4294967295
33 MAX_METER_BANDS = 255
34 MAX_METER_COLORS = 255
35
Zsolt Haraszticd22adc2016-10-25 00:13:06 -070036 def __init__(self, datapath_id, device_id, agent, cxn, rpc):
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -070037 """
38 The upper half of the OpenFlow protocol, focusing on message
39 exchanges.
40 :param agent: Reference to the Agent() instance, can be used to
41 indicate critical errors to break the connection.
42 :param cxn: The lower level message serdes part of the OF protocol.
43 :param rpc: The application level stub on which RPC calls
44 are made as result of processing incoming OpenFlow request messages.
45 """
46 self.datapath_id = datapath_id
Zsolt Haraszticd22adc2016-10-25 00:13:06 -070047 self.device_id = device_id
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -070048 self.agent = agent
49 self.cxn = cxn
50 self.rpc = rpc
sgovindacc736782017-05-02 20:06:37 +053051 self.role = None
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -070052
Shad Ansari9109a282019-05-03 01:24:57 -070053 self.count_pkt_in = 0
54 self.count_pkt_out = 0
55
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -070056 @inlineCallbacks
Zsolt Haraszti2bdb6b32016-11-03 16:56:17 -070057 def start(self):
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -070058 """A new call is made after a fresh reconnect"""
59
Zsolt Haraszti2bdb6b32016-11-03 16:56:17 -070060 log.debug('starting')
61
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -070062 try:
sathishgbbb90992017-06-23 09:52:29 +053063 support = False
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -070064 # send initial hello message
sathishgbbb90992017-06-23 09:52:29 +053065 self.cxn.send(ofp.message.hello(elements=[ofp.common.hello_elem_versionbitmap(
66 bitmaps = [ofp.common.hello_elem_bitmap(self.ofp_version)])]))
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -070067 # expect to receive a hello message
68 msg = yield self.cxn.recv_class(ofp.message.hello)
sathishgbbb90992017-06-23 09:52:29 +053069 # supports only ofp_versions till 31 and single bitmap.
70 if msg:
71 support = ofp.util.verify_version_support(msg,self.ofp_version)
72 if not support:
73 self.cxn.send(ofp.message.hello_failed_error_msg(
74 xid=msg.xid, code=ofp.OFPHFC_INCOMPATIBLE,
75 data='i support only 1.3'))
76 log.error('peer-do-not-support-OpenFlow-version',self.ofp_version)
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -070077
sathishgbbb90992017-06-23 09:52:29 +053078 while support:
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -070079 req = yield self.cxn.recv_any()
80 handler = self.main_handlers.get(req.type, None)
81 if handler:
82 handler(self, req)
83 else:
84 log.error('cannot-handle',
85 request=req, xid=req.xid, type=req.type)
86
87 except Exception, e:
88 log.exception('exception', e=e)
89
Zsolt Haraszti2bdb6b32016-11-03 16:56:17 -070090 log.info('started')
91 returnValue(self)
92
93 def stop(self):
94 log.debug('stopping')
95 pass # nothing to do yet
96 log.info('stopped')
97
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -070098 def handle_echo_request(self, req):
99 self.cxn.send(ofp.message.echo_reply(xid=req.xid))
100
101 @inlineCallbacks
102 def handle_feature_request(self, req):
Zsolt Haraszticd22adc2016-10-25 00:13:06 -0700103 device_info = yield self.rpc.get_device_info(self.device_id)
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700104 kw = pb2dict(device_info.switch_features)
105 self.cxn.send(ofp.message.features_reply(
106 xid=req.xid,
107 datapath_id=self.datapath_id,
108 **kw))
109
110 def handle_stats_request(self, req):
111 handler = self.stats_handlers.get(req.stats_type, None)
112 if handler:
113 handler(self, req)
114 else:
115 raise OpenFlowProtocolError(
116 'Cannot handle stats request type "{}"'.format(req.stats_type))
117
118 def handle_barrier_request(self, req):
alshabibc3fb4942017-01-26 15:34:24 -0800119 # not really doing barrier yet, but we respond
120 # see https://jira.opencord.org/browse/CORD-823
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700121 self.cxn.send(ofp.message.barrier_reply(xid=req.xid))
122
123 def handle_experimenter_request(self, req):
124 raise NotImplementedError()
125
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700126 def handle_flow_mod_request(self, req):
sgovindacc736782017-05-02 20:06:37 +0530127 if self.role == ofp.OFPCR_ROLE_MASTER or self.role == ofp.OFPCR_ROLE_EQUAL:
128 try:
129 grpc_req = to_grpc(req)
130 except Exception, e:
131 log.exception('failed-to-convert', e=e)
132 else:
133 return self.rpc.update_flow_table(self.device_id, grpc_req)
134
135 elif self.role == ofp.OFPCR_ROLE_SLAVE:
136 self.cxn.send(ofp.message.bad_request_error_msg(code=ofp.OFPBRC_IS_SLAVE))
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700137
Koray Kokten8592a232018-08-27 07:41:14 +0000138 def handle_meter_mod_request(self, req):
139 if self.role == ofp.OFPCR_ROLE_MASTER or self.role == ofp.OFPCR_ROLE_EQUAL:
140 try:
141 grpc_req = to_grpc(req)
142 except Exception, e:
143 log.exception('failed-to-convert-meter-mod-request', e=e)
144 else:
145 return self.rpc.update_meter_mod_table(self.device_id, grpc_req)
146
147 elif self.role == ofp.OFPCR_ROLE_SLAVE:
148 self.cxn.send(ofp.message.bad_request_error_msg(code=ofp.OFPBRC_IS_SLAVE))
149
150 @inlineCallbacks
151 def handle_meter_stats_request(self, req):
152 try:
Gamze Abaka53cc0a22019-01-31 12:06:11 +0000153 meters = yield self.rpc.list_meters(self.device_id)
154 self.cxn.send(ofp.message.meter_stats_reply(
155 xid=req.xid, entries=[to_loxi(m.stats) for m in meters]))
Koray Kokten8592a232018-08-27 07:41:14 +0000156 except Exception, e:
157 log.exception("failed-meter-stats-request", req=req, e=e)
158
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700159 def handle_get_async_request(self, req):
160 raise NotImplementedError()
161
162 def handle_get_config_request(self, req):
163 self.cxn.send(ofp.message.get_config_reply(
164 xid=req.xid,
165 miss_send_len=ofp.OFPCML_NO_BUFFER
166 ))
167
Zsolt Haraszti8a774382016-10-24 18:25:54 -0700168 @inlineCallbacks
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700169 def handle_group_mod_request(self, req):
sgovindacc736782017-05-02 20:06:37 +0530170 if self.role == ofp.OFPCR_ROLE_MASTER or self.role == ofp.OFPCR_ROLE_EQUAL:
171 yield self.rpc.update_group_table(self.device_id, to_grpc(req))
172 elif self.role == ofp.OFPCR_ROLE_SLAVE:
173 self.cxn.send(ofp.message.bad_request_error_msg(code=ofp.OFPBRC_IS_SLAVE))
174
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700175 def handle_role_request(self, req):
sgovindacc736782017-05-02 20:06:37 +0530176 if req.role == ofp.OFPCR_ROLE_MASTER or req.role == ofp.OFPCR_ROLE_SLAVE:
sathishg00433e52017-05-23 16:07:41 +0530177 if self.agent.generation_is_defined and (
178 ((req.generation_id - self.agent.cached_generation_id) & 0xffffffffffffffff) if abs(
179 req.generation_id - self.agent.cached_generation_id) > 0x7fffffffffffffff else (
180 req.generation_id - self.agent.cached_generation_id)) < 0:
181 self.cxn.send(ofp.message.bad_request_error_msg(code=ofp.OFPRRFC_STALE))
182 else:
183 self.agent.generation_is_defined = True
184 self.agent.cached_generation_id = req.generation_id
185 self.role = req.role
186 self.cxn.send(ofp.message.role_reply(
187 xid=req.xid, role=req.role, generation_id=req.generation_id))
sgovindacc736782017-05-02 20:06:37 +0530188 elif req.role == ofp.OFPCR_ROLE_EQUAL:
sathishg00433e52017-05-23 16:07:41 +0530189 self.role = req.role
190 self.cxn.send(ofp.message.role_reply(
191 xid=req.xid, role=req.role))
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700192
193 def handle_packet_out_request(self, req):
sgovindacc736782017-05-02 20:06:37 +0530194 if self.role == ofp.OFPCR_ROLE_MASTER or self.role == ofp.OFPCR_ROLE_EQUAL:
195 self.rpc.send_packet_out(self.device_id, to_grpc(req))
Shad Ansari9109a282019-05-03 01:24:57 -0700196 self.count_pkt_out += 1
197 log.debug('counters of_protocol_handler OUT - {}'.format(self.count_pkt_out))
sgovindacc736782017-05-02 20:06:37 +0530198
199 elif self.role == ofp.OFPCR_ROLE_SLAVE:
200 self.cxn.send(ofp.message.bad_request_error_msg(code=ofp.OFPBRC_IS_SLAVE))
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700201
202 def handle_set_config_request(self, req):
alshabibc3fb4942017-01-26 15:34:24 -0800203 # Handle set config appropriately
204 # https://jira.opencord.org/browse/CORD-826
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700205 pass
206
Jonathan Hart8d21c322018-04-17 07:42:02 -0700207 @inlineCallbacks
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700208 def handle_port_mod_request(self, req):
Jonathan Hart8d21c322018-04-17 07:42:02 -0700209 if self.role == ofp.OFPCR_ROLE_MASTER or self.role == ofp.OFPCR_ROLE_EQUAL:
210 port = yield self.rpc.get_port(self.device_id, str(req.port_no))
211
212 if port.ofp_port.config & ofp.OFPPC_PORT_DOWN != \
213 req.config & ofp.OFPPC_PORT_DOWN:
214 if req.config & ofp.OFPPC_PORT_DOWN:
215 self.rpc.disable_port(self.device_id, port.id)
216 else:
217 self.rpc.enable_port(self.device_id, port.id)
218
219 elif self.role == ofp.OFPCR_ROLE_SLAVE:
220 self.cxn.send(ofp.message.bad_request_error_msg(code=ofp.OFPBRC_IS_SLAVE))
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700221
222 def handle_table_mod_request(self, req):
223 raise NotImplementedError()
224
225 def handle_queue_get_config_request(self, req):
226 raise NotImplementedError()
227
228 def handle_set_async_request(self, req):
229 raise NotImplementedError()
230
231 def handle_aggregate_request(self, req):
232 raise NotImplementedError
233
234 @inlineCallbacks
235 def handle_device_description_request(self, req):
Zsolt Haraszticd22adc2016-10-25 00:13:06 -0700236 device_info = yield self.rpc.get_device_info(self.device_id)
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700237 kw = pb2dict(device_info.desc)
238 self.cxn.send(ofp.message.desc_stats_reply(xid=req.xid, **kw))
239
240 def handle_experimenter_stats_request(self, req):
241 raise NotImplementedError()
242
243 @inlineCallbacks
244 def handle_flow_stats_request(self, req):
Zsolt Haraszti3578a1c2017-01-10 15:29:02 -0800245 try:
246 flow_stats = yield self.rpc.list_flows(self.device_id)
247 self.cxn.send(ofp.message.flow_stats_reply(
248 xid=req.xid, entries=[to_loxi(f) for f in flow_stats]))
249 except Exception, e:
250 log.exception('failed-flow-stats-request', req=req)
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700251
Zsolt Haraszti8a774382016-10-24 18:25:54 -0700252 @inlineCallbacks
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700253 def handle_group_stats_request(self, req):
Zsolt Haraszticd22adc2016-10-25 00:13:06 -0700254 group_stats = yield self.rpc.list_groups(self.device_id)
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700255 self.cxn.send(ofp.message.group_stats_reply(
alshabibf4fb2682017-01-12 00:32:56 -0600256 xid=req.xid, entries=[to_loxi(g.stats) for g in group_stats]))
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700257
alshabibf4fb2682017-01-12 00:32:56 -0600258 @inlineCallbacks
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700259 def handle_group_descriptor_request(self, req):
alshabibf4fb2682017-01-12 00:32:56 -0600260 group_stats = yield self.rpc.list_groups(self.device_id)
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700261 self.cxn.send(ofp.message.group_desc_stats_reply(
alshabibf4fb2682017-01-12 00:32:56 -0600262 xid=req.xid, entries=[to_loxi(g.desc) for g in group_stats]))
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700263
264 def handle_group_features_request(self, req):
265 raise NotImplementedError()
266
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700267 def handle_meter_config_request(self, req):
268 raise NotImplementedError()
269
270 def handle_meter_features_request(self, req):
Girish Gowdrue20da0e2019-01-28 20:22:05 -0800271 feature = ofp.meter_features(max_meter=OpenFlowProtocolHandler.MAX_METER_IDS,
272 band_types=ofp.OFPMBT_DROP,
273 capabilities=ofp.OFPMF_KBPS,
274 max_bands=OpenFlowProtocolHandler.MAX_METER_BANDS,
275 max_color=OpenFlowProtocolHandler.MAX_METER_COLORS)
276 self.cxn.send(ofp.message.meter_features_stats_reply(xid=req.xid, flags=None,
277 features=feature))
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700278
Nicolas Palpacuerfd7b8b12018-06-15 13:58:06 -0400279 @inlineCallbacks
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700280 def handle_port_stats_request(self, req):
Nicolas Palpacuerfd7b8b12018-06-15 13:58:06 -0400281 try:
282 ports = yield self.rpc.list_ports(self.device_id)
283 port_stats = [to_loxi(p.ofp_port_stats) for p in ports]
284 of_message = ofp.message.port_stats_reply(
285 xid=req.xid,entries=port_stats)
286 self.cxn.send(of_message)
287 except:
288 log.exception('failed-port_stats-request', req=req)
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700289
290 @inlineCallbacks
291 def handle_port_desc_request(self, req):
Zsolt Haraszticd22adc2016-10-25 00:13:06 -0700292 port_list = yield self.rpc.get_port_list(self.device_id)
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700293 self.cxn.send(ofp.message.port_desc_stats_reply(
294 xid=req.xid,
295 #flags=None,
Zsolt Haraszti66862032016-11-28 14:28:39 -0800296 entries=[to_loxi(port.ofp_port) for port in port_list]
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700297 ))
298
299 def handle_queue_stats_request(self, req):
300 raise NotImplementedError()
301
302 def handle_table_stats_request(self, req):
alshabibc3fb4942017-01-26 15:34:24 -0800303 table_stats = [] # see https://jira.opencord.org/browse/CORD-825
Zsolt Haraszti023ea7c2016-10-16 19:30:34 -0700304 self.cxn.send(ofp.message.table_stats_reply(
305 xid=req.xid, entries=table_stats))
306
307 def handle_table_features_request(self, req):
308 raise NotImplementedError()
309
310 stats_handlers = {
311 ofp.OFPST_AGGREGATE: handle_aggregate_request,
312 ofp.OFPST_DESC: handle_device_description_request,
313 ofp.OFPST_EXPERIMENTER: handle_experimenter_stats_request,
314 ofp.OFPST_FLOW: handle_flow_stats_request,
315 ofp.OFPST_GROUP: handle_group_stats_request,
316 ofp.OFPST_GROUP_DESC: handle_group_descriptor_request,
317 ofp.OFPST_GROUP_FEATURES: handle_group_features_request,
318 ofp.OFPST_METER: handle_meter_stats_request,
319 ofp.OFPST_METER_CONFIG: handle_meter_config_request,
320 ofp.OFPST_METER_FEATURES: handle_meter_features_request,
321 ofp.OFPST_PORT: handle_port_stats_request,
322 ofp.OFPST_PORT_DESC: handle_port_desc_request,
323 ofp.OFPST_QUEUE: handle_queue_stats_request,
324 ofp.OFPST_TABLE: handle_table_stats_request,
325 ofp.OFPST_TABLE_FEATURES: handle_table_features_request
326 }
327
328 main_handlers = {
329 ofp.OFPT_BARRIER_REQUEST: handle_barrier_request,
330 ofp.OFPT_ECHO_REQUEST: handle_echo_request,
331 ofp.OFPT_FEATURES_REQUEST: handle_feature_request,
332 ofp.OFPT_EXPERIMENTER: handle_experimenter_request,
333 ofp.OFPT_FLOW_MOD: handle_flow_mod_request,
334 ofp.OFPT_GET_ASYNC_REQUEST: handle_get_async_request,
335 ofp.OFPT_GET_CONFIG_REQUEST: handle_get_config_request,
336 ofp.OFPT_GROUP_MOD: handle_group_mod_request,
337 ofp.OFPT_METER_MOD: handle_meter_mod_request,
338 ofp.OFPT_PACKET_OUT: handle_packet_out_request,
339 ofp.OFPT_PORT_MOD: handle_port_mod_request,
340 ofp.OFPT_QUEUE_GET_CONFIG_REQUEST: handle_queue_get_config_request,
341 ofp.OFPT_ROLE_REQUEST: handle_role_request,
342 ofp.OFPT_SET_ASYNC: handle_set_async_request,
343 ofp.OFPT_SET_CONFIG: handle_set_config_request,
344 ofp.OFPT_STATS_REQUEST: handle_stats_request,
345 ofp.OFPT_TABLE_MOD: handle_table_mod_request,
346 }
347
Zsolt Haraszticd22adc2016-10-25 00:13:06 -0700348 def forward_packet_in(self, ofp_packet_in):
sgovindacc736782017-05-02 20:06:37 +0530349 if self.role == ofp.OFPCR_ROLE_MASTER or self.role == ofp.OFPCR_ROLE_EQUAL:
350 log.info('sending-packet-in', ofp_packet_in=ofp_packet_in)
351 self.cxn.send(to_loxi(ofp_packet_in))
Shad Ansari9109a282019-05-03 01:24:57 -0700352 self.count_pkt_in += 1
353 log.debug('counters of_protocol_handler IN - {}'.format(self.count_pkt_in))
Zsolt Haraszti217a12e2016-12-19 16:37:55 -0800354
355 def forward_port_status(self, ofp_port_status):
356 self.cxn.send(to_loxi(ofp_port_status))
Gamze Abaka9d343292019-02-20 06:35:18 +0000357
358 def forward_flow_removed(self, ofp_flow_removed):
359 self.cxn.send(to_loxi(ofp_flow_removed))