Passed
Pull Request — master (#412)
by
unknown
01:59
created

VLAN.pack()   C

Complexity

Conditions 8

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 35

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 18
ccs 2
cts 8
cp 0.25
rs 6.6666
cc 8
crap 35
1
"""Basic Network packet types.
2
3
Defines and Implements Basic Network packet types , such as Ethertnet and LLDP.
4
"""
5
6 1
from pyof.foundation.base import GenericStruct
7 1
from pyof.foundation.basic_types import (
8 1
    BinaryData, HWAddress, IPAddress, UBInt8, UBInt16)
9
from pyof.foundation.constants import VLAN_TPID
10 1
from pyof.foundation.exceptions import PackException
11
12
__all__ = ('Ethernet', 'GenericTLV', 'IPv4', 'VLAN', 'TLVWithSubType', 'LLDP')
13 1
14
15
class VLAN(GenericStruct):
16
    """802.1q VLAN header."""
17
18
    #: tpid (:class:`UBInt16`): Tag Protocol Identifier
19
    tpid = UBInt16(VLAN_TPID)
20
    #: _tci (:class:`UBInt16`): Tag Control Information - has the
21
    #: Priority Code Point, DEI/CFI bit and the VLAN ID
22
    _tci = UBInt16()
23
24
    def __init__(self, pcp=None, cfi=None, vid=None):
25
        """The constructor receives the parameters below.
26
27
        Args:
28
            tpid (int): Tag Protocol Identifier. Defaults to 0x8100 for 802.1q.
29
            pcp (int): 802.1p Priority Code Point. Defaults to 0 for Best
30
                       Effort Queue.
31 1
            cfi (int): Canonical Format Indicator. Defaults to 0 for Ethernet.
32
            vid (int): VLAN ID. If no VLAN is specified, value is 0.
33 1
        """
34
        super().__init__()
35 1
        self.tpid = VLAN_TPID
36
        self.pcp = pcp
37 1
        self.cfi = cfi
38
        self.vid = vid
39 1
40
    def pack(self, value=None):
41
        """Pack the struct in a binary representation.
42
43
        Merge some fields to ensure correct packing.
44
45
        Returns:
46
            bytes: Binary representation of this instance.
47
        """
48 1
        if isinstance(value, type(self)):
49
            return value.pack()
50
51
        if self.pcp is None and self.cfi is None and self.vid is None:
52
            return b''
53 1
        self.pcp = self.pcp if self.pcp is not None else 0
54
        self.cfi = self.cfi if self.cfi is not None else 0
55
        self.vid = self.vid if self.vid is not None else 0
56
        self._tci = self.pcp << 13 | self.cfi << 12 | self.vid
57
        return super().pack()
58
59
    def unpack(self, buff, offset=0):
60
        """Unpack a binary struct into this object's attributes.
61
62
        Return the values instead of the lib's basic types.
63
64
        After unpacking, the abscence of a `tpid` value causes the assignment
65
        of `None` to the field values.
66 1
67
        Args:
68 1
            buff (bytes): Binary buffer.
69 1
            offset (int): Where to begin unpacking.
70 1
71
        Raises:
72 1
            :exc:`~.exceptions.UnpackException`: If unpack fails.
73
        """
74
        super().unpack(buff, offset)
75 1
        if self.tpid.value:
76
            self.tpid = self.tpid.value
77 1
            self.pcp = self._tci.value >> 13
78
            self.cfi = (self._tci.value >> 12) & 1
79
            self.vid = self._tci.value & 4095
80 1
        else:
81
            self.tpid = VLAN_TPID
82 1
            self.pcp = None
83
            self.cfi = None
84
            self.vid = None
85
86
87
class Ethernet(GenericStruct):
88
    """Ethernet "struct".
89
90 1
    Objects of this class represents an ethernet packet. It contains the
91
    'Ethernet header', composed by destination (MAC), source (MAC), type
92 1
    (EtherType)[1] and the payload of the packet, as binary data.
93
94
    This class does not consider the Ethernet 'Preamble' or the 'CRC'.
95
96
    There is also a get_hash method, that hashes the binary representation of
97
    the object so we can have a unique representation of the ethernet packet,
98
    so we can keep a track of ethernet packets being flooded over the network.
99
100
    [1] EtherTypes:
101 1
    http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml#ieee-802-numbers-1
102 1
    """
103 1
104 1
    destination = HWAddress()
105
    source = HWAddress()
106
    vlan = VLAN()
107
    ether_type = UBInt16()
108
    data = BinaryData()
109
110
    def __init__(self, destination=None, source=None, vlan=VLAN(),
111
                 ether_type=None, data=b''):
112
        """Create an instance and set its attributes.
113 1
114
        Args:
115
            destination (:class:`~pyof.foundation.basic_types.HWAddress`):
116
                The final destination MAC address.
117
            source (:class:`~pyof.foundation.basic_types.HWAddress`):
118
                The source Mac address of the packet.
119
            ether_type (:class:`~pyof.foundation.basic_types.UBInt16`):
120
                The EtherType of packet.
121
            data (:class:`~pyof.foundation.basic_types.BinaryData`):
122
                The content of the packet in binary format.
123
        """
124
        super().__init__()
125
        self.destination = destination
126 1
        self.source = source
127 1
        self.vlan = vlan
128 1
        self.ether_type = ether_type
129 1
        self.data = data
130 1
131 1
    def get_hash(self):
132
        """Calculate a hash and returns it.
133 1
134
        Returns:
135
            int: Integer value that identifies this instance.
136
        """
137
        return hash(self.pack())
138
139
    def unpack(self, buff, offset=0):
140
        """Unpack a binary message into this object's attributes.
141 1
142
        Unpack the binary value *buff* and update this object attributes based
143
        on the results.
144
145
        Ethernet headers may have VLAN tags. If no VLAN tag is found, a
146
        'wildcard VLAN tag' is inserted to assure correct unpacking.
147
148
        Args:
149 1
            buff (bytes): Binary data package to be unpacked.
150
            offset (int): Where to begin unpacking.
151 1
152 1
        Raises:
153 1
            Exception: If there is a struct unpacking error.
154
        """
155 1
        if buff[12:16] != VLAN_TPID.to_bytes(2, 'big'):
156
            buff = buff[0:12] + b'\x00\x00\x00\x00' + buff[12:]
157
158
        super().unpack(buff, offset)
159
160
161 1
class GenericTLV(GenericStruct):
162
    """TLV structure of LLDP packets.
163
164
    This is a Type, Length and Value (TLV) struct.
165
166
    The LLDP/TLV definition states that the Type field have 7 bits, while
167
    the length have 9 bits. The Value must be between 0-511 octets.
168
169
    Internally, on the instances of this class, the Type is a integer
170
    (0-127) and the Length is dynamically calculated based on the current
171
    type and value.
172
    """
173
174
    def __init__(self, tlv_type=127, value=BinaryData()):
175
        """Create an instance and set its attributes.
176
177
        Args:
178
            tlv_type (int): Type used by this class. Defaults to 127.
179
            value (:class:`~pyof.foundation.basic_types.BinaryData`):
180
                Value stored by GenericTLV.
181
        """
182
        super().__init__()
183
        self.tlv_type = tlv_type
184
        self._value = value
185 1
186
    @property
187
    def value(self):
188
        """Return the value stored by GenericTLV.
189
190
        Returns:
191
            :class:`~pyof.foundation.basic_types.BinaryData`:
192
                Value stored by GenericTLV.
193
        """
194 1
        return self._value
195 1
196
    @property
197 1
    def length(self):
198
        """Return the length of value stored by GenericTLV.
199
200 1
        Returns:
201
            int: Value length in bytes.
202
        """
203
        return len(self.value.pack())
204
205
    @property
206
    def header(self):
207
        """Header of the TLV Packet.
208
209
        The header is composed by the Type (7 bits) and Length (9 bits),
210
        summing up 16 bits. To achieve that, we need to do some bitshift
211
        operations.
212
213
        Returns:
214
            :class:`~pyof.foundation.basic_types.UBInt16`:
215
                Result after all operations.
216
        """
217
        return UBInt16(((self.tlv_type & 127) << 9) | (self.length & 511))
218
219
    def pack(self, value=None):
220
        """Pack the TLV in a binary representation.
221
222
        Returns:
223
            bytes: Binary representation of the struct object.
224
225
        Raises:
226
            :exc:`~.exceptions.ValidationError`: If validation fails.
227
        """
228
        if value is None:
229
            output = self.header.pack()
230
            output += self.value.pack()
231
            return output
232
233
        elif isinstance(value, type(self)):
234
            return value.pack()
235
        else:
236
            msg = "{} is not an instance of {}".format(value,
237
                                                       type(self).__name__)
238
            raise PackException(msg)
239
240
    def unpack(self, buffer, offset=0):
241
        """Unpack a binary message into this object's attributes.
242
243
        Unpack the binary value *buff* and update this object attributes based
244
        on the results.
245
246
        Args:
247
            buff (bytes): Binary data package to be unpacked.
248
            offset (int): Where to begin unpacking.
249
250
        Raises:
251
            Exception: If there is a struct unpacking error.
252
        """
253
        header = UBInt16()
254
        header.unpack(buffer[offset:offset+2])
255
        self.tlv_type = header.value >> 9
256
        length = header.value & 511
257
        begin, end = offset + 2, offset + 2 + length
258
        self._value = BinaryData(buffer[begin:end])
259
260
    def get_size(self, value=None):
261
        """Return struct size.
262
263
        Returns:
264
            int: Returns the struct size based on inner attributes.
265
        """
266
        if isinstance(value, type(self)):
267
            return value.get_size()
268
269
        return 2 + self.length
270
271
272
class IPv4(GenericStruct):
273
    """IPv4 packet "struct".
274
275
    Contains all fields of an IP version 4 packet header, plus the upper layer
276
    content as binary data.
277
    Some of the fields were merged together because of their size being
278
    inferior to 8 bits. They are represented as a single class attribute, but
279
    pack/unpack methods will take into account the values in individual
280
    instance attributes.
281
    """
282
283
    #: _version_ihl (:class:`UBInt8`): IP protocol version + Internet Header
284
    #: Length (words)
285
    _version_ihl = UBInt8()
286
    #: _dscp_ecn (:class:`UBInt8`): Differentiated Services Code Point
287
    #: (ToS - Type of Service) + Explicit Congestion Notification
288
    _dscp_ecn = UBInt8()
289
    #: length (:class:`UBInt16`): IP packet length (bytes)
290
    length = UBInt16()
291
    #: identification (:class:`UBInt16`): Packet ID - common to all fragments
292
    identification = UBInt16()
293
    #: _flags_offset (:class:`UBInt16`): Fragmentation flags + fragmentation
294
    #: offset
295
    _flags_offset = UBInt16()
296
    #: ttl (:class:`UBInt8`): Packet time-to-live
297
    ttl = UBInt8()
298
    #: protocol (:class:`UBInt8`): Upper layer protocol number
299
    protocol = UBInt8()
300
    #: checksum (:class:`UBInt16`): Header checksum
301
    checksum = UBInt16()
302
    #: source (:class:`IPAddress`): Source IPv4 address
303
    source = IPAddress()
304
    #: destination (:class:`IPAddress`): Destination IPv4 address
305
    destination = IPAddress()
306
    #: options (:class:`BinaryData`): IP Options - up to 320 bits, always
307
    #: padded to 32 bits
308
    options = BinaryData()
309
    #: data (:class:`BinaryData`): Packet data
310
    data = BinaryData()
311
312
    def __init__(self, version=4, ihl=5, dscp=0, ecn=0, length=0, # noqa
313
                 identification=0, flags=0, offset=0, ttl=255, protocol=0,
314
                 checksum=0, source="0.0.0.0", destination="0.0.0.0",
315
                 options=b'', data=b''):
316
        """The constructor receives the parameters below.
317
318
        Args:
319
            version (int): IP protocol version. Defaults to 4.
320
            ihl (int): Internet Header Length. Default is 5.
321
            dscp (int): Differentiated Service Code Point. Defaults to 0.
322
            ecn (int): Explicit Congestion Notification. Defaults to 0.
323
            length (int): IP packet length in bytes. Defaults to 0.
324
            identification (int): Packet Id. Defaults to 0.
325
            flags (int): IPv4 Flags. Defults 0.
326
            offset (int): IPv4 offset. Defaults to 0.
327
            ttl (int): Packet time-to-live. Defaults to 255
328
            protocol (int): Upper layer protocol number. Defaults to 0.
329
            checksum (int): Header checksum. Defaults to 0.
330
            source (str): Source IPv4 address. Defaults to "0.0.0.0"
331
            destination (str): Destination IPv4 address. Defaults to "0.0.0.0"
332
            options (bytes): IP options. Defaults to empty bytes.
333
            data (bytes): Packet data. Defaults to empty bytes.
334
        """
335
        super().__init__()
336
        self.version = version
337
        self.ihl = ihl
338
        self.dscp = dscp
339
        self.ecn = ecn
340
        self.length = length
341
        self.identification = identification
342
        self.flags = flags
343
        self.offset = offset
344
        self.ttl = ttl
345
        self.protocol = protocol
346
        self.checksum = checksum
347
        self.source = source
348
        self.destination = destination
349
        self.options = options
350
        self.data = data
351
352
    def _update_checksum(self):
353
        """Update the packet checksum to enable integrity check."""
354
        source_list = [int(octet) for octet in self.source.split(".")]
355
        destination_list = [int(octet) for octet in
356
                            self.destination.split(".")]
357
        source_upper = (source_list[0] << 8) + source_list[1]
358
        source_lower = (source_list[2] << 8) + source_list[3]
359
        destination_upper = (destination_list[0] << 8) + destination_list[1]
360
        destination_lower = (destination_list[2] << 8) + destination_list[3]
361
362
        block_sum = ((self._version_ihl << 8 | self._dscp_ecn) + self.length +
363
                     self.identification + self._flags_offset +
364
                     (self.ttl << 8 | self.protocol) + source_upper +
365
                     source_lower + destination_upper + destination_lower)
366
367
        while block_sum > 65535:
368
            carry = block_sum >> 16
369
            block_sum = (block_sum & 65535) + carry
370
371
        self.checksum = ~block_sum & 65535
372
373
    def pack(self, value=None):
374
        """Pack the struct in a binary representation.
375
376
        Merge some fields to ensure correct packing.
377
378
        Returns:
379
            bytes: Binary representation of this instance.
380
        """
381
        # Set the correct IHL based on options size
382
        if self.options:
383
            self.ihl += int(len(self.options) / 4)
384
385
        # Set the correct packet length based on header length and data
386
        self.length = int(self.ihl * 4 + len(self.data))
387
388
        self._version_ihl = self.version << 4 | self.ihl
389
        self._dscp_ecn = self.dscp << 2 | self.ecn
390
        self._flags_offset = self.flags << 13 | self.offset
391
392
        # Set the checksum field before packing
393
        self._update_checksum()
394
395
        return super().pack()
396
397
    def unpack(self, buff, offset=0):
398
        """Unpack a binary struct into this object's attributes.
399
400
        Return the values instead of the lib's basic types.
401
402
        Args:
403
            buff (bytes): Binary buffer.
404
            offset (int): Where to begin unpacking.
405
406
        Raises:
407
            :exc:`~.exceptions.UnpackException`: If unpack fails.
408
        """
409
        super().unpack(buff, offset)
410
411
        self.version = self._version_ihl.value >> 4
412
        self.ihl = self._version_ihl.value & 15
413
        self.dscp = self._dscp_ecn.value >> 2
414
        self.ecn = self._dscp_ecn.value & 3
415
        self.length = self.length.value
416
        self.identification = self.identification.value
417
        self.flags = self._flags_offset.value >> 13
418
        self.offset = self._flags_offset.value & 8191
419
        self.ttl = self.ttl.value
420
        self.protocol = self.protocol.value
421
        self.checksum = self.checksum.value
422
        self.source = self.source.value
423
        self.destination = self.destination.value
424
425
        if self.ihl > 5:
426
            options_size = (self.ihl - 5) * 4
427
            self.data = self.options.value[options_size:]
428
            self.options = self.options.value[:options_size]
429
        else:
430
            self.data = self.options.value
431
            self.options = b''
432
433
434
class TLVWithSubType(GenericTLV):
435
    """Modify the :class:`GenericTLV` to a Organization Specific TLV structure.
436
437
    Beyond the standard TLV (type, length, value), we can also have a more
438
    specific structure, with the :attr:`value` field being splitted into a
439
    :attr:`sub_type` field and a new :attr:`sub_value` field.
440
    """
441
442
    def __init__(self, tlv_type=1, sub_type=7, sub_value=BinaryData()):
443
        """Create an instance and set its attributes.
444
445
        Args:
446
            tlv_type (int): Type used by this class. Defaults to 1.
447
            sub_type (int): Sub type value used by this class. Defaults to 7.
448
            sub_value (:class:`~pyof.foundation.basic_types.BinaryData`):
449
                Data stored by TLVWithSubType. Defaults to empty BinaryData.
450
        """
451
        super().__init__(tlv_type)
452
        self.sub_type = sub_type
453
        self.sub_value = sub_value
454
455
    @property
456
    def value(self):
457
        """Return sub type and sub value as binary data.
458
459
        Returns:
460
            :class:`~pyof.foundation.basic_types.BinaryData`:
461
                BinaryData calculated.
462
        """
463
        binary = UBInt8(self.sub_type).pack() + self.sub_value.pack()
464
        return BinaryData(binary)
465
466
    def unpack(self, buffer, offset=0):
467
        """Unpack a binary message into this object's attributes.
468
469
        Unpack the binary value *buff* and update this object attributes based
470
        on the results.
471
472
        Args:
473
            buff (bytes): Binary data package to be unpacked.
474
            offset (int): Where to begin unpacking.
475
476
        Raises:
477
            Exception: If there is a struct unpacking error.
478
        """
479
        header = UBInt16()
480
        header.unpack(buffer[offset:offset+2])
481
        self.tlv_type = header.value >> 9
482
        length = header.value & 511
483
        begin, end = offset + 2, offset + 2 + length
484
        sub_type = UBInt8()
485
        sub_type.unpack(buffer[begin:begin+1])
486
        self.sub_type = sub_type.value
487
        self.sub_value = BinaryData(buffer[begin+1:end])
488
489
490
class LLDP(GenericStruct):
491
    """LLDP class.
492
493
    Build a LLDP packet with TLVSubtype and Generic Subtypes.
494
495
    It contains a chassis_id TLV, a port_id TLV, a TTL (Time to live) and
496
    another TLV to represent the end of the LLDP Packet.
497
    """
498
499
    #: chassis_id (:class:`~TLVWithSubType`) with tlv_type = 1 and sub_type = 7
500
    chassis_id = TLVWithSubType(tlv_type=1, sub_type=7)
501
    #: port_id (:class:`TLVWithSubType`) with tlv = 2 and sub_type = 7
502
    port_id = TLVWithSubType(tlv_type=2, sub_type=7)
503
    #: TTL (:class:`GenericTLV`) time is given in seconds, between 0 and 65535,
504
    #: with tlv_type = 3
505
    ttl = GenericTLV(tlv_type=3, value=UBInt16(120))
506
    # We are not using list of tlvs for now
507
    # tlvs = ListOfTLVs()
508
    #: end (:class:`GenericTLV`) with tlv_type = 0
509
    end = GenericTLV(tlv_type=0)
510