File: tcp.py

package info (click to toggle)
python-os-ken 2.5.0-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 21,288 kB
  • sloc: python: 100,257; erlang: 14,517; ansic: 594; sh: 338; makefile: 136
file content (406 lines) | stat: -rw-r--r-- 13,976 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# Copyright (C) 2012 Nippon Telegraph and Telephone Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import struct
import logging

from os_ken.lib import stringify
from . import packet_base
from . import packet_utils
from . import bgp
from . import openflow
from . import zebra


LOG = logging.getLogger(__name__)

# TCP Option Kind Numbers
TCP_OPTION_KIND_END_OF_OPTION_LIST = 0    # End of Option List
TCP_OPTION_KIND_NO_OPERATION = 1          # No-Operation
TCP_OPTION_KIND_MAXIMUM_SEGMENT_SIZE = 2  # Maximum Segment Size
TCP_OPTION_KIND_WINDOW_SCALE = 3          # Window Scale
TCP_OPTION_KIND_SACK_PERMITTED = 4        # SACK Permitted
TCP_OPTION_KIND_SACK = 5                  # SACK
TCP_OPTION_KIND_TIMESTAMPS = 8            # Timestamps
TCP_OPTION_KIND_USER_TIMEOUT = 28         # User Timeout Option
TCP_OPTION_KIND_AUTHENTICATION = 29       # TCP Authentication Option (TCP-AO)

TCP_FIN = 0x001
TCP_SYN = 0x002
TCP_RST = 0x004
TCP_PSH = 0x008
TCP_ACK = 0x010
TCP_URG = 0x020
TCP_ECE = 0x040
TCP_CWR = 0x080
TCP_NS = 0x100


class tcp(packet_base.PacketBase):
    """TCP (RFC 793) header encoder/decoder class.

    An instance has the following attributes at least.
    Most of them are same to the on-wire counterparts but in host byte order.
    __init__ takes the corresponding args in this order.

    ============== ====================
    Attribute      Description
    ============== ====================
    src_port       Source Port
    dst_port       Destination Port
    seq            Sequence Number
    ack            Acknowledgement Number
    offset         Data Offset \
                   (0 means automatically-calculate when encoding)
    bits           Control Bits
    window_size    Window
    csum           Checksum \
                   (0 means automatically-calculate when encoding)
    urgent         Urgent Pointer
    option         List of ``TCPOption`` sub-classes or an bytearray
                   containing options. \
                   None if no options.
    ============== ====================
    """

    _PACK_STR = '!HHIIBBHHH'
    _MIN_LEN = struct.calcsize(_PACK_STR)

    def __init__(self, src_port=1, dst_port=1, seq=0, ack=0, offset=0,
                 bits=0, window_size=0, csum=0, urgent=0, option=None):
        super(tcp, self).__init__()
        self.src_port = src_port
        self.dst_port = dst_port
        self.seq = seq
        self.ack = ack
        self.offset = offset
        self.bits = bits
        self.window_size = window_size
        self.csum = csum
        self.urgent = urgent
        self.option = option

    def __len__(self):
        return self.offset * 4

    def has_flags(self, *flags):
        """Check if flags are set on this packet.

        returns boolean if all passed flags is set

        Example::

            >>> pkt = tcp.tcp(bits=(tcp.TCP_SYN | tcp.TCP_ACK))
            >>> pkt.has_flags(tcp.TCP_SYN, tcp.TCP_ACK)
            True
        """

        mask = sum(flags)
        return (self.bits & mask) == mask

    @staticmethod
    def get_payload_type(src_port, dst_port):
        from os_ken.ofproto.ofproto_common import OFP_TCP_PORT, OFP_SSL_PORT_OLD
        if bgp.TCP_SERVER_PORT in [src_port, dst_port]:
            return bgp.BGPMessage
        elif(src_port in [OFP_TCP_PORT, OFP_SSL_PORT_OLD] or
             dst_port in [OFP_TCP_PORT, OFP_SSL_PORT_OLD]):
            return openflow.openflow
        elif src_port == zebra.ZEBRA_PORT:
            return zebra._ZebraMessageFromZebra
        elif dst_port == zebra.ZEBRA_PORT:
            return zebra.ZebraMessage
        else:
            return None

    @classmethod
    def parser(cls, buf):
        (src_port, dst_port, seq, ack, offset, bits, window_size,
         csum, urgent) = struct.unpack_from(cls._PACK_STR, buf)
        offset >>= 4
        bits &= 0x3f
        length = offset * 4
        if length > tcp._MIN_LEN:
            option_buf = buf[tcp._MIN_LEN:length]
            try:
                option = []
                while option_buf:
                    opt, option_buf = TCPOption.parser(option_buf)
                    option.append(opt)
            except struct.error:
                LOG.warning(
                    'Encounter an error during parsing TCP option field.'
                    'Skip parsing TCP option.')
                option = buf[tcp._MIN_LEN:length]
        else:
            option = None
        msg = cls(src_port, dst_port, seq, ack, offset, bits,
                  window_size, csum, urgent, option)

        return msg, cls.get_payload_type(src_port, dst_port), buf[length:]

    def serialize(self, payload, prev):
        offset = self.offset << 4
        h = bytearray(struct.pack(
            tcp._PACK_STR, self.src_port, self.dst_port, self.seq,
            self.ack, offset, self.bits, self.window_size, self.csum,
            self.urgent))

        if self.option:
            if isinstance(self.option, (list, tuple)):
                option_buf = bytearray()
                for opt in self.option:
                    option_buf.extend(opt.serialize())
                h.extend(option_buf)
                mod = len(option_buf) % 4
            else:
                h.extend(self.option)
                mod = len(self.option) % 4
            if mod:
                h.extend(bytearray(4 - mod))
            if self.offset:
                offset = self.offset << 2
                if len(h) < offset:
                    h.extend(bytearray(offset - len(h)))

        if self.offset == 0:
            self.offset = len(h) >> 2
            offset = self.offset << 4
            struct.pack_into('!B', h, 12, offset)

        if self.csum == 0:
            total_length = len(h) + len(payload)
            self.csum = packet_utils.checksum_ip(prev, total_length,
                                                 h + payload)
            struct.pack_into('!H', h, 16, self.csum)
        return bytes(h)


class TCPOption(stringify.StringifyMixin):
    _KINDS = {}
    _KIND_PACK_STR = '!B'  # kind
    NO_BODY_OFFSET = 1     # kind(1 byte)
    WITH_BODY_OFFSET = 2   # kind(1 byte) + length(1 byte)
    cls_kind = None
    cls_length = None

    def __init__(self, kind=None, length=None):
        self.kind = self.cls_kind if kind is None else kind
        self.length = self.cls_length if length is None else length

    @classmethod
    def register(cls, kind, length):
        def _register(subcls):
            subcls.cls_kind = kind
            subcls.cls_length = length
            cls._KINDS[kind] = subcls
            return subcls
        return _register

    @classmethod
    def parse(cls, buf):
        # For no body TCP Options
        return cls(cls.cls_kind, cls.cls_length), buf[cls.cls_length:]

    @classmethod
    def parser(cls, buf):
        (kind,) = struct.unpack_from(cls._KIND_PACK_STR, buf)
        subcls = cls._KINDS.get(kind)
        if not subcls:
            subcls = TCPOptionUnknown
        return subcls.parse(buf)

    def serialize(self):
        # For no body TCP Options
        return struct.pack(self._KIND_PACK_STR, self.cls_kind)


class TCPOptionUnknown(TCPOption):
    _PACK_STR = '!BB'  # kind, length

    def __init__(self, value, kind, length):
        super(TCPOptionUnknown, self).__init__(kind, length)
        self.value = value if value is not None else b''

    @classmethod
    def parse(cls, buf):
        (kind, length) = struct.unpack_from(cls._PACK_STR, buf)
        value = buf[2:length]
        return cls(value, kind, length), buf[length:]

    def serialize(self):
        self.length = self.WITH_BODY_OFFSET + len(self.value)
        return struct.pack(self._PACK_STR,
                           self.kind, self.length) + self.value


@TCPOption.register(TCP_OPTION_KIND_END_OF_OPTION_LIST,
                    TCPOption.NO_BODY_OFFSET)
class TCPOptionEndOfOptionList(TCPOption):
    pass


@TCPOption.register(TCP_OPTION_KIND_NO_OPERATION,
                    TCPOption.NO_BODY_OFFSET)
class TCPOptionNoOperation(TCPOption):
    pass


@TCPOption.register(TCP_OPTION_KIND_MAXIMUM_SEGMENT_SIZE, 4)
class TCPOptionMaximumSegmentSize(TCPOption):
    _PACK_STR = '!BBH'  # kind, length, max_seg_size

    def __init__(self, max_seg_size, kind=None, length=None):
        super(TCPOptionMaximumSegmentSize, self).__init__(kind, length)
        self.max_seg_size = max_seg_size

    @classmethod
    def parse(cls, buf):
        (_, _, max_seg_size) = struct.unpack_from(cls._PACK_STR, buf)
        return cls(max_seg_size,
                   cls.cls_kind, cls.cls_length), buf[cls.cls_length:]

    def serialize(self):
        return struct.pack(self._PACK_STR,
                           self.kind, self.length, self.max_seg_size)


@TCPOption.register(TCP_OPTION_KIND_WINDOW_SCALE, 3)
class TCPOptionWindowScale(TCPOption):
    _PACK_STR = '!BBB'  # kind, length, shift_cnt

    def __init__(self, shift_cnt, kind=None, length=None):
        super(TCPOptionWindowScale, self).__init__(kind, length)
        self.shift_cnt = shift_cnt

    @classmethod
    def parse(cls, buf):
        (_, _, shift_cnt) = struct.unpack_from(cls._PACK_STR, buf)
        return cls(shift_cnt,
                   cls.cls_kind, cls.cls_length), buf[cls.cls_length:]

    def serialize(self):
        return struct.pack(self._PACK_STR,
                           self.kind, self.length, self.shift_cnt)


@TCPOption.register(TCP_OPTION_KIND_SACK_PERMITTED, 2)
class TCPOptionSACKPermitted(TCPOption):
    _PACK_STR = '!BB'  # kind, length

    def serialize(self):
        return struct.pack(self._PACK_STR, self.kind, self.length)


@TCPOption.register(TCP_OPTION_KIND_SACK,
                    2)  # variable length. 2 is the length except blocks.
class TCPOptionSACK(TCPOption):
    _PACK_STR = '!BB'        # kind, length
    _BLOCK_PACK_STR = '!II'  # Left Edge of Block, Right Edge of Block

    def __init__(self, blocks, kind=None, length=None):
        super(TCPOptionSACK, self).__init__(kind, length)
        # blocks is a list of tuple as followings.
        # self.blocks = [
        #     ('Left Edge of 1st Block', 'Right Edge of 1st Block'),
        #     ...
        #     ('Left Edge of nth Block', 'Right Edge of nth Block')
        # ]
        self.blocks = blocks

    @classmethod
    def parse(cls, buf):
        (_, length) = struct.unpack_from(cls._PACK_STR, buf)
        blocks_buf = buf[2:length]
        blocks = []
        while blocks_buf:
            lr_block = struct.unpack_from(cls._BLOCK_PACK_STR, blocks_buf)
            blocks.append(lr_block)  # (left, right)
            blocks_buf = blocks_buf[8:]
        return cls(blocks, cls.cls_kind, length), buf[length:]

    def serialize(self):
        buf = bytearray()
        for left, right in self.blocks:
            buf += struct.pack(self._BLOCK_PACK_STR, left, right)
        self.length = self.cls_length + len(buf)
        return struct.pack(self._PACK_STR, self.kind, self.length) + buf


@TCPOption.register(TCP_OPTION_KIND_TIMESTAMPS, 10)
class TCPOptionTimestamps(TCPOption):
    _PACK_STR = '!BBII'  # kind, length, ts_val, ts_ecr

    def __init__(self, ts_val, ts_ecr, kind=None, length=None):
        super(TCPOptionTimestamps, self).__init__(kind, length)
        self.ts_val = ts_val
        self.ts_ecr = ts_ecr

    @classmethod
    def parse(cls, buf):
        (_, _, ts_val, ts_ecr) = struct.unpack_from(cls._PACK_STR, buf)
        return cls(ts_val, ts_ecr,
                   cls.cls_kind, cls.cls_length), buf[cls.cls_length:]

    def serialize(self):
        return struct.pack(self._PACK_STR,
                           self.kind, self.length, self.ts_val, self.ts_ecr)


@TCPOption.register(TCP_OPTION_KIND_USER_TIMEOUT, 4)
class TCPOptionUserTimeout(TCPOption):
    _PACK_STR = '!BBH'  # kind, length, granularity(1bit)|user_timeout(15bit)

    def __init__(self, granularity, user_timeout, kind=None, length=None):
        super(TCPOptionUserTimeout, self).__init__(kind, length)
        self.granularity = granularity
        self.user_timeout = user_timeout

    @classmethod
    def parse(cls, buf):
        (_, _, body) = struct.unpack_from(cls._PACK_STR, buf)
        granularity = body >> 15
        user_timeout = body & 0x7fff
        return cls(granularity, user_timeout,
                   cls.cls_kind, cls.cls_length), buf[cls.cls_length:]

    def serialize(self):
        body = (self.granularity << 15) | self.user_timeout
        return struct.pack(self._PACK_STR, self.kind, self.length, body)


@TCPOption.register(TCP_OPTION_KIND_AUTHENTICATION,
                    4)  # variable length. 4 is the length except MAC.
class TCPOptionAuthentication(TCPOption):
    _PACK_STR = '!BBBB'  # kind, length, key_id, r_next_key_id

    def __init__(self, key_id, r_next_key_id, mac, kind=None, length=None):
        super(TCPOptionAuthentication, self).__init__(kind, length)
        self.key_id = key_id
        self.r_next_key_id = r_next_key_id
        self.mac = mac

    @classmethod
    def parse(cls, buf):
        (_, length,
         key_id, r_next_key_id) = struct.unpack_from(cls._PACK_STR, buf)
        mac = buf[4:length]
        return cls(key_id, r_next_key_id, mac,
                   cls.cls_kind, length), buf[length:]

    def serialize(self):
        self.length = self.cls_length + len(self.mac)
        return struct.pack(self._PACK_STR, self.kind, self.length,
                           self.key_id, self.r_next_key_id) + self.mac