Passed
Pull Request — master (#433)
by
unknown
02:34
created

ARP.unpack()   A

Complexity

Conditions 2

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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