1 # Copyright (C) 2011, 2012 Nippon Telegraph and Telephone Corporation.
2 # Copyright (C) 2011, 2012 Isaku Yamahata <yamahata at valinux co jp>
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
8 # http://www.apache.org/licenses/LICENSE-2.0
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
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
18 Basic OpenFlow handling including negotiation.
25 import ryu.base.app_manager
27 from ryu.lib import hub
29 from ryu.controller import ofp_event
30 from ryu.controller.controller import OpenFlowController
31 from ryu.controller.handler import set_ev_handler
32 from ryu.controller.handler import HANDSHAKE_DISPATCHER, CONFIG_DISPATCHER,\
34 from ryu.ofproto import ofproto_parser
37 # The state transition: HANDSHAKE -> CONFIG -> MAIN
39 # HANDSHAKE: if it receives HELLO message with the valid OFP version,
40 # sends Features Request message, and moves to CONFIG.
42 # CONFIG: it receives Features Reply message and moves to MAIN
44 # MAIN: it does nothing. Applications are expected to register their
47 # Note that at any state, when we receive Echo Request message, send
48 # back Echo Reply message.
51 class OFPHandler(ryu.base.app_manager.RyuApp):
52 def __init__(self, *args, **kwargs):
53 super(OFPHandler, self).__init__(*args, **kwargs)
54 self.name = ofp_event.NAME
55 self.controller = None
58 super(OFPHandler, self).start()
59 self.controller = OpenFlowController()
60 return hub.spawn(self.controller)
62 def _hello_failed(self, datapath, error_desc):
63 self.logger.error('%s on datapath %s', error_desc, datapath.address)
64 error_msg = datapath.ofproto_parser.OFPErrorMsg(
66 type_=datapath.ofproto.OFPET_HELLO_FAILED,
67 code=datapath.ofproto.OFPHFC_INCOMPATIBLE,
69 datapath.send_msg(error_msg, close_socket=True)
71 @set_ev_handler(ofp_event.EventOFPHello, HANDSHAKE_DISPATCHER)
72 def hello_handler(self, ev):
73 self.logger.debug('hello ev %s', ev)
75 datapath = msg.datapath
77 # check if received version is supported.
78 # pre 1.0 is not supported
79 elements = getattr(msg, 'elements', None)
81 switch_versions = set()
82 for version in itertools.chain.from_iterable(
83 element.versions for element in elements):
84 switch_versions.add(version)
85 usable_versions = switch_versions & set(
86 datapath.supported_ofp_version)
88 # We didn't send our supported versions for interoperability as
89 # most switches would not understand elements at the moment.
90 # So the switch would think that the negotiated version would
91 # be max(negotiated_versions), but actual usable version is
92 # max(usable_versions).
93 negotiated_versions = set(
94 version for version in switch_versions
95 if version <= max(datapath.supported_ofp_version))
96 if negotiated_versions and not usable_versions:
98 # versions of OF 1.0 and 1.1 from switch
99 # max of OF 1.2 from Ryu and supported_ofp_version = (1.2, )
100 # negotiated version = 1.1
101 # usable version = None
103 'no compatible version found: '
104 'switch versions %s controller version 0x%x, '
105 'the negotiated version is 0x%x, '
106 'but no usable version found. '
107 'If possible, set the switch to use one of OF version %s'
108 % (switch_versions, max(datapath.supported_ofp_version),
109 max(negotiated_versions),
110 sorted(datapath.supported_ofp_version)))
111 self._hello_failed(datapath, error_desc)
113 if (negotiated_versions and usable_versions and
114 max(negotiated_versions) != max(usable_versions)):
116 # versions of OF 1.0 and 1.1 from switch
117 # max of OF 1.2 from Ryu and supported_ofp_version = (1.0, 1.2)
118 # negotiated version = 1.1
119 # usable version = 1.0
121 # TODO: In order to get the version 1.0, Ryu need to send
124 'no compatible version found: '
125 'switch versions 0x%x controller version 0x%x, '
126 'the negotiated version is %s but found usable %s. '
128 'set the switch to use one of OF version %s' % (
129 max(switch_versions),
130 max(datapath.supported_ofp_version),
131 sorted(negotiated_versions),
132 sorted(usable_versions), sorted(usable_versions)))
133 self._hello_failed(datapath, error_desc)
136 usable_versions = set(version for version
137 in datapath.supported_ofp_version
138 if version <= msg.version)
139 if (usable_versions and
140 max(usable_versions) != min(msg.version,
141 datapath.ofproto.OFP_VERSION)):
142 # The version of min(msg.version, datapath.ofproto.OFP_VERSION)
143 # should be used according to the spec. But we can't.
144 # So log it and use max(usable_versions) with the hope that
145 # the switch is able to understand lower version.
148 # OF 1.2 from Ryu and supported_ofp_version = (1.0, 1.2)
149 # In this case, 1.1 should be used according to the spec,
150 # but 1.1 can't be used.
153 # Upon receipt of this message, the recipient must
154 # calculate the OpenFlow protocol version to be used. If
155 # both the Hello message sent and the Hello message
156 # received contained a OFPHET_VERSIONBITMAP hello element,
157 # and if those bitmaps have some common bits set, the
158 # negotiated version must be the highest version set in
159 # both bitmaps. Otherwise, the negotiated version must be
160 # the smaller of the version number that was sent and the
161 # one that was received in the version fields. If the
162 # negotiated version is supported by the recipient, then
163 # the connection proceeds. Otherwise, the recipient must
164 # reply with an OFPT_ERROR message with a type field of
165 # OFPET_HELLO_FAILED, a code field of OFPHFC_INCOMPATIBLE,
166 # and optionally an ASCII string explaining the situation
167 # in data, and then terminate the connection.
168 version = max(usable_versions)
170 'no compatible version found: '
171 'switch 0x%x controller 0x%x, but found usable 0x%x. '
172 'If possible, set the switch to use OF version 0x%x' % (
173 msg.version, datapath.ofproto.OFP_VERSION,
175 self._hello_failed(datapath, error_desc)
178 if not usable_versions:
180 'unsupported version 0x%x. '
181 'If possible, set the switch to use one of the versions %s' % (
182 msg.version, sorted(datapath.supported_ofp_version)))
183 self._hello_failed(datapath, error_desc)
185 datapath.set_version(max(usable_versions))
187 # Move on to config state
188 self.logger.debug('move onto config mode')
189 datapath.set_state(CONFIG_DISPATCHER)
191 # Finally, send feature request
192 features_request = datapath.ofproto_parser.OFPFeaturesRequest(datapath)
193 datapath.send_msg(features_request)
195 @set_ev_handler(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
196 def switch_features_handler(self, ev):
198 datapath = msg.datapath
199 self.logger.debug('switch features ev %s', msg)
201 datapath.id = msg.datapath_id
203 # hacky workaround, will be removed. OF1.3 doesn't have
204 # ports. An application should not depend on them. But there
205 # might be such bad applications so keep this workaround for
207 if datapath.ofproto.OFP_VERSION < 0x04:
208 datapath.ports = msg.ports
212 if datapath.ofproto.OFP_VERSION < 0x04:
213 self.logger.debug('move onto main mode')
214 ev.msg.datapath.set_state(MAIN_DISPATCHER)
216 port_desc = datapath.ofproto_parser.OFPPortDescStatsRequest(
218 datapath.send_msg(port_desc)
220 @set_ev_handler(ofp_event.EventOFPPortDescStatsReply, CONFIG_DISPATCHER)
221 def multipart_reply_handler(self, ev):
223 datapath = msg.datapath
224 with warnings.catch_warnings():
225 warnings.simplefilter('ignore')
226 for port in msg.body:
227 datapath.ports[port.port_no] = port
229 if msg.flags & datapath.ofproto.OFPMPF_REPLY_MORE:
231 self.logger.debug('move onto main mode')
232 ev.msg.datapath.set_state(MAIN_DISPATCHER)
234 @set_ev_handler(ofp_event.EventOFPEchoRequest,
235 [HANDSHAKE_DISPATCHER, CONFIG_DISPATCHER, MAIN_DISPATCHER])
236 def echo_request_handler(self, ev):
238 datapath = msg.datapath
239 echo_reply = datapath.ofproto_parser.OFPEchoReply(datapath)
240 echo_reply.xid = msg.xid
241 echo_reply.data = msg.data
242 datapath.send_msg(echo_reply)
244 @set_ev_handler(ofp_event.EventOFPEchoReply,
245 [HANDSHAKE_DISPATCHER, CONFIG_DISPATCHER, MAIN_DISPATCHER])
246 def echo_reply_handler(self, ev):
248 datapath = msg.datapath
249 datapath.acknowledge_echo_reply(msg.xid)
251 @set_ev_handler(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER)
252 def port_status_handler(self, ev):
254 datapath = msg.datapath
255 ofproto = datapath.ofproto
257 if msg.reason in [ofproto.OFPPR_ADD, ofproto.OFPPR_MODIFY]:
258 datapath.ports[msg.desc.port_no] = msg.desc
259 elif msg.reason == ofproto.OFPPR_DELETE:
260 datapath.ports.pop(msg.desc.port_no, None)
264 self.send_event_to_observers(
265 ofp_event.EventOFPPortStateChange(
266 datapath, msg.reason, msg.desc.port_no),
269 @set_ev_handler(ofp_event.EventOFPErrorMsg,
270 [HANDSHAKE_DISPATCHER, CONFIG_DISPATCHER, MAIN_DISPATCHER])
271 def error_msg_handler(self, ev):
273 ofp = msg.datapath.ofproto
275 "EventOFPErrorMsg received.\n"
276 "version=%s, msg_type=%s, msg_len=%s, xid=%s\n"
278 hex(msg.version), hex(msg.msg_type), hex(msg.msg_len),
280 ofp.ofp_msg_type_to_str(msg.msg_type))
281 if msg.type == ofp.OFPET_EXPERIMENTER:
283 "OFPErrorExperimenterMsg(type=%s, exp_type=%s,"
284 " experimenter=%s, data=b'%s')",
285 hex(msg.type), hex(msg.exp_type),
286 hex(msg.experimenter), utils.binary_str(msg.data))
289 "OFPErrorMsg(type=%s, code=%s, data=b'%s')\n"
292 hex(msg.type), hex(msg.code), utils.binary_str(msg.data),
293 ofp.ofp_error_type_to_str(msg.type),
294 ofp.ofp_error_code_to_str(msg.type, msg.code))
295 if msg.type == ofp.OFPET_HELLO_FAILED:
297 " `-- data: %s", msg.data.decode('ascii'))
298 elif len(msg.data) >= ofp.OFP_HEADER_SIZE:
299 (version, msg_type, msg_len, xid) = ofproto_parser.header(msg.data)
301 " `-- data: version=%s, msg_type=%s, msg_len=%s, xid=%s\n"
303 hex(version), hex(msg_type), hex(msg_len), hex(xid),
304 ofp.ofp_msg_type_to_str(msg_type))
307 "The data field sent from the switch is too short: "
308 "len(msg.data) < OFP_HEADER_SIZE\n"
309 "The OpenFlow Spec says that the data field should contain "
310 "at least 64 bytes of the failed request.\n"
311 "Please check the settings or implementation of your switch.")