Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 1 | # |
Zsolt Haraszti | 3eb27a5 | 2017-01-03 21:56:48 -0800 | [diff] [blame] | 2 | # Copyright 2017 the original author or authors. |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 3 | # |
| 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 | # |
| 16 | import structlog |
Zsolt Haraszti | 2bdb6b3 | 2016-11-03 16:56:17 -0700 | [diff] [blame] | 17 | from twisted.internet.defer import inlineCallbacks, returnValue |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 18 | |
| 19 | import loxi.of13 as ofp |
| 20 | from converter import to_loxi, pb2dict, to_grpc |
| 21 | |
| 22 | log = structlog.get_logger() |
| 23 | |
| 24 | |
| 25 | class OpenFlowProtocolError(Exception): pass |
| 26 | |
| 27 | |
| 28 | class OpenFlowProtocolHandler(object): |
| 29 | |
Zsolt Haraszti | cd22adc | 2016-10-25 00:13:06 -0700 | [diff] [blame] | 30 | def __init__(self, datapath_id, device_id, agent, cxn, rpc): |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 31 | """ |
| 32 | The upper half of the OpenFlow protocol, focusing on message |
| 33 | exchanges. |
| 34 | :param agent: Reference to the Agent() instance, can be used to |
| 35 | indicate critical errors to break the connection. |
| 36 | :param cxn: The lower level message serdes part of the OF protocol. |
| 37 | :param rpc: The application level stub on which RPC calls |
| 38 | are made as result of processing incoming OpenFlow request messages. |
| 39 | """ |
| 40 | self.datapath_id = datapath_id |
Zsolt Haraszti | cd22adc | 2016-10-25 00:13:06 -0700 | [diff] [blame] | 41 | self.device_id = device_id |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 42 | self.agent = agent |
| 43 | self.cxn = cxn |
| 44 | self.rpc = rpc |
| 45 | |
| 46 | @inlineCallbacks |
Zsolt Haraszti | 2bdb6b3 | 2016-11-03 16:56:17 -0700 | [diff] [blame] | 47 | def start(self): |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 48 | """A new call is made after a fresh reconnect""" |
| 49 | |
Zsolt Haraszti | 2bdb6b3 | 2016-11-03 16:56:17 -0700 | [diff] [blame] | 50 | log.debug('starting') |
| 51 | |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 52 | try: |
| 53 | # send initial hello message |
| 54 | self.cxn.send(ofp.message.hello()) |
| 55 | |
| 56 | # expect to receive a hello message |
| 57 | msg = yield self.cxn.recv_class(ofp.message.hello) |
alshabib | c3fb494 | 2017-01-26 15:34:24 -0800 | [diff] [blame] | 58 | # verify version compatibility (must list version 1.3) |
| 59 | # and negotiate if not. |
| 60 | # see https://jira.opencord.org/browse/CORD-822 |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 61 | |
| 62 | while True: |
| 63 | req = yield self.cxn.recv_any() |
| 64 | handler = self.main_handlers.get(req.type, None) |
| 65 | if handler: |
| 66 | handler(self, req) |
| 67 | else: |
| 68 | log.error('cannot-handle', |
| 69 | request=req, xid=req.xid, type=req.type) |
| 70 | |
| 71 | except Exception, e: |
| 72 | log.exception('exception', e=e) |
| 73 | |
Zsolt Haraszti | 2bdb6b3 | 2016-11-03 16:56:17 -0700 | [diff] [blame] | 74 | log.info('started') |
| 75 | returnValue(self) |
| 76 | |
| 77 | def stop(self): |
| 78 | log.debug('stopping') |
| 79 | pass # nothing to do yet |
| 80 | log.info('stopped') |
| 81 | |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 82 | def handle_echo_request(self, req): |
| 83 | self.cxn.send(ofp.message.echo_reply(xid=req.xid)) |
| 84 | |
| 85 | @inlineCallbacks |
| 86 | def handle_feature_request(self, req): |
Zsolt Haraszti | cd22adc | 2016-10-25 00:13:06 -0700 | [diff] [blame] | 87 | device_info = yield self.rpc.get_device_info(self.device_id) |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 88 | kw = pb2dict(device_info.switch_features) |
| 89 | self.cxn.send(ofp.message.features_reply( |
| 90 | xid=req.xid, |
| 91 | datapath_id=self.datapath_id, |
| 92 | **kw)) |
| 93 | |
| 94 | def handle_stats_request(self, req): |
| 95 | handler = self.stats_handlers.get(req.stats_type, None) |
| 96 | if handler: |
| 97 | handler(self, req) |
| 98 | else: |
| 99 | raise OpenFlowProtocolError( |
| 100 | 'Cannot handle stats request type "{}"'.format(req.stats_type)) |
| 101 | |
| 102 | def handle_barrier_request(self, req): |
alshabib | c3fb494 | 2017-01-26 15:34:24 -0800 | [diff] [blame] | 103 | # not really doing barrier yet, but we respond |
| 104 | # see https://jira.opencord.org/browse/CORD-823 |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 105 | self.cxn.send(ofp.message.barrier_reply(xid=req.xid)) |
| 106 | |
| 107 | def handle_experimenter_request(self, req): |
| 108 | raise NotImplementedError() |
| 109 | |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 110 | def handle_flow_mod_request(self, req): |
Zsolt Haraszti | 6a5107c | 2017-01-09 23:42:41 -0800 | [diff] [blame] | 111 | try: |
| 112 | grpc_req = to_grpc(req) |
| 113 | except Exception, e: |
| 114 | log.exception('failed-to-convert', e=e) |
| 115 | else: |
| 116 | return self.rpc.update_flow_table(self.device_id, grpc_req) |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 117 | |
| 118 | def handle_get_async_request(self, req): |
| 119 | raise NotImplementedError() |
| 120 | |
| 121 | def handle_get_config_request(self, req): |
| 122 | self.cxn.send(ofp.message.get_config_reply( |
| 123 | xid=req.xid, |
| 124 | miss_send_len=ofp.OFPCML_NO_BUFFER |
| 125 | )) |
| 126 | |
Zsolt Haraszti | 8a77438 | 2016-10-24 18:25:54 -0700 | [diff] [blame] | 127 | @inlineCallbacks |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 128 | def handle_group_mod_request(self, req): |
Zsolt Haraszti | cd22adc | 2016-10-25 00:13:06 -0700 | [diff] [blame] | 129 | yield self.rpc.update_group_table(self.device_id, to_grpc(req)) |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 130 | |
| 131 | def handle_meter_mod_request(self, req): |
| 132 | raise NotImplementedError() |
| 133 | |
| 134 | def handle_role_request(self, req): |
alshabib | c3fb494 | 2017-01-26 15:34:24 -0800 | [diff] [blame] | 135 | # Handle role messages appropriately to support multiple controllers |
| 136 | # see https://jira.opencord.org/browse/CORD-824 |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 137 | if req.role != ofp.OFPCR_ROLE_MASTER: |
| 138 | raise NotImplementedError() |
| 139 | self.cxn.send(ofp.message.role_reply( |
| 140 | xid=req.xid, role=req.role, generation_id=req.generation_id)) |
| 141 | |
| 142 | def handle_packet_out_request(self, req): |
Zsolt Haraszti | cd22adc | 2016-10-25 00:13:06 -0700 | [diff] [blame] | 143 | self.rpc.send_packet_out(self.device_id, to_grpc(req)) |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 144 | |
| 145 | def handle_set_config_request(self, req): |
alshabib | c3fb494 | 2017-01-26 15:34:24 -0800 | [diff] [blame] | 146 | # Handle set config appropriately |
| 147 | # https://jira.opencord.org/browse/CORD-826 |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 148 | pass |
| 149 | |
| 150 | def handle_port_mod_request(self, req): |
| 151 | raise NotImplementedError() |
| 152 | |
| 153 | def handle_table_mod_request(self, req): |
| 154 | raise NotImplementedError() |
| 155 | |
| 156 | def handle_queue_get_config_request(self, req): |
| 157 | raise NotImplementedError() |
| 158 | |
| 159 | def handle_set_async_request(self, req): |
| 160 | raise NotImplementedError() |
| 161 | |
| 162 | def handle_aggregate_request(self, req): |
| 163 | raise NotImplementedError |
| 164 | |
| 165 | @inlineCallbacks |
| 166 | def handle_device_description_request(self, req): |
Zsolt Haraszti | cd22adc | 2016-10-25 00:13:06 -0700 | [diff] [blame] | 167 | device_info = yield self.rpc.get_device_info(self.device_id) |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 168 | kw = pb2dict(device_info.desc) |
| 169 | self.cxn.send(ofp.message.desc_stats_reply(xid=req.xid, **kw)) |
| 170 | |
| 171 | def handle_experimenter_stats_request(self, req): |
| 172 | raise NotImplementedError() |
| 173 | |
| 174 | @inlineCallbacks |
| 175 | def handle_flow_stats_request(self, req): |
Zsolt Haraszti | 3578a1c | 2017-01-10 15:29:02 -0800 | [diff] [blame] | 176 | try: |
| 177 | flow_stats = yield self.rpc.list_flows(self.device_id) |
| 178 | self.cxn.send(ofp.message.flow_stats_reply( |
| 179 | xid=req.xid, entries=[to_loxi(f) for f in flow_stats])) |
| 180 | except Exception, e: |
| 181 | log.exception('failed-flow-stats-request', req=req) |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 182 | |
Zsolt Haraszti | 8a77438 | 2016-10-24 18:25:54 -0700 | [diff] [blame] | 183 | @inlineCallbacks |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 184 | def handle_group_stats_request(self, req): |
Zsolt Haraszti | cd22adc | 2016-10-25 00:13:06 -0700 | [diff] [blame] | 185 | group_stats = yield self.rpc.list_groups(self.device_id) |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 186 | self.cxn.send(ofp.message.group_stats_reply( |
alshabib | f4fb268 | 2017-01-12 00:32:56 -0600 | [diff] [blame] | 187 | xid=req.xid, entries=[to_loxi(g.stats) for g in group_stats])) |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 188 | |
alshabib | f4fb268 | 2017-01-12 00:32:56 -0600 | [diff] [blame] | 189 | @inlineCallbacks |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 190 | def handle_group_descriptor_request(self, req): |
alshabib | f4fb268 | 2017-01-12 00:32:56 -0600 | [diff] [blame] | 191 | group_stats = yield self.rpc.list_groups(self.device_id) |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 192 | self.cxn.send(ofp.message.group_desc_stats_reply( |
alshabib | f4fb268 | 2017-01-12 00:32:56 -0600 | [diff] [blame] | 193 | xid=req.xid, entries=[to_loxi(g.desc) for g in group_stats])) |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 194 | |
| 195 | def handle_group_features_request(self, req): |
| 196 | raise NotImplementedError() |
| 197 | |
| 198 | def handle_meter_stats_request(self, req): |
alshabib | c3fb494 | 2017-01-26 15:34:24 -0800 | [diff] [blame] | 199 | meter_stats = [] # see https://jira.opencord.org/browse/CORD-825 |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 200 | self.cxn.send(ofp.message.meter_stats_reply( |
| 201 | xid=req.xid, entries=meter_stats)) |
| 202 | |
| 203 | def handle_meter_config_request(self, req): |
| 204 | raise NotImplementedError() |
| 205 | |
| 206 | def handle_meter_features_request(self, req): |
alshabib | 81824e3 | 2016-12-21 21:43:45 -0800 | [diff] [blame] | 207 | self.cxn.send(ofp.message.bad_request_error_msg()) |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 208 | |
| 209 | def handle_port_stats_request(self, req): |
alshabib | c3fb494 | 2017-01-26 15:34:24 -0800 | [diff] [blame] | 210 | port_stats = [] # see https://jira.opencord.org/browse/CORD-825 |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 211 | self.cxn.send(ofp.message.port_stats_reply( |
| 212 | xid=req.xid,entries=port_stats)) |
| 213 | |
| 214 | @inlineCallbacks |
| 215 | def handle_port_desc_request(self, req): |
Zsolt Haraszti | cd22adc | 2016-10-25 00:13:06 -0700 | [diff] [blame] | 216 | port_list = yield self.rpc.get_port_list(self.device_id) |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 217 | self.cxn.send(ofp.message.port_desc_stats_reply( |
| 218 | xid=req.xid, |
| 219 | #flags=None, |
Zsolt Haraszti | 6686203 | 2016-11-28 14:28:39 -0800 | [diff] [blame] | 220 | entries=[to_loxi(port.ofp_port) for port in port_list] |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 221 | )) |
| 222 | |
| 223 | def handle_queue_stats_request(self, req): |
| 224 | raise NotImplementedError() |
| 225 | |
| 226 | def handle_table_stats_request(self, req): |
alshabib | c3fb494 | 2017-01-26 15:34:24 -0800 | [diff] [blame] | 227 | table_stats = [] # see https://jira.opencord.org/browse/CORD-825 |
Zsolt Haraszti | 023ea7c | 2016-10-16 19:30:34 -0700 | [diff] [blame] | 228 | self.cxn.send(ofp.message.table_stats_reply( |
| 229 | xid=req.xid, entries=table_stats)) |
| 230 | |
| 231 | def handle_table_features_request(self, req): |
| 232 | raise NotImplementedError() |
| 233 | |
| 234 | stats_handlers = { |
| 235 | ofp.OFPST_AGGREGATE: handle_aggregate_request, |
| 236 | ofp.OFPST_DESC: handle_device_description_request, |
| 237 | ofp.OFPST_EXPERIMENTER: handle_experimenter_stats_request, |
| 238 | ofp.OFPST_FLOW: handle_flow_stats_request, |
| 239 | ofp.OFPST_GROUP: handle_group_stats_request, |
| 240 | ofp.OFPST_GROUP_DESC: handle_group_descriptor_request, |
| 241 | ofp.OFPST_GROUP_FEATURES: handle_group_features_request, |
| 242 | ofp.OFPST_METER: handle_meter_stats_request, |
| 243 | ofp.OFPST_METER_CONFIG: handle_meter_config_request, |
| 244 | ofp.OFPST_METER_FEATURES: handle_meter_features_request, |
| 245 | ofp.OFPST_PORT: handle_port_stats_request, |
| 246 | ofp.OFPST_PORT_DESC: handle_port_desc_request, |
| 247 | ofp.OFPST_QUEUE: handle_queue_stats_request, |
| 248 | ofp.OFPST_TABLE: handle_table_stats_request, |
| 249 | ofp.OFPST_TABLE_FEATURES: handle_table_features_request |
| 250 | } |
| 251 | |
| 252 | main_handlers = { |
| 253 | ofp.OFPT_BARRIER_REQUEST: handle_barrier_request, |
| 254 | ofp.OFPT_ECHO_REQUEST: handle_echo_request, |
| 255 | ofp.OFPT_FEATURES_REQUEST: handle_feature_request, |
| 256 | ofp.OFPT_EXPERIMENTER: handle_experimenter_request, |
| 257 | ofp.OFPT_FLOW_MOD: handle_flow_mod_request, |
| 258 | ofp.OFPT_GET_ASYNC_REQUEST: handle_get_async_request, |
| 259 | ofp.OFPT_GET_CONFIG_REQUEST: handle_get_config_request, |
| 260 | ofp.OFPT_GROUP_MOD: handle_group_mod_request, |
| 261 | ofp.OFPT_METER_MOD: handle_meter_mod_request, |
| 262 | ofp.OFPT_PACKET_OUT: handle_packet_out_request, |
| 263 | ofp.OFPT_PORT_MOD: handle_port_mod_request, |
| 264 | ofp.OFPT_QUEUE_GET_CONFIG_REQUEST: handle_queue_get_config_request, |
| 265 | ofp.OFPT_ROLE_REQUEST: handle_role_request, |
| 266 | ofp.OFPT_SET_ASYNC: handle_set_async_request, |
| 267 | ofp.OFPT_SET_CONFIG: handle_set_config_request, |
| 268 | ofp.OFPT_STATS_REQUEST: handle_stats_request, |
| 269 | ofp.OFPT_TABLE_MOD: handle_table_mod_request, |
| 270 | } |
| 271 | |
Zsolt Haraszti | cd22adc | 2016-10-25 00:13:06 -0700 | [diff] [blame] | 272 | def forward_packet_in(self, ofp_packet_in): |
Zsolt Haraszti | 8925d1f | 2016-12-21 00:45:19 -0800 | [diff] [blame] | 273 | log.info('sending-packet-in', ofp_packet_in=ofp_packet_in) |
Zsolt Haraszti | cd22adc | 2016-10-25 00:13:06 -0700 | [diff] [blame] | 274 | self.cxn.send(to_loxi(ofp_packet_in)) |
Zsolt Haraszti | 217a12e | 2016-12-19 16:37:55 -0800 | [diff] [blame] | 275 | |
| 276 | def forward_port_status(self, ofp_port_status): |
| 277 | self.cxn.send(to_loxi(ofp_port_status)) |