Passed
Pull Request — master (#371)
by
unknown
02:13
created

IPv4.pack()   A

Complexity

Conditions 2

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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