File: rpc.py

package info (click to toggle)
nfstest 3.2-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,580 kB
  • sloc: python: 24,102; makefile: 3
file content (465 lines) | stat: -rw-r--r-- 17,141 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
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
#===============================================================================
# Copyright 2012 NetApp, Inc. All Rights Reserved,
# contribution by Jorge Mora <mora@netapp.com>
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#===============================================================================
"""
RPC module

Decode RPC layer.
"""
import struct
import traceback
from packet.utils import *
import nfstest_config as c
from baseobj import BaseObj
from packet.nfs.nfs import NFS
from packet.utils import IntHex
from packet.nfs.nlm4 import NLM4args,NLM4res
from packet.nfs.mount3 import MOUNT3args,MOUNT3res
from packet.nfs.portmap2 import PORTMAP2args,PORTMAP2res
from packet.application.rpc_creds import rpc_credential
from packet.application.rpc_const import *
from packet.application.gss import GSS

# Module constants
__author__    = "Jorge Mora (%s)" % c.NFSTEST_AUTHOR_EMAIL
__copyright__ = "Copyright (C) 2012 NetApp, Inc."
__license__   = "GPL v2"
__version__   = "1.5"

class accept_stat_enum(Enum):
    """enum accept_stat"""
    _enumdict = accept_stat

class reject_stat_enum(Enum):
    """enum reject_stat"""
    _enumdict = reject_stat

class auth_stat_enum(Enum):
    """enum auth_stat"""
    _enumdict = auth_stat

class Header(BaseObj):
    """Header object"""
    # Class attributes
    _attrlist = ("size", "last_fragment")

    def __init__(self, size, last_fragment):
        """Constructor which takes the size and last fragment as inputs"""
        self.size          = size
        self.last_fragment = last_fragment

class Prog(BaseObj):
    """Prog object"""
    # Class attributes
    _strfmt1  = "{0},{1}"
    _strfmt2  = "{0},{1}"
    _attrlist = ("low", "high")

    def __init__(self, unpack):
        """Constructor which takes the Unpack object as input"""
        self.low  = unpack.unpack_uint()
        self.high = unpack.unpack_uint()

class RPC(GSS):
    """RPC object

       Usage:
           from packet.application.rpc import RPC

           # Decode the RPC header
           x = RPC(pktt_obj, proto=6)

           # Decode NFS layer
           nfs = x.decode_payload()

       Object definition:

       RPC(
           [
               # If TCP
               fragment_hdr = Header(
                   last_fragment = int,
                   size          = int,
               ),
           ]
           xid  = int,
           type = int,

           [
               # If type == 0 (RPC call)
               rpc_version = int,
               program     = int,
               version     = int,
               procedure   = int,
               credential  = Credential(
                   data   = string,
                   flavor = int,
                   size   = int,
               ),
               verifier = Credential(
                   data   = string,
                   flavor = int,
                   size   = int,
               ),
           ] | [
               # If type == 1 (RPC reply)
               reply_status = int,
               [
                   # If reply_status == 0
                   verifier = Credential(
                       data   = string,
                       flavor = int,
                       size   = int,
                   ),
                   accepted_status = int,
                   [
                       # If accepted_status == 2
                       prog_mismatch = Prog(
                           low  = int,
                           high = int,
                       )
                   ]
               ] | [
                   # If reply_status != 0
                   rejected_status = int,
                   [
                       # If rejected_status == 0
                       prog_mismatch = Prog(
                           low  = int,
                           high = int,
                       )
                   ] | [
                       # If rejected_status != 0
                       auth_status = int,
                   ]
               ]
           ]
           psize = int,    # payload data size
           [data = string] # raw data of payload if unable to decode
       )
    """
    # Class attributes
    _attrlist = ("xid", "type", "rpc_version", "program", "version",
                 "procedure", "reply_status", "credential", "verifier",
                 "accepted_status", "prog_mismatch", "rejected_status",
                 "rpc_mismatch", "auth_status", "psize")

    def __init__(self, pktt, proto=17, state=True):
        """Constructor

           Initialize object's private data.

           pktt:
               Packet trace object (packet.pktt.Pktt) so this layer has
               access to the parent layers.
           proto:
               Transport layer protocol.
           state:
               Save call state. [default: True]
        """
        self._rpc = False
        self._pktt = pktt
        self._proto = proto
        self._state = state

        try:
            self._rpc_header()

            if self._rpc and proto == 17:
                # Save RPC layer on packet object
                pktt.pkt.add_layer("rpc", self)
                if self.type:
                    # Remove packet call from the xid map since reply has
                    # already been decoded
                    pktt._rpc_xid_map.pop(self.xid, None)

                # Decode NFS layer
                self.decode_payload()
        except:
            pass

    def _rpc_header(self):
        """Internal method to decode RPC header"""
        pktt = self._pktt
        unpack = pktt.unpack
        init_size = unpack.size()
        if self._proto == 6:
            # TCP packet
            save_data = ''
            while True:
                # Decode fragment header
                psize = unpack.unpack_uint()
                size = (psize & 0x7FFFFFFF) + len(save_data)
                last_fragment = (psize >> 31)
                if size == 0:
                    return
                if last_fragment == 0 and size < unpack.size():
                    # Save RPC fragment
                    save_data += unpack.read(size)
                else:
                    if len(save_data):
                        # Concatenate RPC fragments
                        unpack.insert(save_data)
                    break
            self.fragment_hdr = Header(size, last_fragment)
        elif self._proto == 17:
            # UDP packet
            pass
        else:
            return

        # Decode XID and RPC type
        self.xid  = IntHex(unpack.unpack_uint())
        self.type = unpack.unpack_uint()

        if self.type == CALL:
            # RPC call
            self.rpc_version = unpack.unpack_uint()
            self.program     = unpack.unpack_uint()
            self.version     = unpack.unpack_uint()
            self.procedure   = unpack.unpack_uint()
            self.credential  = rpc_credential(unpack)
            if not self.credential:
                return
            self.verifier = rpc_credential(unpack, True)
            if self.rpc_version != 2 or (self.credential.flavor in [0,1] and not self.verifier):
                return
        elif self.type == REPLY and pktt.rpc_replies:
            # RPC reply
            self.reply_status = unpack.unpack_uint()
            if self.reply_status == MSG_ACCEPTED:
                self.verifier = rpc_credential(unpack, True)
                if not self.verifier:
                    return
                self.accepted_status = accept_stat_enum(unpack)
                if self.accepted_status == PROG_MISMATCH:
                    self.prog_mismatch = Prog(unpack)
                elif accept_stat.get(self.accepted_status) is None:
                    # Invalid accept_stat
                    return
            elif self.reply_status == MSG_DENIED:
                self.rejected_status = reject_stat_enum(unpack)
                if self.rejected_status == RPC_MISMATCH:
                    self.rpc_mismatch = Prog(unpack)
                elif self.rejected_status == AUTH_ERROR:
                    self.auth_status = auth_stat_enum(unpack)
                    if auth_stat.get(self.auth_status) is None:
                        # Invalid auth_status
                        return
                elif reject_stat.get(self.rejected_status) is None:
                    # Invalid rejected status
                    return
            elif reply_stat.get(self.reply_status) is None:
                # Invalid reply status
                return
        else:
            return

        if self._proto == 6:
            hsize = init_size - unpack.size() - 4
            self.fragment_hdr.data_size = self.fragment_hdr.size - hsize

        self._rpc = True
        self.psize = unpack.size()
        if not self._state or not pktt.rpc_replies:
            # Do not save state
            return

        xid = self.xid
        if self.type == CALL:
            # Save call packet in the xid map
            pktt._rpc_xid_map[xid] = pktt.pkt
            pktt.pkt_call = None
        elif self.type == REPLY:
            try:
                pkt_call = pktt._rpc_xid_map.get(self.xid, None)
                pktt.pkt_call = pkt_call
                rpc_header = pkt_call.rpc

                self.program   = rpc_header.program
                self.version   = rpc_header.version
                self.procedure = rpc_header.procedure
                if rpc_header.credential.flavor == RPCSEC_GSS:
                    self.verifier.gssproc = rpc_header.credential.gssproc
                    self.verifier.service = rpc_header.credential.service
                    self.verifier.version = rpc_header.credential.version
            except Exception:
                pass

    def __bool__(self):
        """Truth value testing for the built-in operation bool()"""
        return self._rpc

    def __str__(self):
        """String representation of object

           The representation depends on the verbose level set by debug_repr().
           If set to 0 the generic object representation is returned.
           If set to 1 the representation of the object is:
               'RPC call   program: 100003, version: 4, procedure: 0, xid: 0xe37d3d5 '

           If set to 2 the representation of the object is as follows:
               'CALL(0), program: 100003, version: 4, procedure: 0, xid: 0xe37d3d5'
        """
        errstr = ""
        rdebug = self.debug_repr()
        if rdebug > 0:
            prog = ''
            for item in ['program', 'version', 'procedure']:
                value = getattr(self, item, None)
                if value != None:
                    prog += ", %s: %d" % (item, value)
        if self.type == REPLY and rdebug in (1,2):
            if self.reply_status == MSG_DENIED:
                if self.rejected_status == RPC_MISMATCH:
                    errstr = ", %s(%s)" % (self.rejected_status, self.rpc_mismatch)
                elif self.rejected_status == AUTH_ERROR:
                    errstr = ", %s(%s)" % (self.rejected_status, self.auth_status)
            elif self.accepted_status != SUCCESS:
                if self.accepted_status == PROG_MISMATCH:
                    errstr = ", %s(%s)" % (self.accepted_status, self.prog_mismatch)
                else:
                    errstr = ", %s" % self.accepted_status
        if rdebug == 1:
            rtype = "%-5s" % msg_type.get(self.type, 'Unknown').lower()
            out = "RPC   %s xid: %s%s%s" % (rtype, self.xid, prog, errstr)
        elif rdebug == 2:
            rtype = "%-5s(%d)" % (msg_type.get(self.type, 'Unknown'), self.type)
            if self.type == CALL:
                creds = ", %s" % self.credential
            else:
                if len(errstr):
                    creds = errstr
                else:
                    creds = ", %s" % self.verifier
            out = "%s, xid: %s%s%s" % (rtype, self.xid, prog, creds)
        else:
            out = BaseObj.__str__(self)
        return out

    def decode_payload(self):
        """Decode RPC load

           For RPC calls it is easy to decide if the RPC payload is an NFS packet
           since the RPC program is in the RPC header, which for NFS the
           program number is 100003. On the other hand, for RPC replies the RPC
           header does not have any information on what the payload is, so the
           transaction ID (xid) is used to map the replies to their calls and
           thus deciding if RPC payload is an NFS packet or not.
           This is further complicated when trying to decode callbacks, since
           the program number for callbacks could be any number in the transient
           program range [0x40000000, 0x5FFFFFFF]. Therefore, any program number
           in the transient range is considered a callback and if the decoding
           succeeds then this is an NFS callback, otherwise it is not.

           Since the RPC replies do not contain any information about what type
           of payload, when they are decoded correctly as NFS replies this
           information is inserted in the RPC (pkt.rpc) object.
           This information includes program number, RPC version, procedure
           number as well as the call_index which is the packet index of its
           corresponding call for each reply.

           x.pkt.nfs = <NFSobject>

           where <NFSobject> is an object of type COMPOUND4args or COMPOUND4res

           class COMPOUND4args(
               tag          = string,
               minorversion = int,
               argarray     = [],
           )

           The argarray is a list of nfs_argop4 objects:

           class nfs_argop4(
               argop = int,
               [<opobject> = <opargobject>,]
           )

           where opobject could be opsequence, opgetattr, etc., and opargobject
           is the object which has the arguments for the given opobject, e.g.,
           SEQUENCE4args, GETATTR4args, etc.

           class COMPOUND4res(
               tag      = string,
               status   = int,
               resarray = [],
           )

           The resarray is a list of nfs_resop4 objects:

           class nfs_resop4(
               resop = int,
               [<opobject> = <opresobject>,]
           )

           where opobject could be opsequence, opgetattr, etc., and opresobject
           is the object which has the results for the given opobject, e.g.,
           SEQUENCE4res, GETATTR4res, etc.
        """
        ret = None
        layer = None
        pktt = self._pktt
        unpack = pktt.unpack
        self.decode_gss_data()

        # Make sure to catch any errors
        try:
            if self.program == 100003:
                # Decode NFS layer
                layer = "nfs"
                ret = NFS(self, False)
            elif self.program == 100005:
                # MOUNT protocol
                layer = "mount"
                if self.type == 0:
                    ret = MOUNT3args(unpack, self.procedure)
                else:
                    ret = MOUNT3res(unpack, self.procedure)
            elif self.program == 100021:
                # NLM protocol
                layer = "nlm"
                if self.type == 0:
                    ret = NLM4args(unpack, self.procedure)
                else:
                    ret = NLM4res(unpack, self.procedure)
            elif self.program == 100000:
                # PORTMAP protocol
                layer = "portmap"
                if self.type == 0:
                    ret = PORTMAP2args(unpack, self.procedure)
                else:
                    ret = PORTMAP2res(unpack, self.procedure)
            elif self.program >= 0x40000000 and self.program < 0x60000000:
                # This is a crude way to figure out if call/reply is a callback
                # based on the fact that NFS is always program 100003 and anything
                # in the transient program range is considered a callback
                layer = "nfs"
                ret = NFS(self, True)
            else:
                # Unable to decode RPC load so just get the load bytes
                if self._proto == 6:
                    self.data = unpack.read(self.fragment_hdr.data_size)
                else:
                    # Just get the bytes but leave them in the buffer
                    self.data = unpack.getbytes()

            if ret:
                ret._rpc = self
                pktt.pkt.add_layer(layer, ret)
                self.decode_gss_checksum()
        except Exception:
            # Could not decode RPC load
            self.dprint('PKT3', traceback.format_exc())
            return
        return ret