Skip to content

Commit 0f4ded3

Browse files
authored
Rewrite ICMP extensions (#4332)
1 parent 0a2b2bc commit 0f4ded3

File tree

8 files changed

+433
-229
lines changed

8 files changed

+433
-229
lines changed

scapy/contrib/icmp_extensions.py

Lines changed: 26 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -2,186 +2,29 @@
22
# This file is part of Scapy
33
# See https://scapy.net/ for more information
44

5-
# scapy.contrib.description = ICMP Extensions
6-
# scapy.contrib.status = loads
7-
8-
import struct
9-
10-
import scapy
11-
from scapy.compat import chb
12-
from scapy.packet import Packet, bind_layers
13-
from scapy.fields import BitField, ByteField, ConditionalField, \
14-
FieldLenField, IPField, IntField, PacketListField, ShortField, \
15-
StrLenField
16-
from scapy.layers.inet import IP, ICMP, checksum
17-
from scapy.layers.inet6 import IP6Field
18-
from scapy.error import warning
19-
from scapy.contrib.mpls import MPLS
20-
from scapy.config import conf
21-
22-
23-
class ICMPExtensionObject(Packet):
24-
name = 'ICMP Extension Object'
25-
fields_desc = [ShortField('len', None),
26-
ByteField('classnum', 0),
27-
ByteField('classtype', 0)]
28-
29-
def post_build(self, p, pay):
30-
if self.len is None:
31-
tmp_len = len(p) + len(pay)
32-
p = struct.pack('!H', tmp_len) + p[2:]
33-
return p + pay
34-
35-
36-
class ICMPExtensionHeader(Packet):
37-
name = 'ICMP Extension Header (RFC4884)'
38-
fields_desc = [BitField('version', 2, 4),
39-
BitField('reserved', 0, 12),
40-
BitField('chksum', None, 16)]
41-
42-
_min_ieo_len = len(ICMPExtensionObject())
43-
44-
def post_build(self, p, pay):
45-
if self.chksum is None:
46-
ck = checksum(p)
47-
p = p[:2] + chb(ck >> 8) + chb(ck & 0xff) + p[4:]
48-
return p + pay
49-
50-
def guess_payload_class(self, payload):
51-
if len(payload) < self._min_ieo_len:
52-
return Packet.guess_payload_class(self, payload)
53-
54-
# Look at fields of the generic ICMPExtensionObject to determine which
55-
# bound extension type to use.
56-
ieo = ICMPExtensionObject(payload)
57-
if ieo.len < self._min_ieo_len:
58-
return Packet.guess_payload_class(self, payload)
59-
60-
for fval, cls in self.payload_guess:
61-
if all(hasattr(ieo, k) and v == ieo.getfieldval(k)
62-
for k, v in fval.items()):
63-
return cls
64-
return ICMPExtensionObject
65-
66-
67-
def ICMPExtension_post_dissection(self, pkt):
68-
# RFC4884 section 5.2 says if the ICMP packet length
69-
# is >144 then ICMP extensions start at byte 137.
70-
71-
lastlayer = pkt.lastlayer()
72-
if not isinstance(lastlayer, conf.padding_layer):
73-
return
74-
75-
if IP in pkt:
76-
if (ICMP in pkt and
77-
pkt[ICMP].type in [3, 11, 12] and
78-
pkt.len > 144):
79-
bytes = pkt[ICMP].build()[136:]
80-
else:
81-
return
82-
elif scapy.layers.inet6.IPv6 in pkt:
83-
if ((scapy.layers.inet6.ICMPv6TimeExceeded in pkt or
84-
scapy.layers.inet6.ICMPv6DestUnreach in pkt) and
85-
pkt.plen > 144):
86-
bytes = pkt[scapy.layers.inet6.ICMPv6TimeExceeded].build()[136:]
87-
else:
88-
return
89-
else:
90-
return
91-
92-
# validate checksum
93-
ieh = ICMPExtensionHeader(bytes)
94-
if checksum(ieh.build()):
95-
return # failed
96-
97-
lastlayer.load = lastlayer.load[:-len(ieh)]
98-
lastlayer.add_payload(ieh)
99-
100-
101-
class ICMPExtensionMPLS(ICMPExtensionObject):
102-
name = 'ICMP Extension Object - MPLS (RFC4950)'
103-
104-
fields_desc = [ShortField('len', None),
105-
ByteField('classnum', 1),
106-
ByteField('classtype', 1),
107-
PacketListField('stack', [], MPLS,
108-
length_from=lambda pkt: pkt.len - 4)]
109-
110-
111-
class ICMPExtensionInterfaceInformation(ICMPExtensionObject):
112-
name = 'ICMP Extension Object - Interface Information Object (RFC5837)'
113-
114-
fields_desc = [ShortField('len', None),
115-
ByteField('classnum', 2),
116-
BitField('interface_role', 0, 2),
117-
BitField('reserved', 0, 2),
118-
BitField('has_ifindex', 0, 1),
119-
BitField('has_ipaddr', 0, 1),
120-
BitField('has_ifname', 0, 1),
121-
BitField('has_mtu', 0, 1),
122-
123-
ConditionalField(
124-
IntField('ifindex', None),
125-
lambda pkt: pkt.has_ifindex == 1),
126-
127-
ConditionalField(
128-
ShortField('afi', None),
129-
lambda pkt: pkt.has_ipaddr == 1),
130-
ConditionalField(
131-
ShortField('reserved2', 0),
132-
lambda pkt: pkt.has_ipaddr == 1),
133-
ConditionalField(
134-
IPField('ip4', None),
135-
lambda pkt: pkt.afi == 1),
136-
ConditionalField(
137-
IP6Field('ip6', None),
138-
lambda pkt: pkt.afi == 2),
139-
140-
ConditionalField(
141-
FieldLenField('ifname_len', None, fmt='B',
142-
length_of='ifname'),
143-
lambda pkt: pkt.has_ifname == 1),
144-
ConditionalField(
145-
StrLenField('ifname', None,
146-
length_from=lambda pkt: pkt.ifname_len),
147-
lambda pkt: pkt.has_ifname == 1),
148-
149-
ConditionalField(
150-
IntField('mtu', None),
151-
lambda pkt: pkt.has_mtu == 1)]
152-
153-
def self_build(self, **kwargs):
154-
if self.afi is None:
155-
if self.ip4 is not None:
156-
self.afi = 1
157-
elif self.ip6 is not None:
158-
self.afi = 2
159-
160-
if self.has_ifindex and self.ifindex is None:
161-
warning('has_ifindex set but ifindex is not set.')
162-
if self.has_ipaddr and self.afi is None:
163-
warning('has_ipaddr set but afi is not set.')
164-
if self.has_ipaddr and self.ip4 is None and self.ip6 is None:
165-
warning('has_ipaddr set but ip4 or ip6 is not set.')
166-
if self.has_ifname and self.ifname is None:
167-
warning('has_ifname set but ifname is not set.')
168-
if self.has_mtu and self.mtu is None:
169-
warning('has_mtu set but mtu is not set.')
170-
171-
return ICMPExtensionObject.self_build(self, **kwargs)
172-
173-
174-
# Add the post_dissection() method to the existing ICMPv4 and
175-
# ICMPv6 error messages
176-
scapy.layers.inet.ICMPerror.post_dissection = ICMPExtension_post_dissection
177-
scapy.layers.inet.TCPerror.post_dissection = ICMPExtension_post_dissection
178-
scapy.layers.inet.UDPerror.post_dissection = ICMPExtension_post_dissection
179-
180-
scapy.layers.inet6.ICMPv6DestUnreach.post_dissection = ICMPExtension_post_dissection # noqa: E501
181-
scapy.layers.inet6.ICMPv6TimeExceeded.post_dissection = ICMPExtension_post_dissection # noqa: E501
182-
183-
184-
# ICMPExtensionHeader looks at fields from the upper layer object when
185-
# determining which upper layer to use.
186-
bind_layers(ICMPExtensionHeader, ICMPExtensionMPLS, classnum=1, classtype=1)
187-
bind_layers(ICMPExtensionHeader, ICMPExtensionInterfaceInformation, classnum=2)
5+
# scapy.contrib.description = ICMP Extensions (deprecated)
6+
# scapy.contrib.status = deprecated
7+
8+
__all__ = [
9+
"ICMPExtensionObject",
10+
"ICMPExtensionHeader",
11+
"ICMPExtensionInterfaceInformation",
12+
"ICMPExtensionMPLS",
13+
]
14+
15+
import warnings
16+
17+
from scapy.layers.inet import (
18+
ICMPExtension_Object as ICMPExtensionObject,
19+
ICMPExtension_Header as ICMPExtensionHeader,
20+
ICMPExtension_InterfaceInformation as ICMPExtensionInterfaceInformation,
21+
)
22+
from scapy.contrib.mpls import (
23+
ICMPExtension_MPLS as ICMPExtensionMPLS,
24+
)
25+
26+
warnings.warn(
27+
"scapy.contrib.icmp_extensions is deprecated. Behavior has changed ! "
28+
"Use scapy.layers.inet",
29+
DeprecationWarning
30+
)

scapy/contrib/mpls.py

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,24 @@
66
# scapy.contrib.status = loads
77

88
from scapy.packet import Packet, bind_layers, Padding
9-
from scapy.fields import BitField, ByteField, ShortField
10-
from scapy.layers.inet import IP, UDP
11-
from scapy.contrib.bier import BIER
9+
from scapy.fields import (
10+
BitField,
11+
ByteField,
12+
ByteEnumField,
13+
PacketListField,
14+
ShortField,
15+
)
16+
17+
from scapy.layers.inet import (
18+
_ICMP_classnums,
19+
ICMPExtension_Object,
20+
IP,
21+
UDP,
22+
)
1223
from scapy.layers.inet6 import IPv6
1324
from scapy.layers.l2 import Ether, GRE
14-
from scapy.compat import orb
25+
26+
from scapy.contrib.bier import BIER
1527

1628

1729
class EoMCW(Packet):
@@ -37,21 +49,36 @@ def guess_payload_class(self, payload):
3749
if len(payload) >= 1:
3850
if not self.s:
3951
return MPLS
40-
ip_version = (orb(payload[0]) >> 4) & 0xF
52+
ip_version = (payload[0] >> 4) & 0xF
4153
if ip_version == 4:
4254
return IP
4355
elif ip_version == 5:
4456
return BIER
4557
elif ip_version == 6:
4658
return IPv6
4759
else:
48-
if orb(payload[0]) == 0 and orb(payload[1]) == 0:
60+
if payload[0] == 0 and payload[1] == 0:
4961
return EoMCW
5062
else:
5163
return Ether
5264
return Padding
5365

5466

67+
# ICMP Extension
68+
69+
class ICMPExtension_MPLS(ICMPExtension_Object):
70+
name = "ICMP Extension Object - MPLS (RFC4950)"
71+
72+
fields_desc = [
73+
ShortField("len", None),
74+
ByteEnumField("classnum", 1, _ICMP_classnums),
75+
ByteField("classtype", 1),
76+
PacketListField("stack", [], MPLS, length_from=lambda pkt: pkt.len - 4),
77+
]
78+
79+
80+
# Bindings
81+
5582
bind_layers(Ether, MPLS, type=0x8847)
5683
bind_layers(IP, MPLS, proto=137)
5784
bind_layers(IPv6, MPLS, nh=137)

scapy/fields.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1893,6 +1893,8 @@ def i2repr(self,
18931893
def getfield(self, pkt, s):
18941894
# type: (Packet, bytes) -> Tuple[bytes, bytes]
18951895
len_pkt = self.length_from(pkt)
1896+
if len_pkt == 0:
1897+
return s, b""
18961898
return s[len_pkt:], self.m2i(pkt, s[:len_pkt])
18971899

18981900
def addfield(self, pkt, s, val):

0 commit comments

Comments
 (0)