From f838267d6fddbf394de26b4fba34ef923b3ff5ba Mon Sep 17 00:00:00 2001 From: itzzdev09 Date: Fri, 28 Aug 2026 03:13:32 +0530 Subject: [PATCH] Support multiple entries in IPOption_Timestamp RFC 791 requires the originating host to compose the timestamp option with a data area large enough to hold every timestamp it expects back, so that each router on the path can append one. IPOption_Timestamp modelled only a single entry, so a target host had no room to record anything and would reply with the overflow flag set. Model the data area as a list instead: - flg 0 (timestamp_only) uses 'timestamps', a list of 32-bit timestamps. - flg 1/3 use 'pairs', a list of IPOption_Timestamp_Pair, each an internet address followed by its timestamp. Both are sized from the option length on dissection, mirroring IPOption_RR. The defaults are empty lists, also as in IPOption_RR, so 'pointer' now defaults to 5 (the RFC minimum, pointing at the first free octet) rather than to 9, which assumed one entry was already present. Note this replaces the 'internet_address' and 'timestamp' fields; existing callers move to 'pairs=[IPOption_Timestamp_Pair(...)]' or 'timestamps=[...]'. --- scapy/layers/inet.py | 34 ++++++++++++++++++++++++++++++---- test/scapy/layers/inet.uts | 18 +++++++++++++++--- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/scapy/layers/inet.py b/scapy/layers/inet.py index 006875bd53e..9f322121f97 100644 --- a/scapy/layers/inet.py +++ b/scapy/layers/inet.py @@ -251,21 +251,47 @@ class IPOption_Traceroute(IPOption): IPField("originator_ip", "0.0.0.0")] +class IPOption_Timestamp_Pair(Packet): + """An internet address / timestamp pair of an IP timestamp option. + + Used when the option's flg field asks for addresses next to the + timestamps, i.e. flg 1 (timestamp_and_ip_addr) and 3 + (prespecified_ip_addr). + """ + name = "IP Option Timestamp Pair" + fields_desc = [IPField("internet_address", "0.0.0.0"), + IntField("timestamp", 0)] + + def extract_padding(self, s): + return b"", s + + class IPOption_Timestamp(IPOption): name = "IP Option Timestamp" optclass = 2 option = 4 + # RFC 791 requires the originating host to compose the option with a data + # area large enough to hold every timestamp it expects back, so the + # timestamps (and their addresses, depending on flg) form a list rather + # than a single entry. fields_desc = [_IPOption_HDR, ByteField("length", None), - ByteField("pointer", 9), + ByteField("pointer", 5), BitField("oflw", 0, 4), BitEnumField("flg", 1, 4, {0: "timestamp_only", 1: "timestamp_and_ip_addr", 3: "prespecified_ip_addr"}), - ConditionalField(IPField("internet_address", "0.0.0.0"), - lambda pkt: pkt.flg != 0), - IntField('timestamp', 0)] + ConditionalField( + FieldListField( + "timestamps", [], IntField("", 0), + length_from=lambda pkt: max(0, (pkt.length or 4) - 4)), # noqa: E501 + lambda pkt: pkt.flg == 0), + ConditionalField( + PacketListField( + "pairs", [], IPOption_Timestamp_Pair, + length_from=lambda pkt: max(0, (pkt.length or 4) - 4)), # noqa: E501 + lambda pkt: pkt.flg != 0)] def post_build(self, p, pay): if self.length is None: diff --git a/test/scapy/layers/inet.uts b/test/scapy/layers/inet.uts index 7803eb734f0..b79fe0f55ac 100644 --- a/test/scapy/layers/inet.uts +++ b/test/scapy/layers/inet.uts @@ -18,12 +18,24 @@ assert r == b'\x00' r = raw(IPOption_LSRR(routers=["1.2.3.4","5.6.7.8"])) r assert r == b'\x83\x0b\x04\x01\x02\x03\x04\x05\x06\x07\x08' -r = raw(IPOption_Timestamp(internet_address='192.168.15.7', timestamp=11223344)) +r = raw(IPOption_Timestamp(pointer=9, pairs=[IPOption_Timestamp_Pair(internet_address='192.168.15.7', timestamp=11223344)])) r assert r == b'D\x0c\t\x01\xc0\xa8\x0f\x07\x00\xabA0' -r = raw(IPOption_Timestamp(flg=0, length=8)) +r = raw(IPOption_Timestamp(flg=0, pointer=9, timestamps=[0])) r assert r == b'D\x08\t\x00\x00\x00\x00\x00' +# RFC 791: the data area holds as many entries as the sender reserves room for +r = raw(IPOption_Timestamp(pairs=[IPOption_Timestamp_Pair(internet_address='192.168.15.7', timestamp=1), IPOption_Timestamp_Pair(internet_address='10.0.0.1', timestamp=2)])) +r +assert r == b'D\x14\x05\x01\xc0\xa8\x0f\x07\x00\x00\x00\x01\n\x00\x00\x01\x00\x00\x00\x02' +r = raw(IPOption_Timestamp(flg=0, timestamps=[1, 2, 3])) +r +assert r == b'D\x10\x05\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03' +# and they are dissected back into the list +p = IPOption_Timestamp(b'D\x14\x05\x01\xc0\xa8\x0f\x07\x00\x00\x00\x01\n\x00\x00\x01\x00\x00\x00\x02') +assert [(e.internet_address, e.timestamp) for e in p.pairs] == [('192.168.15.7', 1), ('10.0.0.1', 2)] +p = IPOption_Timestamp(b'D\x10\x05\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03') +assert p.timestamps == [1, 2, 3] = IP options individual dissection ~ IP options @@ -476,7 +488,7 @@ pkt = IP(len=54, ihl=6, options=[IPOption_RR()]) / TCP() / ("A" * 10) bpkt = IP(raw(pkt)) assert bpkt.chksum == 0x70bc and bpkt.payload.chksum == 0x4b2c -pkt = IP(options=[IPOption_Timestamp()]) / TCP() / ("A" * 10) +pkt = IP(options=[IPOption_Timestamp(pointer=9, pairs=[IPOption_Timestamp_Pair()])]) / TCP() / ("A" * 10) bpkt = IP(raw(pkt)) assert bpkt.chksum == 0x2caa and bpkt.payload.chksum == 0x4b2c