1 # Copyright (C) 2013 Nippon Telegraph and Telephone Corporation.
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
20 from ryu.base import app_manager
21 from ryu.controller import event
22 from ryu.controller import handler
23 from ryu.controller import ofp_event
24 from ryu.controller.handler import set_ev_cls
25 from ryu.exception import RyuException
26 from ryu.exception import OFPUnknownVersion
27 from ryu.lib import hub
28 from ryu.lib import mac
29 from ryu.lib.dpid import dpid_to_str
30 from ryu.lib.packet import bpdu
31 from ryu.lib.packet import ethernet
32 from ryu.lib.packet import llc
33 from ryu.lib.packet import packet
34 from ryu.ofproto import ofproto_v1_0
35 from ryu.ofproto import ofproto_v1_2
36 from ryu.ofproto import ofproto_v1_3
41 # for OpenFlow 1.2/1.3
42 BPDU_PKT_IN_PRIORITY = 0xffff
43 NO_PKT_IN_PRIORITY = 0xfffe
46 # Result of compared config BPDU priority.
52 DESIGNATED_PORT = 0 # The port which sends BPDU.
53 ROOT_PORT = 1 # The port which receives BPDU from a root bridge.
54 NON_DESIGNATED_PORT = 2 # The port which blocked.
56 """ How to decide the port roles.
59 a bridge has smallest bridge ID is chosen as a root.
60 it sends original config BPDU.
62 forwards config BPDU received from the root bridge.
64 +-----------------------+
66 +-----------------------+
71 +-----------------+ +-----------------+
72 | Non Root bridge |(D)---(ND)| Non Root bridge |
73 +-----------------+ +-----------------+
76 the nearest port to a root bridge of the bridge.
77 it is determined by the cost of the path, etc.
79 the port of the side near the root bridge of each link.
80 it is determined by the cost of the path, etc.
81 NON_DESIGNATED_PORT(ND):
82 the port other than a ROOT_PORT and DESIGNATED_PORT.
87 # DISABLE: Administratively down or link down by an obstacle.
88 # BLOCK : Not part of spanning tree.
89 # LISTEN : Not learning or relaying frames.
90 # LEARN : Learning but not relaying frames.
91 # FORWARD: Learning and relaying frames.
92 PORT_STATE_DISABLE = 0
96 PORT_STATE_FORWARD = 4
99 PORT_CONFIG_V1_0 = {PORT_STATE_DISABLE: (ofproto_v1_0.OFPPC_NO_RECV_STP
100 | ofproto_v1_0.OFPPC_NO_RECV
101 | ofproto_v1_0.OFPPC_NO_FLOOD
102 | ofproto_v1_0.OFPPC_NO_FWD),
103 PORT_STATE_BLOCK: (ofproto_v1_0.OFPPC_NO_RECV
104 | ofproto_v1_0.OFPPC_NO_FLOOD
105 | ofproto_v1_0.OFPPC_NO_FWD),
106 PORT_STATE_LISTEN: (ofproto_v1_0.OFPPC_NO_RECV
107 | ofproto_v1_0.OFPPC_NO_FLOOD),
108 PORT_STATE_LEARN: ofproto_v1_0.OFPPC_NO_FLOOD,
109 PORT_STATE_FORWARD: 0}
112 PORT_CONFIG_V1_2 = {PORT_STATE_DISABLE: (ofproto_v1_2.OFPPC_NO_RECV
113 | ofproto_v1_2.OFPPC_NO_FWD),
114 PORT_STATE_BLOCK: (ofproto_v1_2.OFPPC_NO_FWD
115 | ofproto_v1_2.OFPPC_NO_PACKET_IN),
116 PORT_STATE_LISTEN: ofproto_v1_2.OFPPC_NO_PACKET_IN,
117 PORT_STATE_LEARN: ofproto_v1_2.OFPPC_NO_PACKET_IN,
118 PORT_STATE_FORWARD: 0}
121 PORT_CONFIG_V1_3 = {PORT_STATE_DISABLE: (ofproto_v1_3.OFPPC_NO_RECV
122 | ofproto_v1_3.OFPPC_NO_FWD),
123 PORT_STATE_BLOCK: (ofproto_v1_3.OFPPC_NO_FWD
124 | ofproto_v1_3.OFPPC_NO_PACKET_IN),
125 PORT_STATE_LISTEN: ofproto_v1_3.OFPPC_NO_PACKET_IN,
126 PORT_STATE_LEARN: ofproto_v1_3.OFPPC_NO_PACKET_IN,
127 PORT_STATE_FORWARD: 0}
129 """ Port state machine
131 +------------------------<--------------------------+
133 +--> [BLOCK] -----+--> [LISTEN] ----> [LEARN] ------+----> [FORWARD]
134 *3 | | 15sec | 15sec *1 |
136 +----<---+------<------+----------<----------+
138 *1 if port role == DESIGNATED_PORT or ROOT_PORT
139 *2 if port role == NON_DESIGNATED_PORT
140 *3 re-calculation of Spanning tree occurred.
142 When bridge has started, each port state is set to [LISTEN]
143 except port configuration is disable.
144 If port configuration is disable or link down occurred,
145 the port state is set to [DISABLE]
149 # Throw this event when network topology is changed.
150 # Flush filtering database, when you receive this event.
151 class EventTopologyChange(event.EventBase):
152 def __init__(self, dp):
153 super(EventTopologyChange, self).__init__()
157 # Throw this event when port status is changed.
158 class EventPortStateChange(event.EventBase):
159 def __init__(self, dp, port):
160 super(EventPortStateChange, self).__init__()
162 self.port_no = port.ofport.port_no
163 self.port_state = port.state
166 # Event for receive packet in message except BPDU packet.
167 class EventPacketIn(event.EventBase):
168 def __init__(self, msg):
169 super(EventPacketIn, self).__init__()
173 # For Python3 compatibility
174 # Note: The following is the official workaround for cmp() in Python2.
175 # https://docs.python.org/3.0/whatsnew/3.0.html#ordering-comparisons
177 return (a > b) - (a < b)
180 class Stp(app_manager.RyuApp):
181 """ STP(spanning tree) library. """
183 OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION,
184 ofproto_v1_2.OFP_VERSION,
185 ofproto_v1_3.OFP_VERSION]
188 super(Stp, self).__init__()
192 self.bridge_list = {}
195 for dpid in self.bridge_list:
196 self._unregister_bridge(dpid)
198 def _set_logger(self):
199 self.logger.propagate = False
200 hdlr = logging.StreamHandler()
201 fmt_str = '[STP][%(levelname)s] dpid=%(dpid)s: %(message)s'
202 hdlr.setFormatter(logging.Formatter(fmt_str))
203 self.logger.addHandler(hdlr)
205 def set_config(self, config):
206 """ Use this API if you want to set up configuration
207 of each bridge and ports.
208 Set configuration with 'config' parameter as follows.
210 config = {<dpid>: {'bridge': {'priority': <value>,
211 'sys_ext_id': <value>,
213 'hello_time': <value>,
214 'fwd_delay': <value>}
215 'ports': {<port_no>: {'priority': <value>,
216 'path_cost': <value>,
217 'enable': <True/False>},
218 <port_no>: {...},,,}}
222 NOTE: You may omit each field.
223 If omitted, a default value is set up.
224 It becomes effective when a bridge starts.
227 ------------------------------------------------------
228 | bridge | priority | bpdu.DEFAULT_BRIDGE_PRIORITY |
230 | | max_age | bpdu.DEFAULT_MAX_AGE |
231 | | hello_time | bpdu.DEFAULT_HELLO_TIME |
232 | | fwd_delay | bpdu.DEFAULT_FORWARD_DELAY |
233 |--------|------------|------------------------------|
234 | port | priority | bpdu.DEFAULT_PORT_PRIORITY |
235 | | path_cost | (Set up automatically |
236 | | | according to link speed.) |
238 ------------------------------------------------------
240 assert isinstance(config, dict)
243 @set_ev_cls(ofp_event.EventOFPStateChange,
244 [handler.MAIN_DISPATCHER, handler.DEAD_DISPATCHER])
245 def dispacher_change(self, ev):
246 assert ev.datapath is not None
247 if ev.state == handler.MAIN_DISPATCHER:
248 self._register_bridge(ev.datapath)
249 elif ev.state == handler.DEAD_DISPATCHER:
250 self._unregister_bridge(ev.datapath.id)
252 def _register_bridge(self, dp):
253 self._unregister_bridge(dp.id)
255 dpid_str = {'dpid': dpid_to_str(dp.id)}
256 self.logger.info('Join as stp bridge.', extra=dpid_str)
258 bridge = Bridge(dp, self.logger,
259 self.config.get(dp.id, {}),
260 self.send_event_to_observers)
261 except OFPUnknownVersion as message:
262 self.logger.error(str(message), extra=dpid_str)
265 self.bridge_list[dp.id] = bridge
267 def _unregister_bridge(self, dp_id):
268 if dp_id in self.bridge_list:
269 self.bridge_list[dp_id].delete()
270 del self.bridge_list[dp_id]
271 self.logger.info('Leave stp bridge.',
272 extra={'dpid': dpid_to_str(dp_id)})
274 @set_ev_cls(ofp_event.EventOFPPacketIn, handler.MAIN_DISPATCHER)
275 def packet_in_handler(self, ev):
276 if ev.msg.datapath.id in self.bridge_list:
277 bridge = self.bridge_list[ev.msg.datapath.id]
278 bridge.packet_in_handler(ev.msg)
280 @set_ev_cls(ofp_event.EventOFPPortStatus, handler.MAIN_DISPATCHER)
281 def port_status_handler(self, ev):
283 dpid_str = {'dpid': dpid_to_str(dp.id)}
285 reason = ev.msg.reason
286 link_down_flg = port.state & 0b1
288 if dp.id in self.bridge_list:
289 bridge = self.bridge_list[dp.id]
291 if reason is dp.ofproto.OFPPR_ADD:
292 self.logger.info('[port=%d] Port add.',
293 port.port_no, extra=dpid_str)
294 bridge.port_add(port)
295 elif reason is dp.ofproto.OFPPR_DELETE:
296 self.logger.info('[port=%d] Port delete.',
297 port.port_no, extra=dpid_str)
298 bridge.port_delete(port)
300 assert reason is dp.ofproto.OFPPR_MODIFY
301 if bridge.ports_state[port.port_no] == port.state:
303 self.logger.debug('[port=%d] Link status not changed.',
304 port.port_no, extra=dpid_str)
307 self.logger.info('[port=%d] Link down.',
308 port.port_no, extra=dpid_str)
309 bridge.link_down(port)
311 self.logger.info('[port=%d] Link up.',
312 port.port_no, extra=dpid_str)
316 def compare_root_path(path_cost1, path_cost2, bridge_id1, bridge_id2,
318 """ Decide the port of the side near a root bridge.
319 It is compared by the following priorities.
321 2. designated bridge ID value
322 3. designated port ID value """
323 result = Stp._cmp_value(path_cost1, path_cost2)
325 result = Stp._cmp_value(bridge_id1, bridge_id2)
327 result = Stp._cmp_value(port_id1, port_id2)
331 def compare_bpdu_info(my_priority, my_times, rcv_priority, rcv_times):
332 """ Check received BPDU is superior to currently held BPDU
333 by the following comparison.
334 - root bridge ID value
336 - designated bridge ID value
337 - designated port ID value
339 if my_priority is None:
342 result = Stp._cmp_value(rcv_priority.root_id.value,
343 my_priority.root_id.value)
345 result = Stp.compare_root_path(
346 rcv_priority.root_path_cost,
347 my_priority.root_path_cost,
348 rcv_priority.designated_bridge_id.value,
349 my_priority.designated_bridge_id.value,
350 rcv_priority.designated_port_id.value,
351 my_priority.designated_port_id.value)
353 result1 = Stp._cmp_value(
354 rcv_priority.designated_bridge_id.value,
356 my_priority.designated_bridge_id.mac_addr))
357 result2 = Stp._cmp_value(
358 rcv_priority.designated_port_id.value,
359 my_priority.designated_port_id.port_no)
360 if not result1 and not result2:
363 result = Stp._cmp_obj(rcv_times, my_times)
367 def _cmp_value(value1, value2):
368 result = cmp(value1, value2)
377 def _cmp_obj(obj1, obj2):
378 for key in obj1.__dict__.keys():
379 if (not hasattr(obj2, key)
380 or getattr(obj1, key) != getattr(obj2, key)):
385 class Bridge(object):
386 _DEFAULT_VALUE = {'priority': bpdu.DEFAULT_BRIDGE_PRIORITY,
388 'max_age': bpdu.DEFAULT_MAX_AGE,
389 'hello_time': bpdu.DEFAULT_HELLO_TIME,
390 'fwd_delay': bpdu.DEFAULT_FORWARD_DELAY}
392 def __init__(self, dp, logger, config, send_ev_func):
393 super(Bridge, self).__init__()
396 self.dpid_str = {'dpid': dpid_to_str(dp.id)}
397 self.send_event = send_ev_func
400 bridge_conf = config.get('bridge', {})
401 values = self._DEFAULT_VALUE
402 for key, value in bridge_conf.items():
404 system_id = list(dp.ports.values())[0].hw_addr
406 self.bridge_id = BridgeId(values['priority'],
407 values['sys_ext_id'],
409 self.bridge_times = Times(0, # message_age
411 values['hello_time'],
414 self.root_priority = Priority(self.bridge_id, 0, None, None)
415 self.root_times = self.bridge_times
418 self.ports_state = {}
419 self.ports_conf = config.get('ports', {})
420 for ofport in dp.ports.values():
421 self.port_add(ofport)
423 # Install BPDU PacketIn flow. (OpenFlow 1.2/1.3)
424 if dp.ofproto == ofproto_v1_2 or dp.ofproto == ofproto_v1_3:
425 ofctl = OfCtl_v1_2later(self.dp)
426 ofctl.add_bpdu_pkt_in_flow()
429 def is_root_bridge(self):
430 return bool(self.bridge_id.value == self.root_priority.root_id.value)
433 for port in self.ports.values():
436 def port_add(self, ofport):
437 if ofport.port_no <= MAX_PORT_NO:
438 port_conf = self.ports_conf.get(ofport.port_no, {})
439 self.ports[ofport.port_no] = Port(self.dp, self.logger,
440 port_conf, self.send_event,
441 self.recalculate_spanning_tree,
442 self.topology_change_notify,
446 self.ports_state[ofport.port_no] = ofport.state
448 def port_delete(self, ofp_port):
449 self.link_down(ofp_port)
450 self.ports[ofp_port.port_no].delete()
451 del self.ports[ofp_port.port_no]
452 del self.ports_state[ofp_port.port_no]
454 def link_up(self, ofp_port):
455 port = self.ports[ofp_port.port_no]
456 port.up(DESIGNATED_PORT, self.root_priority, self.root_times)
457 self.ports_state[ofp_port.port_no] = ofp_port.state
459 def link_down(self, ofp_port):
460 """ DESIGNATED_PORT/NON_DESIGNATED_PORT: change status to DISABLE.
461 ROOT_PORT: change status to DISABLE and recalculate STP. """
462 port = self.ports[ofp_port.port_no]
463 init_stp_flg = bool(port.role is ROOT_PORT)
465 port.down(PORT_STATE_DISABLE, msg_init=True)
466 self.ports_state[ofp_port.port_no] = ofp_port.state
468 self.recalculate_spanning_tree()
470 def packet_in_handler(self, msg):
472 if dp.ofproto == ofproto_v1_0:
473 in_port_no = msg.in_port
475 assert dp.ofproto == ofproto_v1_2 or dp.ofproto == ofproto_v1_3
477 for match_field in msg.match.fields:
478 if match_field.header == dp.ofproto.OXM_OF_IN_PORT:
479 in_port_no = match_field.value
481 if in_port_no not in self.ports:
484 in_port = self.ports[in_port_no]
485 if in_port.state == PORT_STATE_DISABLE:
488 pkt = packet.Packet(msg.data)
489 if bpdu.ConfigurationBPDUs in pkt:
490 # Received Configuration BPDU.
491 # - If received superior BPDU:
492 # Re-calculates spanning tree.
493 # - If received Topology Change BPDU:
494 # Throws EventTopologyChange.
495 # Forwards Topology Change BPDU.
496 (bpdu_pkt, ) = pkt.get_protocols(bpdu.ConfigurationBPDUs)
497 if bpdu_pkt.message_age > bpdu_pkt.max_age:
498 log_msg = 'Drop BPDU packet which message_age exceeded.'
499 self.logger.debug(log_msg, extra=self.dpid_str)
502 rcv_info, rcv_tc = in_port.rcv_config_bpdu(bpdu_pkt)
504 if rcv_info is SUPERIOR:
505 self.logger.info('[port=%d] Receive superior BPDU.',
506 in_port_no, extra=self.dpid_str)
507 self.recalculate_spanning_tree(init=False)
510 self.send_event(EventTopologyChange(self.dp))
512 if in_port.role is ROOT_PORT:
513 self._forward_tc_bpdu(rcv_tc)
515 elif bpdu.TopologyChangeNotificationBPDUs in pkt:
516 # Received Topology Change Notification BPDU.
517 # Send Topology Change Ack BPDU and throws EventTopologyChange.
519 # Sends Topology Change BPDU from all port.
521 # Sends Topology Change Notification BPDU to root bridge.
522 in_port.transmit_ack_bpdu()
523 self.topology_change_notify(None)
525 elif bpdu.RstBPDUs in pkt:
531 # Received non BPDU packet.
532 # Throws EventPacketIn.
533 self.send_event(EventPacketIn(msg))
535 def recalculate_spanning_tree(self, init=True):
536 """ Re-calculation of spanning tree. """
538 for port in self.ports.values():
539 if port.state is not PORT_STATE_DISABLE:
540 port.down(PORT_STATE_BLOCK, msg_init=init)
542 # Send topology change event.
544 self.send_event(EventTopologyChange(self.dp))
548 self.root_priority = Priority(self.bridge_id, 0, None, None)
549 self.root_times = self.bridge_times
552 self.logger.info('Root bridge.', extra=self.dpid_str)
553 for port_no in self.ports:
554 port_roles[port_no] = DESIGNATED_PORT
558 self.root_times) = self._spanning_tree_algorithm()
561 for port_no, role in port_roles.items():
562 if self.ports[port_no].state is not PORT_STATE_DISABLE:
563 self.ports[port_no].up(role, self.root_priority,
566 def _spanning_tree_algorithm(self):
567 """ Update tree roles.
569 all port is DESIGNATED_PORT.
571 select one ROOT_PORT and some DESIGNATED_PORT,
572 and the other port is set to NON_DESIGNATED_PORT."""
575 root_port = self._select_root_port()
577 if root_port is None:
578 # My bridge is a root bridge.
579 self.logger.info('Root bridge.', extra=self.dpid_str)
580 root_priority = self.root_priority
581 root_times = self.root_times
583 for port_no in self.ports:
584 if self.ports[port_no].state is not PORT_STATE_DISABLE:
585 port_roles[port_no] = DESIGNATED_PORT
587 # Other bridge is a root bridge.
588 self.logger.info('Non root bridge.', extra=self.dpid_str)
589 root_priority = root_port.designated_priority
590 root_times = root_port.designated_times
592 port_roles[root_port.ofport.port_no] = ROOT_PORT
594 d_ports = self._select_designated_port(root_port)
595 for port_no in d_ports:
596 port_roles[port_no] = DESIGNATED_PORT
598 for port in self.ports.values():
599 if port.state is not PORT_STATE_DISABLE:
600 port_roles.setdefault(port.ofport.port_no,
603 return port_roles, root_priority, root_times
605 def _select_root_port(self):
606 """ ROOT_PORT is the nearest port to a root bridge.
607 It is determined by the cost of path, etc. """
610 for port in self.ports.values():
611 root_msg = (self.root_priority if root_port is None
612 else root_port.designated_priority)
613 port_msg = port.designated_priority
614 if port.state is PORT_STATE_DISABLE or port_msg is None:
616 if root_msg.root_id.value > port_msg.root_id.value:
618 elif root_msg.root_id.value == port_msg.root_id.value:
619 if root_msg.designated_bridge_id is None:
622 result = Stp.compare_root_path(
623 port_msg.root_path_cost,
624 root_msg.root_path_cost,
625 port_msg.designated_bridge_id.value,
626 root_msg.designated_bridge_id.value,
627 port_msg.designated_port_id.value,
628 root_msg.designated_port_id.value)
632 if result is SUPERIOR:
637 def _select_designated_port(self, root_port):
638 """ DESIGNATED_PORT is a port of the side near the root bridge
639 of each link. It is determined by the cost of each path, etc
640 same as ROOT_PORT. """
642 root_msg = root_port.designated_priority
644 for port in self.ports.values():
645 port_msg = port.designated_priority
646 if (port.state is PORT_STATE_DISABLE
647 or port.ofport.port_no == root_port.ofport.port_no):
649 if (port_msg is None or
650 (port_msg.root_id.value != root_msg.root_id.value)):
651 d_ports.append(port.ofport.port_no)
653 result = Stp.compare_root_path(
654 root_msg.root_path_cost,
655 port_msg.root_path_cost - port.path_cost,
656 self.bridge_id.value,
657 port_msg.designated_bridge_id.value,
659 port_msg.designated_port_id.value)
660 if result is SUPERIOR:
661 d_ports.append(port.ofport.port_no)
665 def topology_change_notify(self, port_state):
667 if port_state is PORT_STATE_FORWARD:
668 for port in self.ports.values():
669 if port.role is DESIGNATED_PORT:
676 self.send_event(EventTopologyChange(self.dp))
677 if self.is_root_bridge:
678 self._transmit_tc_bpdu()
680 self._transmit_tcn_bpdu()
682 def _transmit_tc_bpdu(self):
683 for port in self.ports.values():
684 port.transmit_tc_bpdu()
686 def _transmit_tcn_bpdu(self):
688 for port in self.ports.values():
689 if port.role is ROOT_PORT:
693 root_port.transmit_tcn_bpdu()
695 def _forward_tc_bpdu(self, fwd_flg):
696 for port in self.ports.values():
697 port.send_tc_flg = fwd_flg
701 _DEFAULT_VALUE = {'priority': bpdu.DEFAULT_PORT_PRIORITY,
702 'path_cost': bpdu.PORT_PATH_COST_10MB,
705 def __init__(self, dp, logger, config, send_ev_func, timeout_func,
706 topology_change_func, bridge_id, bridge_times, ofport):
707 super(Port, self).__init__()
710 self.dpid_str = {'dpid': dpid_to_str(dp.id)}
711 self.config_enable = config.get('enable',
712 self._DEFAULT_VALUE['enable'])
713 self.send_event = send_ev_func
714 self.wait_bpdu_timeout = timeout_func
715 self.topology_change_notify = topology_change_func
716 self.ofctl = (OfCtl_v1_0(dp) if dp.ofproto == ofproto_v1_0
717 else OfCtl_v1_2later(dp))
720 self.bridge_id = bridge_id
722 self.port_priority = None
723 self.port_times = None
724 # ofproto_v1_X_parser.OFPPhyPort data
727 values = self._DEFAULT_VALUE
728 path_costs = {dp.ofproto.OFPPF_10MB_HD: bpdu.PORT_PATH_COST_10MB,
729 dp.ofproto.OFPPF_10MB_FD: bpdu.PORT_PATH_COST_10MB,
730 dp.ofproto.OFPPF_100MB_HD: bpdu.PORT_PATH_COST_100MB,
731 dp.ofproto.OFPPF_100MB_FD: bpdu.PORT_PATH_COST_100MB,
732 dp.ofproto.OFPPF_1GB_HD: bpdu.PORT_PATH_COST_1GB,
733 dp.ofproto.OFPPF_1GB_FD: bpdu.PORT_PATH_COST_1GB,
734 dp.ofproto.OFPPF_10GB_FD: bpdu.PORT_PATH_COST_10GB}
735 for rate in sorted(path_costs, reverse=True):
736 if ofport.curr & rate:
737 values['path_cost'] = path_costs[rate]
739 for key, value in values.items():
741 self.port_id = PortId(values['priority'], ofport.port_no)
742 self.path_cost = values['path_cost']
743 self.state = (None if self.config_enable else PORT_STATE_DISABLE)
746 self.designated_priority = None
747 self.designated_times = None
748 # BPDU handling threads
749 self.send_bpdu_thread = PortThread(self._transmit_bpdu)
750 self.wait_bpdu_thread = PortThread(self._wait_bpdu_timer)
751 self.send_tc_flg = None
752 self.send_tc_timer = None
753 self.send_tcn_flg = None
754 self.wait_timer_event = None
755 # State machine thread
756 self.state_machine = PortThread(self._state_machine)
757 self.state_event = None
759 self.up(DESIGNATED_PORT,
760 Priority(bridge_id, 0, None, None),
763 self.state_machine.start()
764 self.logger.debug('[port=%d] Start port state machine.',
765 self.ofport.port_no, extra=self.dpid_str)
768 self.state_machine.stop()
769 self.send_bpdu_thread.stop()
770 self.wait_bpdu_thread.stop()
771 if self.state_event is not None:
772 self.state_event.set()
773 self.state_event = None
774 if self.wait_timer_event is not None:
775 self.wait_timer_event.set()
776 self.wait_timer_event = None
777 self.logger.debug('[port=%d] Stop port threads.',
778 self.ofport.port_no, extra=self.dpid_str)
780 def up(self, role, root_priority, root_times):
781 """ A port is started in the state of LISTEN. """
782 self.port_priority = root_priority
783 self.port_times = root_times
785 state = (PORT_STATE_LISTEN if self.config_enable
786 else PORT_STATE_DISABLE)
787 self._change_role(role)
788 self._change_status(state)
790 def down(self, state, msg_init=False):
791 """ A port will be in the state of DISABLE or BLOCK,
793 assert (state is PORT_STATE_DISABLE
794 or state is PORT_STATE_BLOCK)
795 if not self.config_enable:
799 self.designated_priority = None
800 self.designated_times = None
802 self._change_role(DESIGNATED_PORT)
803 self._change_status(state)
805 def _state_machine(self):
806 """ Port state machine.
807 Change next status when timer is exceeded
808 or _change_status() method is called."""
809 role_str = {ROOT_PORT: 'ROOT_PORT ',
810 DESIGNATED_PORT: 'DESIGNATED_PORT ',
811 NON_DESIGNATED_PORT: 'NON_DESIGNATED_PORT'}
812 state_str = {PORT_STATE_DISABLE: 'DISABLE',
813 PORT_STATE_BLOCK: 'BLOCK',
814 PORT_STATE_LISTEN: 'LISTEN',
815 PORT_STATE_LEARN: 'LEARN',
816 PORT_STATE_FORWARD: 'FORWARD'}
818 if self.state is PORT_STATE_DISABLE:
819 self.ofctl.set_port_status(self.ofport, self.state)
822 self.logger.info('[port=%d] %s / %s', self.ofport.port_no,
823 role_str[self.role], state_str[self.state],
826 self.state_event = hub.Event()
827 timer = self._get_timer()
829 timeout = hub.Timeout(timer)
831 self.state_event.wait()
832 except hub.Timeout as t:
834 err_msg = 'Internal error. Not my timeout.'
835 raise RyuException(msg=err_msg)
836 new_state = self._get_next_state()
837 self._change_status(new_state, thread_switch=False)
841 self.state_event.wait()
843 self.state_event = None
845 def _get_timer(self):
846 timer = {PORT_STATE_DISABLE: None,
847 PORT_STATE_BLOCK: None,
848 PORT_STATE_LISTEN: self.port_times.forward_delay,
849 PORT_STATE_LEARN: self.port_times.forward_delay,
850 PORT_STATE_FORWARD: None}
851 return timer[self.state]
853 def _get_next_state(self):
854 next_state = {PORT_STATE_DISABLE: None,
855 PORT_STATE_BLOCK: None,
856 PORT_STATE_LISTEN: PORT_STATE_LEARN,
857 PORT_STATE_LEARN: (PORT_STATE_FORWARD
858 if (self.role is ROOT_PORT or
859 self.role is DESIGNATED_PORT)
860 else PORT_STATE_BLOCK),
861 PORT_STATE_FORWARD: None}
862 return next_state[self.state]
864 def _change_status(self, new_state, thread_switch=True):
865 if new_state is not PORT_STATE_DISABLE:
866 self.ofctl.set_port_status(self.ofport, new_state)
868 if(new_state is PORT_STATE_FORWARD
869 or (self.state is PORT_STATE_FORWARD
870 and (new_state is PORT_STATE_DISABLE
871 or new_state is PORT_STATE_BLOCK))):
872 self.topology_change_notify(new_state)
874 if (new_state is PORT_STATE_DISABLE
875 or new_state is PORT_STATE_BLOCK):
876 self.send_tc_flg = False
877 self.send_tc_timer = None
878 self.send_tcn_flg = False
879 self.send_bpdu_thread.stop()
880 elif new_state is PORT_STATE_LISTEN:
881 self.send_bpdu_thread.start()
883 self.state = new_state
884 self.send_event(EventPortStateChange(self.dp, self))
886 if self.state_event is not None:
887 self.state_event.set()
888 self.state_event = None
890 hub.sleep(0) # For thread switching.
892 def _change_role(self, new_role):
893 if self.role is new_role:
896 if (new_role is ROOT_PORT
897 or new_role is NON_DESIGNATED_PORT):
898 self.wait_bpdu_thread.start()
900 assert new_role is DESIGNATED_PORT
901 self.wait_bpdu_thread.stop()
903 def rcv_config_bpdu(self, bpdu_pkt):
904 # Check received BPDU is superior to currently held BPDU.
905 root_id = BridgeId(bpdu_pkt.root_priority,
906 bpdu_pkt.root_system_id_extension,
907 bpdu_pkt.root_mac_address)
908 root_path_cost = bpdu_pkt.root_path_cost
909 designated_bridge_id = BridgeId(bpdu_pkt.bridge_priority,
910 bpdu_pkt.bridge_system_id_extension,
911 bpdu_pkt.bridge_mac_address)
912 designated_port_id = PortId(bpdu_pkt.port_priority,
913 bpdu_pkt.port_number)
915 msg_priority = Priority(root_id, root_path_cost,
916 designated_bridge_id,
918 msg_times = Times(bpdu_pkt.message_age,
921 bpdu_pkt.forward_delay)
923 rcv_info = Stp.compare_bpdu_info(self.designated_priority,
924 self.designated_times,
925 msg_priority, msg_times)
926 if rcv_info is SUPERIOR:
927 self.designated_priority = msg_priority
928 self.designated_times = msg_times
931 if ((rcv_info is SUPERIOR or rcv_info is REPEATED)
932 and (self.role is ROOT_PORT
933 or self.role is NON_DESIGNATED_PORT)):
934 self._update_wait_bpdu_timer()
936 elif rcv_info is INFERIOR and self.role is DESIGNATED_PORT:
939 # Check TopologyChange flag.
942 tc_flag_mask = 0b00000001
943 tcack_flag_mask = 0b10000000
944 if bpdu_pkt.flags & tc_flag_mask:
945 self.logger.debug('[port=%d] receive TopologyChange BPDU.',
946 self.ofport.port_no, extra=self.dpid_str)
948 if bpdu_pkt.flags & tcack_flag_mask:
949 self.logger.debug('[port=%d] receive TopologyChangeAck BPDU.',
950 self.ofport.port_no, extra=self.dpid_str)
951 if self.send_tcn_flg:
952 self.send_tcn_flg = False
954 return rcv_info, rcv_tc
956 def _update_wait_bpdu_timer(self):
957 if self.wait_timer_event is not None:
958 self.wait_timer_event.set()
959 self.wait_timer_event = None
960 self.logger.debug('[port=%d] Wait BPDU timer is updated.',
961 self.ofport.port_no, extra=self.dpid_str)
962 hub.sleep(0) # For thread switching.
964 def _wait_bpdu_timer(self):
968 self.wait_timer_event = hub.Event()
969 message_age = (self.designated_times.message_age
970 if self.designated_times else 0)
971 timer = self.port_times.max_age - message_age
972 timeout = hub.Timeout(timer)
974 self.wait_timer_event.wait()
975 except hub.Timeout as t:
977 err_msg = 'Internal error. Not my timeout.'
978 raise RyuException(msg=err_msg)
979 self.logger.info('[port=%d] Wait BPDU timer is exceeded.',
980 self.ofport.port_no, extra=self.dpid_str)
984 self.wait_timer_event = None
989 if time_exceed: # Bridge.recalculate_spanning_tree
990 hub.spawn(self.wait_bpdu_timeout)
992 def _transmit_bpdu(self):
994 # Send config BPDU packet if port role is DESIGNATED_PORT.
995 if self.role == DESIGNATED_PORT:
996 now = datetime.datetime.today()
997 if self.send_tc_timer and self.send_tc_timer < now:
998 self.send_tc_timer = None
999 self.send_tc_flg = False
1001 if not self.send_tc_flg:
1003 log_msg = '[port=%d] Send Config BPDU.'
1006 log_msg = '[port=%d] Send TopologyChange BPDU.'
1007 bpdu_data = self._generate_config_bpdu(flags)
1008 self.ofctl.send_packet_out(self.ofport.port_no, bpdu_data)
1009 self.logger.debug(log_msg, self.ofport.port_no,
1010 extra=self.dpid_str)
1012 # Send Topology Change Notification BPDU until receive Ack.
1013 if self.send_tcn_flg:
1014 bpdu_data = self._generate_tcn_bpdu()
1015 self.ofctl.send_packet_out(self.ofport.port_no, bpdu_data)
1016 self.logger.debug('[port=%d] Send TopologyChangeNotify BPDU.',
1017 self.ofport.port_no, extra=self.dpid_str)
1019 hub.sleep(self.port_times.hello_time)
1021 def transmit_tc_bpdu(self):
1022 """ Set send_tc_flg to send Topology Change BPDU. """
1023 if not self.send_tc_flg:
1024 timer = datetime.timedelta(seconds=self.port_times.max_age
1025 + self.port_times.forward_delay)
1026 self.send_tc_timer = datetime.datetime.today() + timer
1027 self.send_tc_flg = True
1029 def transmit_ack_bpdu(self):
1030 """ Send Topology Change Ack BPDU. """
1031 ack_flags = 0b10000001
1032 bpdu_data = self._generate_config_bpdu(ack_flags)
1033 self.ofctl.send_packet_out(self.ofport.port_no, bpdu_data)
1035 def transmit_tcn_bpdu(self):
1036 self.send_tcn_flg = True
1038 def _generate_config_bpdu(self, flags):
1039 src_mac = self.ofport.hw_addr
1040 dst_mac = bpdu.BRIDGE_GROUP_ADDRESS
1041 length = (bpdu.bpdu._PACK_LEN + bpdu.ConfigurationBPDUs.PACK_LEN
1042 + llc.llc._PACK_LEN + llc.ControlFormatU._PACK_LEN)
1044 e = ethernet.ethernet(dst_mac, src_mac, length)
1045 l = llc.llc(llc.SAP_BPDU, llc.SAP_BPDU, llc.ControlFormatU())
1046 b = bpdu.ConfigurationBPDUs(
1048 root_priority=self.port_priority.root_id.priority,
1049 root_mac_address=self.port_priority.root_id.mac_addr,
1050 root_path_cost=self.port_priority.root_path_cost + self.path_cost,
1051 bridge_priority=self.bridge_id.priority,
1052 bridge_mac_address=self.bridge_id.mac_addr,
1053 port_priority=self.port_id.priority,
1054 port_number=self.ofport.port_no,
1055 message_age=self.port_times.message_age + 1,
1056 max_age=self.port_times.max_age,
1057 hello_time=self.port_times.hello_time,
1058 forward_delay=self.port_times.forward_delay)
1060 pkt = packet.Packet()
1068 def _generate_tcn_bpdu(self):
1069 src_mac = self.ofport.hw_addr
1070 dst_mac = bpdu.BRIDGE_GROUP_ADDRESS
1071 length = (bpdu.bpdu._PACK_LEN
1072 + bpdu.TopologyChangeNotificationBPDUs.PACK_LEN
1073 + llc.llc._PACK_LEN + llc.ControlFormatU._PACK_LEN)
1075 e = ethernet.ethernet(dst_mac, src_mac, length)
1076 l = llc.llc(llc.SAP_BPDU, llc.SAP_BPDU, llc.ControlFormatU())
1077 b = bpdu.TopologyChangeNotificationBPDUs()
1079 pkt = packet.Packet()
1088 class PortThread(object):
1089 def __init__(self, function):
1090 super(PortThread, self).__init__()
1091 self.function = function
1096 self.thread = hub.spawn(self.function)
1099 if self.thread is not None:
1100 hub.kill(self.thread)
1101 hub.joinall([self.thread])
1105 class BridgeId(object):
1106 def __init__(self, priority, system_id_extension, mac_addr):
1107 super(BridgeId, self).__init__()
1108 self.priority = priority
1109 self.system_id_extension = system_id_extension
1110 self.mac_addr = mac_addr
1111 self.value = bpdu.ConfigurationBPDUs.encode_bridge_id(
1112 priority, system_id_extension, mac_addr)
1115 class PortId(object):
1116 def __init__(self, priority, port_no):
1117 super(PortId, self).__init__()
1118 self.priority = priority
1119 self.port_no = port_no
1120 self.value = bpdu.ConfigurationBPDUs.encode_port_id(priority, port_no)
1123 class Priority(object):
1124 def __init__(self, root_id, root_path_cost,
1125 designated_bridge_id, designated_port_id):
1126 super(Priority, self).__init__()
1127 self.root_id = root_id
1128 self.root_path_cost = root_path_cost
1129 self.designated_bridge_id = designated_bridge_id
1130 self.designated_port_id = designated_port_id
1133 class Times(object):
1134 def __init__(self, message_age, max_age, hello_time, forward_delay):
1135 super(Times, self).__init__()
1136 self.message_age = message_age
1137 self.max_age = max_age
1138 self.hello_time = hello_time
1139 self.forward_delay = forward_delay
1142 class OfCtl_v1_0(object):
1143 def __init__(self, dp):
1144 super(OfCtl_v1_0, self).__init__()
1147 def send_packet_out(self, out_port, data):
1148 actions = [self.dp.ofproto_parser.OFPActionOutput(out_port, 0)]
1149 self.dp.send_packet_out(buffer_id=self.dp.ofproto.OFP_NO_BUFFER,
1150 in_port=self.dp.ofproto.OFPP_CONTROLLER,
1151 actions=actions, data=data)
1153 def set_port_status(self, port, state):
1154 ofproto_parser = self.dp.ofproto_parser
1156 msg = ofproto_parser.OFPPortMod(self.dp, port.port_no, port.hw_addr,
1157 PORT_CONFIG_V1_0[state], mask,
1159 self.dp.send_msg(msg)
1162 class OfCtl_v1_2later(OfCtl_v1_0):
1163 def __init__(self, dp):
1164 super(OfCtl_v1_2later, self).__init__(dp)
1166 def set_port_status(self, port, state):
1167 ofp = self.dp.ofproto
1168 parser = self.dp.ofproto_parser
1169 config = {ofproto_v1_2: PORT_CONFIG_V1_2,
1170 ofproto_v1_3: PORT_CONFIG_V1_3}
1172 # Only turn on the relevant bits defined on OpenFlow 1.2+, otherwise
1173 # some switch that follows the specification strictly will report
1174 # OFPPMFC_BAD_CONFIG error.
1176 msg = parser.OFPPortMod(self.dp, port.port_no, port.hw_addr,
1177 config[ofp][state], mask, port.advertised)
1178 self.dp.send_msg(msg)
1180 if config[ofp][state] & ofp.OFPPC_NO_PACKET_IN:
1181 self.add_no_pkt_in_flow(port.port_no)
1183 self.del_no_pkt_in_flow(port.port_no)
1185 def add_bpdu_pkt_in_flow(self):
1186 ofp = self.dp.ofproto
1187 parser = self.dp.ofproto_parser
1189 match = parser.OFPMatch(eth_dst=bpdu.BRIDGE_GROUP_ADDRESS)
1190 actions = [parser.OFPActionOutput(ofp.OFPP_CONTROLLER,
1191 ofp.OFPCML_NO_BUFFER)]
1192 inst = [parser.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS,
1194 mod = parser.OFPFlowMod(self.dp, priority=BPDU_PKT_IN_PRIORITY,
1195 match=match, instructions=inst)
1196 self.dp.send_msg(mod)
1198 def add_no_pkt_in_flow(self, in_port):
1199 parser = self.dp.ofproto_parser
1201 match = parser.OFPMatch(in_port=in_port)
1202 mod = parser.OFPFlowMod(self.dp, priority=NO_PKT_IN_PRIORITY,
1204 self.dp.send_msg(mod)
1206 def del_no_pkt_in_flow(self, in_port):
1207 ofp = self.dp.ofproto
1208 parser = self.dp.ofproto_parser
1210 match = parser.OFPMatch(in_port=in_port)
1211 mod = parser.OFPFlowMod(self.dp, command=ofp.OFPFC_DELETE_STRICT,
1212 out_port=ofp.OFPP_ANY, out_group=ofp.OFPG_ANY,
1213 priority=NO_PKT_IN_PRIORITY, match=match)
1214 self.dp.send_msg(mod)