scapy icon indicating copy to clipboard operation
scapy copied to clipboard

[enhancement] Provide a kind of packet that doesn't expect a payload

Open alxroyer-thales opened this issue 8 months ago • 3 comments

In order to make it easier to integrate with packet fields.

alxroyer-thales avatar Mar 28 '25 16:03 alxroyer-thales

The following example intends to make use of PacketListField:

class B0(Packet):
    fields_desc = [
        ByteField("value", default=0)
    ]
class A0(Packet):
    fields_desc = [
        PacketListField("b", pkt_cls=B0, count_from=lambda pkt: 2, default=[])
    ]
a0 = A0(bytes.fromhex("0102"))
a0.show()

Output:

###[ A0 ]###
  \b         \
   |###[ B0 ]###
   |  value     = 1
   |###[ Raw ]###
   |     load      = b'\x02'

While parsing B0 items, Scapy does not break dissection once the first item has been analyzed, and continues on considering that the remaining content is the payload of the latter.

For the concern, the length_from parameter of the PacketLenField makes it convenient to restrict the amount of data to parse for a given packet field.

class B1(Packet):
    fields_desc = [
        ByteField("value", default=0)
    ]
class A1(Packet):
    fields_desc = [
        FieldListField(
            "b",
            field=PacketLenField("", B1(), B1, length_from=lambda pkt: 1),
            count_from=lambda pkt: 2,
            default=[],
        ),
    ]
a1 = A1(bytes.fromhex("0102"))
a1.show()

Output:

###[ A1 ]###
  b         = [<B1  value=1 |>, <B1  value=2 |>]

Or, another trick to override the extract_padding() method for the final class to let Scapy know there's no payload to parse:

class B2(Packet):
    fields_desc = [
        ByteField("value", default=0)
    ]
    def extract_padding(self, s):
        return b'', s
class A2(Packet):
    fields_desc = [
        PacketListField("b", pkt_cls=B2, count_from=lambda pkt: 2, default=[])
    ]
a2 = A2(bytes.fromhex("0102"))
a2.show()

Output:

###[ A2 ]###
  \b         \
   |###[ B2 ]###
   |  value     = 1
   |###[ B2 ]###
   |  value     = 2

alxroyer-thales avatar Mar 28 '25 16:03 alxroyer-thales

Proposal: provide a FinalPacket class that implements this no-payload trait.

class FinalPacket(Packet):
    def extract_padding(self, s):
        return b'', s
class B3(FinalPacket):
    fields_desc = [
        ByteField("value", default=0)
    ]
class A3(Packet):
    fields_desc = [
        PacketListField("b", pkt_cls=B3, count_from=lambda pkt: 2, default=[])
    ]
a3 = A3(bytes.fromhex("0102"))
a3.show()

Output:

###[ A3 ]###
  \b         \
   |###[ B3 ]###
   |  value     = 1
   |###[ B3 ]###
   |  value     = 2

If of interest, I may provide a PR for that.

alxroyer-thales avatar Mar 28 '25 16:03 alxroyer-thales

Possibly related to #1021?

alxroyer-thales avatar Mar 28 '25 16:03 alxroyer-thales