Passed
Pull Request — master (#425)
by Carlos Eduardo
02:30
created

GenericTLV.get_size()   A

Complexity

Conditions 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 3.6875

Importance

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