backing up
[vsorcdistro/.git] / ryu / build / lib.linux-armv7l-2.7 / ryu / lib / stplib.py
1 # Copyright (C) 2013 Nippon Telegraph and Telephone Corporation.
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
12 # implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16
17 import datetime
18 import logging
19
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
37
38
39 MAX_PORT_NO = 0xfff
40
41 # for OpenFlow 1.2/1.3
42 BPDU_PKT_IN_PRIORITY = 0xffff
43 NO_PKT_IN_PRIORITY = 0xfffe
44
45
46 # Result of compared config BPDU priority.
47 SUPERIOR = -1
48 REPEATED = 0
49 INFERIOR = 1
50
51 # Port role
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.
55
56 """ How to decide the port roles.
57
58      Root bridge:
59        a bridge has smallest bridge ID is chosen as a root.
60        it sends original config BPDU.
61      Non Root bridge:
62        forwards config BPDU received from the root bridge.
63
64                    +-----------------------+
65                    |      Root bridge      |
66                    +-----------------------+
67                      (D)               (D)
68                       |                 |
69                       |                 |
70                      (R)               (R)
71         +-----------------+          +-----------------+
72         | Non Root bridge |(D)---(ND)| Non Root bridge |
73         +-----------------+          +-----------------+
74
75      ROOT_PORT(R):
76        the nearest port to a root bridge of the bridge.
77        it is determined by the cost of the path, etc.
78      DESIGNATED_PORT(D):
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.
83 """
84
85
86 # Port state
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
93 PORT_STATE_BLOCK = 1
94 PORT_STATE_LISTEN = 2
95 PORT_STATE_LEARN = 3
96 PORT_STATE_FORWARD = 4
97
98 # for OpenFlow 1.0
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}
110
111 # for OpenFlow 1.2
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}
119
120 # for OpenFlow 1.3
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}
128
129 """ Port state machine
130
131     +------------------------<--------------------------+
132     |                                                   |*2
133     +--> [BLOCK] -----+--> [LISTEN] ----> [LEARN] ------+----> [FORWARD]
134                   *3  |        |    15sec    |    15sec   *1       |
135                       |        |*3           |*3                   |*3
136                       +----<---+------<------+----------<----------+
137
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.
141
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]
146 """
147
148
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__()
154         self.dp = dp
155
156
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__()
161         self.dp = dp
162         self.port_no = port.ofport.port_no
163         self.port_state = port.state
164
165
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__()
170         self.msg = msg
171
172
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
176 def cmp(a, b):
177     return (a > b) - (a < b)
178
179
180 class Stp(app_manager.RyuApp):
181     """ STP(spanning tree) library. """
182
183     OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION,
184                     ofproto_v1_2.OFP_VERSION,
185                     ofproto_v1_3.OFP_VERSION]
186
187     def __init__(self):
188         super(Stp, self).__init__()
189         self.name = 'stplib'
190         self._set_logger()
191         self.config = {}
192         self.bridge_list = {}
193
194     def close(self):
195         for dpid in self.bridge_list:
196             self._unregister_bridge(dpid)
197
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)
204
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.
209
210              config = {<dpid>: {'bridge': {'priority': <value>,
211                                            'sys_ext_id': <value>,
212                                            'max_age': <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>: {...},,,}}
219                        <dpid>: {...},
220                        <dpid>: {...},,,}
221
222              NOTE: You may omit each field.
223                     If omitted, a default value is set up.
224                    It becomes effective when a bridge starts.
225
226              Default values:
227              ------------------------------------------------------
228              | bridge | priority   | bpdu.DEFAULT_BRIDGE_PRIORITY |
229              |        | sys_ext_id | 0                            |
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.)  |
237              |        | enable     | True                         |
238              ------------------------------------------------------
239         """
240         assert isinstance(config, dict)
241         self.config = config
242
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)
251
252     def _register_bridge(self, dp):
253         self._unregister_bridge(dp.id)
254
255         dpid_str = {'dpid': dpid_to_str(dp.id)}
256         self.logger.info('Join as stp bridge.', extra=dpid_str)
257         try:
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)
263             return
264
265         self.bridge_list[dp.id] = bridge
266
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)})
273
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)
279
280     @set_ev_cls(ofp_event.EventOFPPortStatus, handler.MAIN_DISPATCHER)
281     def port_status_handler(self, ev):
282         dp = ev.msg.datapath
283         dpid_str = {'dpid': dpid_to_str(dp.id)}
284         port = ev.msg.desc
285         reason = ev.msg.reason
286         link_down_flg = port.state & 0b1
287
288         if dp.id in self.bridge_list:
289             bridge = self.bridge_list[dp.id]
290
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)
299             else:
300                 assert reason is dp.ofproto.OFPPR_MODIFY
301                 if bridge.ports_state[port.port_no] == port.state:
302                     # Do nothing
303                     self.logger.debug('[port=%d] Link status not changed.',
304                                       port.port_no, extra=dpid_str)
305                     return
306                 if link_down_flg:
307                     self.logger.info('[port=%d] Link down.',
308                                      port.port_no, extra=dpid_str)
309                     bridge.link_down(port)
310                 else:
311                     self.logger.info('[port=%d] Link up.',
312                                      port.port_no, extra=dpid_str)
313                     bridge.link_up(port)
314
315     @staticmethod
316     def compare_root_path(path_cost1, path_cost2, bridge_id1, bridge_id2,
317                           port_id1, port_id2):
318         """ Decide the port of the side near a root bridge.
319             It is compared by the following priorities.
320              1. root path cost
321              2. designated bridge ID value
322              3. designated port ID value """
323         result = Stp._cmp_value(path_cost1, path_cost2)
324         if not result:
325             result = Stp._cmp_value(bridge_id1, bridge_id2)
326             if not result:
327                 result = Stp._cmp_value(port_id1, port_id2)
328         return result
329
330     @staticmethod
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
335              - root path cost
336              - designated bridge ID value
337              - designated port ID value
338              - times """
339         if my_priority is None:
340             result = SUPERIOR
341         else:
342             result = Stp._cmp_value(rcv_priority.root_id.value,
343                                     my_priority.root_id.value)
344             if not result:
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)
352                 if not result:
353                     result1 = Stp._cmp_value(
354                         rcv_priority.designated_bridge_id.value,
355                         mac.haddr_to_int(
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:
361                         result = SUPERIOR
362                     else:
363                         result = Stp._cmp_obj(rcv_times, my_times)
364         return result
365
366     @staticmethod
367     def _cmp_value(value1, value2):
368         result = cmp(value1, value2)
369         if result < 0:
370             return SUPERIOR
371         elif result == 0:
372             return REPEATED
373         else:
374             return INFERIOR
375
376     @staticmethod
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)):
381                 return SUPERIOR
382         return REPEATED
383
384
385 class Bridge(object):
386     _DEFAULT_VALUE = {'priority': bpdu.DEFAULT_BRIDGE_PRIORITY,
387                       'sys_ext_id': 0,
388                       'max_age': bpdu.DEFAULT_MAX_AGE,
389                       'hello_time': bpdu.DEFAULT_HELLO_TIME,
390                       'fwd_delay': bpdu.DEFAULT_FORWARD_DELAY}
391
392     def __init__(self, dp, logger, config, send_ev_func):
393         super(Bridge, self).__init__()
394         self.dp = dp
395         self.logger = logger
396         self.dpid_str = {'dpid': dpid_to_str(dp.id)}
397         self.send_event = send_ev_func
398
399         # Bridge data
400         bridge_conf = config.get('bridge', {})
401         values = self._DEFAULT_VALUE
402         for key, value in bridge_conf.items():
403             values[key] = value
404         system_id = list(dp.ports.values())[0].hw_addr
405
406         self.bridge_id = BridgeId(values['priority'],
407                                   values['sys_ext_id'],
408                                   system_id)
409         self.bridge_times = Times(0,  # message_age
410                                   values['max_age'],
411                                   values['hello_time'],
412                                   values['fwd_delay'])
413         # Root bridge data
414         self.root_priority = Priority(self.bridge_id, 0, None, None)
415         self.root_times = self.bridge_times
416         # Ports
417         self.ports = {}
418         self.ports_state = {}
419         self.ports_conf = config.get('ports', {})
420         for ofport in dp.ports.values():
421             self.port_add(ofport)
422
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()
427
428     @property
429     def is_root_bridge(self):
430         return bool(self.bridge_id.value == self.root_priority.root_id.value)
431
432     def delete(self):
433         for port in self.ports.values():
434             port.delete()
435
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,
443                                               self.bridge_id,
444                                               self.bridge_times,
445                                               ofport)
446             self.ports_state[ofport.port_no] = ofport.state
447
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]
453
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
458
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)
464
465         port.down(PORT_STATE_DISABLE, msg_init=True)
466         self.ports_state[ofp_port.port_no] = ofp_port.state
467         if init_stp_flg:
468             self.recalculate_spanning_tree()
469
470     def packet_in_handler(self, msg):
471         dp = msg.datapath
472         if dp.ofproto == ofproto_v1_0:
473             in_port_no = msg.in_port
474         else:
475             assert dp.ofproto == ofproto_v1_2 or dp.ofproto == ofproto_v1_3
476             in_port_no = None
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
480                     break
481         if in_port_no not in self.ports:
482             return
483
484         in_port = self.ports[in_port_no]
485         if in_port.state == PORT_STATE_DISABLE:
486             return
487
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)
500                 return
501
502             rcv_info, rcv_tc = in_port.rcv_config_bpdu(bpdu_pkt)
503
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)
508
509             elif rcv_tc:
510                 self.send_event(EventTopologyChange(self.dp))
511
512             if in_port.role is ROOT_PORT:
513                 self._forward_tc_bpdu(rcv_tc)
514
515         elif bpdu.TopologyChangeNotificationBPDUs in pkt:
516             # Received Topology Change Notification BPDU.
517             # Send Topology Change Ack BPDU and throws EventTopologyChange.
518             # - Root bridge:
519             #    Sends Topology Change BPDU from all port.
520             # - Non root bridge:
521             #    Sends Topology Change Notification BPDU to root bridge.
522             in_port.transmit_ack_bpdu()
523             self.topology_change_notify(None)
524
525         elif bpdu.RstBPDUs in pkt:
526             # Received Rst BPDU.
527             # TODO: RSTP
528             pass
529
530         else:
531             # Received non BPDU packet.
532             # Throws EventPacketIn.
533             self.send_event(EventPacketIn(msg))
534
535     def recalculate_spanning_tree(self, init=True):
536         """ Re-calculation of spanning tree. """
537         # All port down.
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)
541
542         # Send topology change event.
543         if init:
544             self.send_event(EventTopologyChange(self.dp))
545
546         # Update tree roles.
547         port_roles = {}
548         self.root_priority = Priority(self.bridge_id, 0, None, None)
549         self.root_times = self.bridge_times
550
551         if init:
552             self.logger.info('Root bridge.', extra=self.dpid_str)
553             for port_no in self.ports:
554                 port_roles[port_no] = DESIGNATED_PORT
555         else:
556             (port_roles,
557              self.root_priority,
558              self.root_times) = self._spanning_tree_algorithm()
559
560         # All port up.
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,
564                                        self.root_times)
565
566     def _spanning_tree_algorithm(self):
567         """ Update tree roles.
568              - Root bridge:
569                 all port is DESIGNATED_PORT.
570              - Non root bridge:
571                 select one ROOT_PORT and some DESIGNATED_PORT,
572                 and the other port is set to NON_DESIGNATED_PORT."""
573         port_roles = {}
574
575         root_port = self._select_root_port()
576
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
582
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
586         else:
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
591
592             port_roles[root_port.ofport.port_no] = ROOT_PORT
593
594             d_ports = self._select_designated_port(root_port)
595             for port_no in d_ports:
596                 port_roles[port_no] = DESIGNATED_PORT
597
598             for port in self.ports.values():
599                 if port.state is not PORT_STATE_DISABLE:
600                     port_roles.setdefault(port.ofport.port_no,
601                                           NON_DESIGNATED_PORT)
602
603         return port_roles, root_priority, root_times
604
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. """
608         root_port = None
609
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:
615                 continue
616             if root_msg.root_id.value > port_msg.root_id.value:
617                 result = SUPERIOR
618             elif root_msg.root_id.value == port_msg.root_id.value:
619                 if root_msg.designated_bridge_id is None:
620                     result = INFERIOR
621                 else:
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)
629             else:
630                 result = INFERIOR
631
632             if result is SUPERIOR:
633                 root_port = port
634
635         return root_port
636
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. """
641         d_ports = []
642         root_msg = root_port.designated_priority
643
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):
648                 continue
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)
652             else:
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,
658                     port.port_id.value,
659                     port_msg.designated_port_id.value)
660                 if result is SUPERIOR:
661                     d_ports.append(port.ofport.port_no)
662
663         return d_ports
664
665     def topology_change_notify(self, port_state):
666         notice = False
667         if port_state is PORT_STATE_FORWARD:
668             for port in self.ports.values():
669                 if port.role is DESIGNATED_PORT:
670                     notice = True
671                     break
672         else:
673             notice = True
674
675         if notice:
676             self.send_event(EventTopologyChange(self.dp))
677             if self.is_root_bridge:
678                 self._transmit_tc_bpdu()
679             else:
680                 self._transmit_tcn_bpdu()
681
682     def _transmit_tc_bpdu(self):
683         for port in self.ports.values():
684             port.transmit_tc_bpdu()
685
686     def _transmit_tcn_bpdu(self):
687         root_port = None
688         for port in self.ports.values():
689             if port.role is ROOT_PORT:
690                 root_port = port
691                 break
692         if root_port:
693             root_port.transmit_tcn_bpdu()
694
695     def _forward_tc_bpdu(self, fwd_flg):
696         for port in self.ports.values():
697             port.send_tc_flg = fwd_flg
698
699
700 class Port(object):
701     _DEFAULT_VALUE = {'priority': bpdu.DEFAULT_PORT_PRIORITY,
702                       'path_cost': bpdu.PORT_PATH_COST_10MB,
703                       'enable': True}
704
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__()
708         self.dp = dp
709         self.logger = logger
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))
718
719         # Bridge data
720         self.bridge_id = bridge_id
721         # Root bridge data
722         self.port_priority = None
723         self.port_times = None
724         # ofproto_v1_X_parser.OFPPhyPort data
725         self.ofport = ofport
726         # Port 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]
738                 break
739         for key, value in values.items():
740             values[key] = value
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)
744         self.role = None
745         # Receive BPDU data
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
758
759         self.up(DESIGNATED_PORT,
760                 Priority(bridge_id, 0, None, None),
761                 bridge_times)
762
763         self.state_machine.start()
764         self.logger.debug('[port=%d] Start port state machine.',
765                           self.ofport.port_no, extra=self.dpid_str)
766
767     def delete(self):
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)
779
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
784
785         state = (PORT_STATE_LISTEN if self.config_enable
786                  else PORT_STATE_DISABLE)
787         self._change_role(role)
788         self._change_status(state)
789
790     def down(self, state, msg_init=False):
791         """ A port will be in the state of DISABLE or BLOCK,
792              and be stopped.  """
793         assert (state is PORT_STATE_DISABLE
794                 or state is PORT_STATE_BLOCK)
795         if not self.config_enable:
796             return
797
798         if msg_init:
799             self.designated_priority = None
800             self.designated_times = None
801
802         self._change_role(DESIGNATED_PORT)
803         self._change_status(state)
804
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'}
817
818         if self.state is PORT_STATE_DISABLE:
819             self.ofctl.set_port_status(self.ofport, self.state)
820
821         while True:
822             self.logger.info('[port=%d] %s / %s', self.ofport.port_no,
823                              role_str[self.role], state_str[self.state],
824                              extra=self.dpid_str)
825
826             self.state_event = hub.Event()
827             timer = self._get_timer()
828             if timer:
829                 timeout = hub.Timeout(timer)
830                 try:
831                     self.state_event.wait()
832                 except hub.Timeout as t:
833                     if t is not timeout:
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)
838                 finally:
839                     timeout.cancel()
840             else:
841                 self.state_event.wait()
842
843             self.state_event = None
844
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]
852
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]
863
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)
867
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)
873
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()
882
883         self.state = new_state
884         self.send_event(EventPortStateChange(self.dp, self))
885
886         if self.state_event is not None:
887             self.state_event.set()
888             self.state_event = None
889         if thread_switch:
890             hub.sleep(0)  # For thread switching.
891
892     def _change_role(self, new_role):
893         if self.role is new_role:
894             return
895         self.role = new_role
896         if (new_role is ROOT_PORT
897                 or new_role is NON_DESIGNATED_PORT):
898             self.wait_bpdu_thread.start()
899         else:
900             assert new_role is DESIGNATED_PORT
901             self.wait_bpdu_thread.stop()
902
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)
914
915         msg_priority = Priority(root_id, root_path_cost,
916                                 designated_bridge_id,
917                                 designated_port_id)
918         msg_times = Times(bpdu_pkt.message_age,
919                           bpdu_pkt.max_age,
920                           bpdu_pkt.hello_time,
921                           bpdu_pkt.forward_delay)
922
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
929
930         chk_flg = False
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()
935             chk_flg = True
936         elif rcv_info is INFERIOR and self.role is DESIGNATED_PORT:
937             chk_flg = True
938
939         # Check TopologyChange flag.
940         rcv_tc = False
941         if chk_flg:
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)
947                 rcv_tc = True
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
953
954         return rcv_info, rcv_tc
955
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.
963
964     def _wait_bpdu_timer(self):
965         time_exceed = False
966
967         while True:
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)
973             try:
974                 self.wait_timer_event.wait()
975             except hub.Timeout as t:
976                 if t is not timeout:
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)
981                 time_exceed = True
982             finally:
983                 timeout.cancel()
984                 self.wait_timer_event = None
985
986             if time_exceed:
987                 break
988
989         if time_exceed:  # Bridge.recalculate_spanning_tree
990             hub.spawn(self.wait_bpdu_timeout)
991
992     def _transmit_bpdu(self):
993         while True:
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
1000
1001                 if not self.send_tc_flg:
1002                     flags = 0b00000000
1003                     log_msg = '[port=%d] Send Config BPDU.'
1004                 else:
1005                     flags = 0b00000001
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)
1011
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)
1018
1019             hub.sleep(self.port_times.hello_time)
1020
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
1028
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)
1034
1035     def transmit_tcn_bpdu(self):
1036         self.send_tcn_flg = True
1037
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)
1043
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(
1047             flags=flags,
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)
1059
1060         pkt = packet.Packet()
1061         pkt.add_protocol(e)
1062         pkt.add_protocol(l)
1063         pkt.add_protocol(b)
1064         pkt.serialize()
1065
1066         return pkt.data
1067
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)
1074
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()
1078
1079         pkt = packet.Packet()
1080         pkt.add_protocol(e)
1081         pkt.add_protocol(l)
1082         pkt.add_protocol(b)
1083         pkt.serialize()
1084
1085         return pkt.data
1086
1087
1088 class PortThread(object):
1089     def __init__(self, function):
1090         super(PortThread, self).__init__()
1091         self.function = function
1092         self.thread = None
1093
1094     def start(self):
1095         self.stop()
1096         self.thread = hub.spawn(self.function)
1097
1098     def stop(self):
1099         if self.thread is not None:
1100             hub.kill(self.thread)
1101             hub.joinall([self.thread])
1102             self.thread = None
1103
1104
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)
1113
1114
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)
1121
1122
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
1131
1132
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
1140
1141
1142 class OfCtl_v1_0(object):
1143     def __init__(self, dp):
1144         super(OfCtl_v1_0, self).__init__()
1145         self.dp = dp
1146
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)
1152
1153     def set_port_status(self, port, state):
1154         ofproto_parser = self.dp.ofproto_parser
1155         mask = 0b1111111
1156         msg = ofproto_parser.OFPPortMod(self.dp, port.port_no, port.hw_addr,
1157                                         PORT_CONFIG_V1_0[state], mask,
1158                                         port.advertised)
1159         self.dp.send_msg(msg)
1160
1161
1162 class OfCtl_v1_2later(OfCtl_v1_0):
1163     def __init__(self, dp):
1164         super(OfCtl_v1_2later, self).__init__(dp)
1165
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}
1171
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.
1175         mask = 0b1100101
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)
1179
1180         if config[ofp][state] & ofp.OFPPC_NO_PACKET_IN:
1181             self.add_no_pkt_in_flow(port.port_no)
1182         else:
1183             self.del_no_pkt_in_flow(port.port_no)
1184
1185     def add_bpdu_pkt_in_flow(self):
1186         ofp = self.dp.ofproto
1187         parser = self.dp.ofproto_parser
1188
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,
1193                                              actions)]
1194         mod = parser.OFPFlowMod(self.dp, priority=BPDU_PKT_IN_PRIORITY,
1195                                 match=match, instructions=inst)
1196         self.dp.send_msg(mod)
1197
1198     def add_no_pkt_in_flow(self, in_port):
1199         parser = self.dp.ofproto_parser
1200
1201         match = parser.OFPMatch(in_port=in_port)
1202         mod = parser.OFPFlowMod(self.dp, priority=NO_PKT_IN_PRIORITY,
1203                                 match=match)
1204         self.dp.send_msg(mod)
1205
1206     def del_no_pkt_in_flow(self, in_port):
1207         ofp = self.dp.ofproto
1208         parser = self.dp.ofproto_parser
1209
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)