Repository: secdev/scapy

Issue:
Add layers for Delay Tolerant Networking Protocols
Adds layers (in scapy/contrib) for Licklider Transmission Protocol (LTP, RFC5326) and Bundle Protocol (RFC5050).


Discussion:

Resolving patch:
diff --git a/scapy/contrib/bp.py b/scapy/contrib/bp.py
new file mode 100644
--- /dev/null
+++ b/scapy/contrib/bp.py
@@ -0,0 +1,128 @@
+#!/usr/bin/env python
+
+# This file is part of Scapy
+# Scapy 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
+# any later version.
+#
+# Scapy 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.
+#
+# You should have received a copy of the GNU General Public License
+# along with Scapy. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+ Copyright 2012, The MITRE Corporation
+
+                              NOTICE
+    This software/technical data was produced for the U.S. Government
+    under Prime Contract No. NASA-03001 and JPL Contract No. 1295026
+      and is subject to FAR 52.227-14 (6/87) Rights in Data General,
+        and Article GP-51, Rights in Data  General, respectively.
+       This software is publicly released under MITRE case #12-3054
+"""
+
+# scapy.contrib.description = Bundle Protocol (BP)
+# scapy.contrib.status = loads
+
+from scapy.packet import *
+from scapy.fields import *
+from scapy.layers.inet import UDP
+from scapy.contrib.ltp import *
+from scapy.contrib.sdnv import *
+
+
+class BP(Packet):
+    name = "BP"
+    fields_desc = [ByteField('version', 0x06),
+                   SDNV2('ProcFlags', 0),
+                   SDNV2LenField('BlockLen', None),
+                   SDNV2('DSO', 0),
+                   SDNV2('DSSO', 0),
+                   SDNV2('SSO', 0),
+                   SDNV2('SSSO', 0),
+                   SDNV2('RTSO', 0),
+                   SDNV2('RTSSO', 0),
+                   SDNV2('CSO', 0),
+                   SDNV2('CSSO', 0),
+                   SDNV2('CT', 0),
+                   SDNV2('CTSN', 0),
+                   SDNV2('LT', 0),
+                   SDNV2('DL', 0),
+                   ConditionalField(SDNV2("FO", 0), lambda x: (
+                       x.ProcFlags & 0x01)),
+                   ConditionalField(SDNV2("ADUL", 0), lambda x: (
+                       x.ProcFlags & 0x01)),
+                   ]
+
+    def mysummary(self):
+        tmp = "BP(%version%) flags("
+        if (self.ProcFlags & 0x01):
+            tmp += ' FR'
+        if (self.ProcFlags & 0x02):
+            tmp += ' AR'
+        if (self.ProcFlags & 0x04):
+            tmp += ' DF'
+        if (self.ProcFlags & 0x08):
+            tmp += ' CT'
+        if (self.ProcFlags & 0x10):
+            tmp += ' S'
+        if (self.ProcFlags & 0x20):
+            tmp += ' ACKME'
+        RAWCOS = (self.ProcFlags & 0x0180)
+        COS = (self.ProcFlags & 0x180) >> 7
+        cos_tmp = ''
+        if COS == 0x00:
+            cos_tmp += 'B '
+        if COS == 0x01:
+            cos_tmp += 'N '
+        if COS == 0x02:
+            cos_tmp += 'E '
+        if COS & 0xFE000:
+            cos_tmp += 'SRR: ('
+        if COS & 0x02000:
+            cos_tmp += 'Rec '
+        if COS & 0x04000:
+            cos_tmp += 'CA '
+        if COS & 0x08000:
+            cos_tmp += 'FWD '
+        if COS & 0x10000:
+            cos_tmp += 'DLV '
+        if COS & 0x20000:
+            cos_tmp += 'DEL '
+        if COS & 0xFE000:
+            cos_tmp += ') '
+
+        if cos_tmp:
+            tmp += ' Pr: ' + cos_tmp
+
+        tmp += " ) len(%BlockLen%) "
+        if self.DL == 0:
+            tmp += "CBHE: d[%DSO%,%DSSO%] s[%SSO%, %SSSO%] r[%RTSO%, %RTSSO%] c[%CSO%, %CSSO%] "
+        else:
+            tmp += "dl[%DL%] "
+        tmp += "ct[%CT%] ctsn[%CTSN%] lt[%LT%] "
+        if (self.ProcFlags & 0x01):
+            tmp += "fo[%FO%] "
+            tmp += "tl[%ADUL%]"
+
+        return self.sprintf(tmp), [LTP]
+
+
+class BPBLOCK(Packet):
+    fields_desc = [ByteEnumField('Type', 1, {1: "Bundle payload block"}),
+                   SDNV2('ProcFlags', 0),
+                   SDNV2FieldLenField('BlockLen', None, length_of="load"),
+                   StrLenField("load", "", length_from=lambda pkt: pkt.BlockLen)
+                   ]
+
+    def mysummary(self):
+        return self.sprintf("BPBLOCK(%Type%) Flags: %ProcFlags% Len: %BlockLen%")
+
+
+ltp_bind_payload(BP, lambda pkt: pkt.DATA_ClientServiceID == 1)
+bind_layers(BP,                     BPBLOCK)
+bind_layers(BPBLOCK,                BPBLOCK)
diff --git a/scapy/contrib/ltp.py b/scapy/contrib/ltp.py
new file mode 100755
--- /dev/null
+++ b/scapy/contrib/ltp.py
@@ -0,0 +1,204 @@
+#!/usr/bin/env python
+
+# This file is part of Scapy
+# Scapy 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
+# any later version.
+#
+# Scapy 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.
+#
+# You should have received a copy of the GNU General Public License
+# along with Scapy. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+ Copyright 2012, The MITRE Corporation
+
+                              NOTICE
+    This software/technical data was produced for the U.S. Government
+    under Prime Contract No. NASA-03001 and JPL Contract No. 1295026
+      and is subject to FAR 52.227-14 (6/87) Rights in Data General,
+        and Article GP-51, Rights in Data  General, respectively.
+       This software is publicly released under MITRE case #12-3054
+"""
+
+# scapy.contrib.description = Licklider Transmission Protocol (LTP)
+# scapy.contrib.status = loads
+
+import scapy.modules.six as six
+from scapy.packet import Packet, bind_layers, bind_top_down
+from scapy.fields import *
+from scapy.layers.inet import UDP
+from scapy.config import conf
+from scapy.contrib.sdnv import *
+
+# LTP https://tools.ietf.org/html/rfc5326
+
+_ltp_flag_vals = {
+    0: '0x0 Red data, NOT (Checkpoint, EORP or EOB)',
+    1: '0x1 Red data, Checkpoint, NOT (EORP or EOB)',
+    2: '0x2 Red data, Checkpoint, EORP, NOT EOB',
+    3: '0x3 Red data, Checkpoint, EORP, EOB',
+    4: '0x4 Green data, NOT EOB',
+    5: '0x5 Green data, undefined',
+    6: '0x6 Green data, undefined',
+    7: '0x7 Green data, EOB',
+    8: '0x8 Report segment',
+    9: '0x9 Report-acknowledgment segmen',
+    10: '0xA Control segment, undefined',
+    11: '0xB Control segment, undefined',
+    12: '0xC Cancel segment from block sender',
+    13: '0xD Cancel-acknowledgment segment to block sender',
+    14: '0xE Cancel segment from block receiver',
+    15: '0xF Cancel-acknowledgment segment to block receiver'}
+
+_ltp_cancel_reasons = {
+    0: 'USR_CNCLD  - Client service canceled session.',
+    1: 'UNREACH    - Unreachable client service.',
+    2: 'RLEXC      - Retransmission limit exceeded.',
+    3: 'MISCOLORED - Received miscolored segment.',
+    4: 'SYS_CNCLD  - System error condition.',
+    5: 'RXMTCYCEXC - Exceeded the retransmission cycles limit.',
+    6: 'RESERVED'}   # Reserved 0x06-0xFF
+
+# LTP Extensions https://tools.ietf.org/html/rfc5327
+
+_ltp_extension_tag = {
+    0: 'LTP authentication extension',
+    1: 'LTP cookie extension'
+}
+
+_ltp_data_segment = [0, 1, 2, 3, 4, 5, 6, 7]
+_ltp_checkpoint_segment = [1, 2, 3]
+
+_ltp_payload_conditions = {}
+
+
+def ltp_bind_payload(cls, lambd):
+    """Bind payload class to the LTP packets.
+    params:
+     - cls: the class to bind
+     - lambd: lambda that will be called to check
+              wether or not the cls should be used
+
+              lambda pkt: ...
+    """
+    _ltp_payload_conditions[cls] = lambd
+
+
+class LTPex(Packet):
+    name = "LTP Extension"
+    fields_desc = [
+        ByteEnumField("ExTag", 0, _ltp_extension_tag),
+        SDNV2FieldLenField("ExLength", None, length_of="ExData"),
+        # SDNV2FieldLenField
+        StrLenField("ExData", "", length_from=lambda x: x.ExLength)
+    ]
+
+    def default_payload_class(self, pay):
+        return conf.padding_layer
+
+
+class LTPReceptionClaim(Packet):
+    name = "LTP Reception Claim"
+    fields_desc = [SDNV2("ReceptionClaimOffset", 0),
+                   SDNV2("ReceptionClaimLength", 0)]
+
+    def default_payload_class(self, pay):
+        return conf.padding_layer
+
+
+def _ltp_guess_payload(pkt, *args):
+    for k, v in six.iteritems(_ltp_payload_conditions):
+        if v(pkt):
+            return k
+    return conf.raw_layer
+
+
+class LTP(Packet):
+    name = "LTP"
+    fields_desc = [
+        BitField('version', 0, 4),
+        BitEnumField('flags', 0, 4, _ltp_flag_vals),
+        SDNV2("SessionOriginator", 0),
+        SDNV2("SessionNumber", 0),
+        BitFieldLenField("HeaderExtensionCount", None, 4, count_of="HeaderExtensions"),
+        BitFieldLenField("TrailerExtensionCount", None, 4, count_of="TrailerExtensions"),
+        PacketListField("HeaderExtensions", [], LTPex, count_from=lambda x: x.HeaderExtensionCount),
+        #
+        # LTP segments containing data have a DATA header
+        #
+        ConditionalField(SDNV2("DATA_ClientServiceID", 0),
+                         lambda x: x.flags in _ltp_data_segment),
+        ConditionalField(SDNV2("DATA_PayloadOffset", 0),
+                         lambda x: x.flags in _ltp_data_segment),
+        ConditionalField(SDNV2FieldLenField("DATA_PayloadLength", None, length_of="LTP_Payload"),
+                         lambda x: x.flags in _ltp_data_segment),
+        #
+        # LTP segments that are checkpoints will have a checkpoint serial number and report serial number.
+        #
+        ConditionalField(SDNV2("CheckpointSerialNo", 0),
+                         lambda x: x.flags in _ltp_checkpoint_segment),
+        ConditionalField(SDNV2("ReportSerialNo", 0),
+                         lambda x: x.flags in _ltp_checkpoint_segment),
+        #
+        # Then comes the actual payload for data carrying segments.
+        #
+        ConditionalField(PacketListField("LTP_Payload", None, next_cls_cb=_ltp_guess_payload,
+                                         length_from=lambda x: x.DATA_PayloadLength),
+                         lambda x: x.flags in _ltp_data_segment),
+        #
+        # Report ACKS acknowledge a particular report serial number.
+        #
+        ConditionalField(SDNV2("RA_ReportSerialNo", 0),
+                         lambda x: x.flags == 9),
+        #
+        # Reception reports have the following fields.
+        #
+        ConditionalField(SDNV2("ReportSerialNo", 0),
+                         lambda x: x.flags == 8),
+        ConditionalField(SDNV2("ReportCheckpointSerialNo", 0),
+                         lambda x: x.flags == 8),
+        ConditionalField(SDNV2("ReportUpperBound", 0),
+                         lambda x: x.flags == 8),
+        ConditionalField(SDNV2("ReportLowerBound", 0),
+                         lambda x: x.flags == 8),
+        ConditionalField(SDNV2FieldLenField("ReportReceptionClaimCount", None, count_of="ReportReceptionClaims"),
+                         lambda x: x.flags == 8),
+        ConditionalField(PacketListField("ReportReceptionClaims", [], LTPReceptionClaim,
+                                         count_from=lambda x: x.ReportReceptionClaimCount),
+                         lambda x: x.flags == 8 and (not x.ReportReceptionClaimCount or x.ReportReceptionClaimCount > 0)),
+        #
+        # Cancellation Requests
+        #
+        ConditionalField(ByteEnumField("CancelFromSenderReason",
+                                       15, _ltp_cancel_reasons),
+                         lambda x: x.flags == 12),
+        ConditionalField(ByteEnumField("CancelFromReceiverReason",
+                                       15, _ltp_cancel_reasons),
+                         lambda x: x.flags == 14),
+        #
+        # Cancellation Acknowldgements
+        #
+        ConditionalField(SDNV2("CancelAckToBlockSender", 0),
+                         lambda x: x.flags == 13),
+        ConditionalField(SDNV2("CancelAckToBlockReceiver", 0),
+                         lambda x: x.flags == 15),
+        #
+        # Finally, trailing extensions
+        #
+        PacketListField("TrailerExtensions", [], LTPex, count_from=lambda x: x.TrailerExtensionCount)
+    ]
+
+    def mysummary(self):
+        return self.sprintf("LTP %SessionNumber%"), [UDP]
+
+
+bind_top_down(UDP, LTP, sport=1113)
+bind_top_down(UDP, LTP, dport=1113)
+bind_top_down(UDP, LTP, sport=2113)
+bind_top_down(UDP, LTP, dport=2113)
+bind_layers(UDP, LTP, sport=1113, dport=1113)
diff --git a/scapy/contrib/sdnv.py b/scapy/contrib/sdnv.py
new file mode 100644
--- /dev/null
+++ b/scapy/contrib/sdnv.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python
+
+# This file is part of Scapy
+# Scapy 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
+# any later version.
+#
+# Scapy 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.
+#
+# You should have received a copy of the GNU General Public License
+# along with Scapy. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+ Copyright 2012, The MITRE Corporation
+
+                              NOTICE
+    This software/technical data was produced for the U.S. Government
+    under Prime Contract No. NASA-03001 and JPL Contract No. 1295026
+      and is subject to FAR 52.227-14 (6/87) Rights in Data General,
+        and Article GP-51, Rights in Data  General, respectively.
+       This software is publicly released under MITRE case #12-3054
+"""
+
+# scapy.contrib.description = Self-Delimiting Numeric Values (SDNV)
+# scapy.contrib.status = library
+
+from scapy.fields import Field, FieldLenField
+
+## SDNV definitions
+
+
+class SDNVValueError(Exception):
+    def __init__(self, maxValue):
+        self.maxValue = maxValue
+
+
+class SDNV:
+    def __init__(self, maxValue=2**32 - 1):
+        self.maxValue = maxValue
+        return
+
+    def setMax(self, max):
+        self.maxValue = maxValue
+
+    def getMax(self):
+        return self.maxValue
+
+    def encode(self, number):
+        if number > self.maxValue:
+            raise SDNVValueError(self.maxValue)
+
+        foo = bytearray()
+        foo.append(number & 0x7F)
+        number = number >> 7
+
+        while (number > 0):
+            thisByte = number & 0x7F
+            thisByte |= 0x80
+            number = number >> 7
+            temp = bytearray()
+            temp.append(thisByte)
+            foo = temp + foo
+
+        return(foo)
+
+    def decode(self, ba, offset):
+        number = 0
+        numBytes = 1
+
+        b = ba[offset]
+        number = (b & 0x7F)
+        while (b & 0x80 == 0x80):
+            number = number << 7
+            if (number > self.maxValue):
+                raise SDNVValueError(self.maxValue)
+            b = ba[offset + numBytes]
+            number += (b & 0x7F)
+            numBytes += 1
+        if (number > self.maxValue):
+            raise SDNVValueError(self.maxValue)
+        return(number, numBytes)
+
+
+SDNVUtil = SDNV()
+
+
+class SDNV2(Field):
+    """ SDNV2 field """
+
+    def addfield(self, pkt, s, val):
+        return s+raw(SDNVUtil.encode(val))
+
+    def getfield(self, pkt, s):
+        b = bytearray(s)
+        val, len = SDNVUtil.decode(b, 0)
+        return s[len:], val
+
+
+class SDNV2FieldLenField(FieldLenField, SDNV2):
+    def addfield(self, pkt, s, val):
+        return s+raw(SDNVUtil.encode(FieldLenField.i2m(self, pkt, val)))
+
+
+class SDNV2LenField(LenField, SDNV2):
+    def addfield(self, pkt, s, val):
+        return s+raw(SDNVUtil.encode(LenField.i2m(self, pkt, val)))


Test patch: