86890704fd
todo: add documentation & wireshark dissector
202 lines
8.2 KiB
Python
Executable file
202 lines
8.2 KiB
Python
Executable file
# 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 BitEnumField, BitField, BitFieldLenField, \
|
|
ByteEnumField, ConditionalField, PacketListField, StrLenField
|
|
from scapy.layers.inet import UDP
|
|
from scapy.config import conf
|
|
from scapy.contrib.sdnv import SDNV2, SDNV2FieldLenField
|
|
|
|
# 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.
|
|
|
|
:param cls: the class to bind
|
|
:param lambd: lambda that will be called to check
|
|
whether or not the cls should be used
|
|
ex: 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"), # noqa: E501
|
|
BitFieldLenField("TrailerExtensionCount", None, 4, count_of="TrailerExtensions"), # noqa: E501
|
|
PacketListField("HeaderExtensions", [], LTPex, count_from=lambda x: x.HeaderExtensionCount), # noqa: E501
|
|
#
|
|
# 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"), # noqa: E501
|
|
lambda x: x.flags in _ltp_data_segment),
|
|
#
|
|
# LTP segments that are checkpoints will have a checkpoint serial number and report serial number. # noqa: E501
|
|
#
|
|
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, # noqa: E501
|
|
length_from=lambda x: x.DATA_PayloadLength), # noqa: E501
|
|
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"), # noqa: E501
|
|
lambda x: x.flags == 8),
|
|
ConditionalField(PacketListField("ReportReceptionClaims", [], LTPReceptionClaim, # noqa: E501
|
|
count_from=lambda x: x.ReportReceptionClaimCount), # noqa: E501
|
|
lambda x: x.flags == 8 and (not x.ReportReceptionClaimCount or x.ReportReceptionClaimCount > 0)), # noqa: E501
|
|
#
|
|
# 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) # noqa: E501
|
|
]
|
|
|
|
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)
|