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"""
18Bridge Protocol Data Unit(BPDU, IEEE 802.1D) parser/serializer
19http://standards.ieee.org/getieee802/download/802.1D-2004.pdf
20
21
22Configuration BPDUs format
23
24    +----------------------------------------------+---------+
25    |                  Structure                   |  Octet  |
26    +==============================================+=========+
27    | Protocol Identifier = 0000 0000 0000 0000    |  1 - 2  |
28    |                                              |         |
29    +----------------------------------------------+---------+
30    | Protocol Version Identifier = 0000 0000      |  3      |
31    +----------------------------------------------+---------+
32    | BPDU Type = 0000 0000                        |  4      |
33    +----------------------------------------------+---------+
34    | Flags                                        |  5      |
35    +----------------------------------------------+---------+
36    | Root Identifier                              |  6 - 13 |
37    |  include - priority                          |         |
38    |            system ID extension               |         |
39    |            MAC address                       |         |
40    +----------------------------------------------+---------+
41    | Root Path Cost                               | 14 - 17 |
42    |                                              |         |
43    +----------------------------------------------+---------+
44    | Bridge Identifier                            | 18 - 25 |
45    |  include - priority                          |         |
46    |            system ID extension               |         |
47    |            MAC address                       |         |
48    +----------------------------------------------+---------+
49    | Port Identifier                              | 26 - 27 |
50    |  include - priority                          |         |
51    |            port number                       |         |
52    +----------------------------------------------+---------+
53    | Message Age                                  | 28 - 29 |
54    |                                              |         |
55    +----------------------------------------------+---------+
56    | Max Age                                      | 30 - 31 |
57    |                                              |         |
58    +----------------------------------------------+---------+
59    | Hello Time                                   | 32 - 33 |
60    |                                              |         |
61    +----------------------------------------------+---------+
62    | Forward Delay                                | 34 - 35 |
63    |                                              |         |
64    +----------------------------------------------+---------+
65
66
67Topology Change NotificationBPDUs format
68
69    +----------------------------------------------+---------+
70    |                  Structure                   |  Octet  |
71    +==============================================+=========+
72    | Protocol Identifier = 0000 0000 0000 0000    |  1 - 2  |
73    |                                              |         |
74    +----------------------------------------------+---------+
75    | Protocol Version Identifier = 0000 0000      |  3      |
76    +----------------------------------------------+---------+
77    | BPDU Type = 1000 0000                        |  4      |
78    +----------------------------------------------+---------+
79
80
81Rapid Spanning Tree BPDUs(RST BPDUs) format
82
83    +----------------------------------------------+---------+
84    |                  Structure                   |  Octet  |
85    +==============================================+=========+
86    | Protocol Identifier = 0000 0000 0000 0000    |  1 - 2  |
87    |                                              |         |
88    +----------------------------------------------+---------+
89    | Protocol Version Identifier = 0000 0010      |  3      |
90    +----------------------------------------------+---------+
91    | BPDU Type = 0000 0010                        |  4      |
92    +----------------------------------------------+---------+
93    | Flags                                        |  5      |
94    +----------------------------------------------+---------+
95    | Root Identifier                              |  6 - 13 |
96    |  include - priority                          |         |
97    |            system ID extension               |         |
98    |            MAC address                       |         |
99    +----------------------------------------------+---------+
100    | Root Path Cost                               | 14 - 17 |
101    |                                              |         |
102    +----------------------------------------------+---------+
103    | Bridge Identifier                            | 18 - 25 |
104    |  include - priority                          |         |
105    |            system ID extension               |         |
106    |            MAC address                       |         |
107    +----------------------------------------------+---------+
108    | Port Identifier                              | 26 - 27 |
109    |  include - priority                          |         |
110    |            port number                       |         |
111    +----------------------------------------------+---------+
112    | Message Age                                  | 28 - 29 |
113    |                                              |         |
114    +----------------------------------------------+---------+
115    | Max Age                                      | 30 - 31 |
116    |                                              |         |
117    +----------------------------------------------+---------+
118    | Hello Time                                   | 32 - 33 |
119    |                                              |         |
120    +----------------------------------------------+---------+
121    | Forward Delay                                | 34 - 35 |
122    |                                              |         |
123    +----------------------------------------------+---------+
124    | Version 1 Length = 0000 0000                 | 36      |
125    +----------------------------------------------+---------+
126
127"""
128
129
130import binascii
131import struct
132from . import packet_base
133from ryu.lib import addrconv
134
135
136# BPDU destination
137BRIDGE_GROUP_ADDRESS = '01:80:c2:00:00:00'
138
139
140PROTOCOL_IDENTIFIER = 0
141PROTOCOLVERSION_ID_BPDU = 0
142PROTOCOLVERSION_ID_RSTBPDU = 2
143TYPE_CONFIG_BPDU = 0
144TYPE_TOPOLOGY_CHANGE_BPDU = 128
145TYPE_RSTBPDU = 2
146DEFAULT_BRIDGE_PRIORITY = 32768
147DEFAULT_PORT_PRIORITY = 128
148PORT_PATH_COST_100KB = 200000000
149PORT_PATH_COST_1MB = 20000000
150PORT_PATH_COST_10MB = 2000000
151PORT_PATH_COST_100MB = 200000
152PORT_PATH_COST_1GB = 20000
153PORT_PATH_COST_10GB = 2000
154PORT_PATH_COST_100GB = 200
155PORT_PATH_COST_1TB = 20
156PORT_PATH_COST_10TB = 2
157DEFAULT_MAX_AGE = 20
158DEFAULT_HELLO_TIME = 2
159DEFAULT_FORWARD_DELAY = 15
160VERSION_1_LENGTH = 0
161
162
163class bpdu(packet_base.PacketBase):
164    """Bridge Protocol Data Unit(BPDU) header encoder/decoder base class.
165    """
166    _PACK_STR = '!HBB'
167    _PACK_LEN = struct.calcsize(_PACK_STR)
168    _BPDU_TYPES = {}
169
170    _MIN_LEN = _PACK_LEN
171
172    @staticmethod
173    def register_bpdu_type(sub_cls):
174        bpdu._BPDU_TYPES.setdefault(sub_cls.VERSION_ID, {})
175        bpdu._BPDU_TYPES[sub_cls.VERSION_ID][sub_cls.BPDU_TYPE] = sub_cls
176        return sub_cls
177
178    def __init__(self):
179        super(bpdu, self).__init__()
180
181        assert hasattr(self, 'VERSION_ID')
182        assert hasattr(self, 'BPDU_TYPE')
183
184        self._protocol_id = PROTOCOL_IDENTIFIER
185        self._version_id = self.VERSION_ID
186        self._bpdu_type = self.BPDU_TYPE
187
188        if hasattr(self, 'check_parameters'):
189            self.check_parameters()
190
191    @classmethod
192    def parser(cls, buf):
193        assert len(buf) >= cls._PACK_LEN
194        (protocol_id, version_id,
195         bpdu_type) = struct.unpack_from(cls._PACK_STR, buf)
196        assert protocol_id == PROTOCOL_IDENTIFIER
197
198        if (version_id in cls._BPDU_TYPES
199                and bpdu_type in cls._BPDU_TYPES[version_id]):
200            bpdu_cls = cls._BPDU_TYPES[version_id][bpdu_type]
201            assert len(buf[cls._PACK_LEN:]) >= bpdu_cls.PACK_LEN
202            return bpdu_cls.parser(buf[cls._PACK_LEN:])
203        else:
204            # Unknown bpdu version/type.
205            return buf, None, None
206
207    def serialize(self, payload, prev):
208        return struct.pack(bpdu._PACK_STR, self._protocol_id,
209                           self._version_id, self._bpdu_type)
210
211
212@bpdu.register_bpdu_type
213class ConfigurationBPDUs(bpdu):
214    """Configuration BPDUs(IEEE 802.1D) header encoder/decoder class.
215
216    An instance has the following attributes at least.
217    Most of them are same to the on-wire counterparts but in host byte
218    order.
219    __init__ takes the corresponding args in this order.
220
221    ========================== ===============================================
222    Attribute                  Description
223    ========================== ===============================================
224    flags                      | Bit 1: Topology Change flag
225                               | Bits 2 through 7: unused and take the value 0
226                               | Bit 8: Topology Change Acknowledgment flag
227    root_priority              Root Identifier priority \
228                               set 0-61440 in steps of 4096
229    root_system_id_extension   Root Identifier system ID extension
230    root_mac_address           Root Identifier MAC address
231    root_path_cost             Root Path Cost
232    bridge_priority            Bridge Identifier priority \
233                               set 0-61440 in steps of 4096
234    bridge_system_id_extension Bridge Identifier system ID extension
235    bridge_mac_address         Bridge Identifier MAC address
236    port_priority              Port Identifier priority \
237                               set 0-240 in steps of 16
238    port_number                Port Identifier number
239    message_age                Message Age timer value
240    max_age                    Max Age timer value
241    hello_time                 Hello Time timer value
242    forward_delay              Forward Delay timer value
243    ========================== ===============================================
244    """
245
246    VERSION_ID = PROTOCOLVERSION_ID_BPDU
247    BPDU_TYPE = TYPE_CONFIG_BPDU
248    _PACK_STR = '!BQIQHHHHH'
249    PACK_LEN = struct.calcsize(_PACK_STR)
250    _TYPE = {
251        'ascii': [
252            'root_mac_address', "bridge_mac_address"
253        ]
254    }
255
256    _BRIDGE_PRIORITY_STEP = 4096
257    _PORT_PRIORITY_STEP = 16
258    _TIMER_STEP = float(1) / 256
259
260    def __init__(self, flags=0, root_priority=DEFAULT_BRIDGE_PRIORITY,
261                 root_system_id_extension=0,
262                 root_mac_address='00:00:00:00:00:00',
263                 root_path_cost=0, bridge_priority=DEFAULT_BRIDGE_PRIORITY,
264                 bridge_system_id_extension=0,
265                 bridge_mac_address='00:00:00:00:00:00',
266                 port_priority=DEFAULT_PORT_PRIORITY, port_number=0,
267                 message_age=0, max_age=DEFAULT_MAX_AGE,
268                 hello_time=DEFAULT_HELLO_TIME,
269                 forward_delay=DEFAULT_FORWARD_DELAY):
270        self.flags = flags
271        self.root_priority = root_priority
272        self.root_system_id_extension = root_system_id_extension
273        self.root_mac_address = root_mac_address
274        self.root_path_cost = root_path_cost
275        self.bridge_priority = bridge_priority
276        self.bridge_system_id_extension = bridge_system_id_extension
277        self.bridge_mac_address = bridge_mac_address
278        self.port_priority = port_priority
279        self.port_number = port_number
280        self.message_age = message_age
281        self.max_age = max_age
282        self.hello_time = hello_time
283        self.forward_delay = forward_delay
284
285        super(ConfigurationBPDUs, self).__init__()
286
287    def check_parameters(self):
288        assert (self.flags >> 1 & 0b111111) == 0
289        assert self.root_priority % self._BRIDGE_PRIORITY_STEP == 0
290        assert self.bridge_priority % self._BRIDGE_PRIORITY_STEP == 0
291        assert self.port_priority % self._PORT_PRIORITY_STEP == 0
292        assert self.message_age % self._TIMER_STEP == 0
293        assert self.max_age % self._TIMER_STEP == 0
294        assert self.hello_time % self._TIMER_STEP == 0
295        assert self.forward_delay % self._TIMER_STEP == 0
296
297    @classmethod
298    def parser(cls, buf):
299        (flags, root_id, root_path_cost, bridge_id,
300         port_id, message_age, max_age, hello_time,
301         forward_delay) = struct.unpack_from(ConfigurationBPDUs._PACK_STR, buf)
302
303        (root_priority,
304         root_system_id_extension,
305         root_mac_address) = cls._decode_bridge_id(root_id)
306        (bridge_priority,
307         bridge_system_id_extension,
308         bridge_mac_address) = cls._decode_bridge_id(bridge_id)
309        (port_priority,
310         port_number) = cls._decode_port_id(port_id)
311
312        return (cls(flags, root_priority, root_system_id_extension,
313                    root_mac_address, root_path_cost,
314                    bridge_priority, bridge_system_id_extension,
315                    bridge_mac_address, port_priority, port_number,
316                    cls._decode_timer(message_age),
317                    cls._decode_timer(max_age),
318                    cls._decode_timer(hello_time),
319                    cls._decode_timer(forward_delay)),
320                None, buf[ConfigurationBPDUs.PACK_LEN:])
321
322    def serialize(self, payload, prev):
323        base = super(ConfigurationBPDUs, self).serialize(payload, prev)
324
325        root_id = self.encode_bridge_id(self.root_priority,
326                                        self.root_system_id_extension,
327                                        self.root_mac_address)
328        bridge_id = self.encode_bridge_id(self.bridge_priority,
329                                          self.bridge_system_id_extension,
330                                          self.bridge_mac_address)
331        port_id = self.encode_port_id(self.port_priority,
332                                      self.port_number)
333        sub = struct.pack(ConfigurationBPDUs._PACK_STR,
334                          self.flags,
335                          root_id,
336                          self.root_path_cost,
337                          bridge_id,
338                          port_id,
339                          self._encode_timer(self.message_age),
340                          self._encode_timer(self.max_age),
341                          self._encode_timer(self.hello_time),
342                          self._encode_timer(self.forward_delay))
343
344        return base + sub
345
346    @staticmethod
347    def _decode_bridge_id(bridge_id):
348        priority = (bridge_id >> 48) & 0xf000
349        system_id_extension = (bridge_id >> 48) & 0xfff
350        mac_addr = bridge_id & 0xffffffffffff
351
352        mac_addr_list = [format((mac_addr >> (8 * i)) & 0xff, '02x')
353                         for i in range(0, 6)]
354        mac_addr_list.reverse()
355        mac_address_bin = binascii.a2b_hex(''.join(mac_addr_list))
356        mac_address = addrconv.mac.bin_to_text(mac_address_bin)
357
358        return priority, system_id_extension, mac_address
359
360    @staticmethod
361    def encode_bridge_id(priority, system_id_extension, mac_address):
362        mac_addr = int(binascii.hexlify(addrconv.mac.text_to_bin(mac_address)),
363                       16)
364        return ((priority + system_id_extension) << 48) + mac_addr
365
366    @staticmethod
367    def _decode_port_id(port_id):
368        priority = port_id >> 8 & 0xf0
369        port_number = port_id & 0xfff
370        return priority, port_number
371
372    @staticmethod
373    def encode_port_id(priority, port_number):
374        return (priority << 8) + port_number
375
376    @staticmethod
377    def _decode_timer(timer):
378        return timer / float(0x100)
379
380    @staticmethod
381    def _encode_timer(timer):
382        return int(timer) * 0x100
383
384
385@bpdu.register_bpdu_type
386class TopologyChangeNotificationBPDUs(bpdu):
387    """Topology Change Notification BPDUs(IEEE 802.1D)
388    header encoder/decoder class.
389    """
390
391    VERSION_ID = PROTOCOLVERSION_ID_BPDU
392    BPDU_TYPE = TYPE_TOPOLOGY_CHANGE_BPDU
393    _PACK_STR = ''
394    PACK_LEN = struct.calcsize(_PACK_STR)
395
396    def __init__(self):
397        super(TopologyChangeNotificationBPDUs, self).__init__()
398
399    @classmethod
400    def parser(cls, buf):
401        return cls(), None, buf[bpdu._PACK_LEN:]
402
403
404@bpdu.register_bpdu_type
405class RstBPDUs(ConfigurationBPDUs):
406    """Rapid Spanning Tree BPDUs(RST BPDUs, IEEE 802.1D)
407    header encoder/decoder class.
408
409    An instance has the following attributes at least.
410    Most of them are same to the on-wire counterparts but in host byte
411    order.
412    __init__ takes the corresponding args in this order.
413
414    ========================== ===========================================
415    Attribute                  Description
416    ========================== ===========================================
417    flags                      | Bit 1: Topology Change flag
418                               | Bit 2: Proposal flag
419                               | Bits 3 and 4: Port Role
420                               | Bit 5: Learning flag
421                               | Bit 6: Forwarding flag
422                               | Bit 7: Agreement flag
423                               | Bit 8: Topology Change Acknowledgment flag
424    root_priority              Root Identifier priority \
425                               set 0-61440 in steps of 4096
426    root_system_id_extension   Root Identifier system ID extension
427    root_mac_address           Root Identifier MAC address
428    root_path_cost             Root Path Cost
429    bridge_priority            Bridge Identifier priority \
430                               set 0-61440 in steps of 4096
431    bridge_system_id_extension Bridge Identifier system ID extension
432    bridge_mac_address         Bridge Identifier MAC address
433    port_priority              Port Identifier priority \
434                               set 0-240 in steps of 16
435    port_number                Port Identifier number
436    message_age                Message Age timer value
437    max_age                    Max Age timer value
438    hello_time                 Hello Time timer value
439    forward_delay              Forward Delay timer value
440    ========================== ===========================================
441    """
442
443    VERSION_ID = PROTOCOLVERSION_ID_RSTBPDU
444    BPDU_TYPE = TYPE_RSTBPDU
445    _PACK_STR = '!B'
446    PACK_LEN = struct.calcsize(_PACK_STR)
447
448    def __init__(self, flags=0, root_priority=DEFAULT_BRIDGE_PRIORITY,
449                 root_system_id_extension=0,
450                 root_mac_address='00:00:00:00:00:00',
451                 root_path_cost=0, bridge_priority=DEFAULT_BRIDGE_PRIORITY,
452                 bridge_system_id_extension=0,
453                 bridge_mac_address='00:00:00:00:00:00',
454                 port_priority=DEFAULT_PORT_PRIORITY, port_number=0,
455                 message_age=0, max_age=DEFAULT_MAX_AGE,
456                 hello_time=DEFAULT_HELLO_TIME,
457                 forward_delay=DEFAULT_FORWARD_DELAY):
458        self._version_1_length = VERSION_1_LENGTH
459
460        super(RstBPDUs, self).__init__(flags, root_priority,
461                                       root_system_id_extension,
462                                       root_mac_address, root_path_cost,
463                                       bridge_priority,
464                                       bridge_system_id_extension,
465                                       bridge_mac_address,
466                                       port_priority, port_number,
467                                       message_age, max_age,
468                                       hello_time, forward_delay)
469
470    def check_parameters(self):
471        assert self.root_priority % self._BRIDGE_PRIORITY_STEP == 0
472        assert self.bridge_priority % self._BRIDGE_PRIORITY_STEP == 0
473        assert self.port_priority % self._PORT_PRIORITY_STEP == 0
474        assert self.message_age % self._TIMER_STEP == 0
475        assert self.max_age % self._TIMER_STEP == 0
476        assert self.hello_time % self._TIMER_STEP == 0
477        assert self.forward_delay % self._TIMER_STEP == 0
478
479    @classmethod
480    def parser(cls, buf):
481        get_cls, next_type, buf = super(RstBPDUs, cls).parser(buf)
482
483        (version_1_length,) = struct.unpack_from(RstBPDUs._PACK_STR, buf)
484        assert version_1_length == VERSION_1_LENGTH
485
486        return get_cls, next_type, buf[RstBPDUs.PACK_LEN:]
487
488    def serialize(self, payload, prev):
489        base = super(RstBPDUs, self).serialize(payload, prev)
490        sub = struct.pack(RstBPDUs._PACK_STR, self._version_1_length)
491        return base + sub
492