Skip to content

Commit

Permalink
[inet6] recognize unknown router advertisement options (#4233)
Browse files Browse the repository at this point in the history
  • Loading branch information
evverx committed Jan 28, 2024
1 parent ae79fcb commit 5a1abdc
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 36 deletions.
57 changes: 30 additions & 27 deletions scapy/layers/inet6.py
Original file line number Diff line number Diff line change
Expand Up @@ -1737,18 +1737,41 @@ class _ICMPv6NDGuessPayload:

def guess_payload_class(self, p):
if len(p) > 1:
return icmp6ndoptscls.get(orb(p[0]), Raw) # s/Raw/ICMPv6NDOptUnknown/g ? # noqa: E501
return icmp6ndoptscls.get(orb(p[0]), ICMPv6NDOptUnknown)


# Beginning of ICMPv6 Neighbor Discovery Options.

class ICMPv6NDOptDataField(StrLenField):
__slots__ = ["strip_zeros"]

def __init__(self, name, default, strip_zeros=False, **kwargs):
super().__init__(name, default, **kwargs)
self.strip_zeros = strip_zeros

def i2len(self, pkt, x):
return len(self.i2m(pkt, x))

def i2m(self, pkt, x):
r = (len(x) + 2) % 8
if r:
x += b"\x00" * (8 - r)
return x

def m2i(self, pkt, x):
if self.strip_zeros:
x = x.rstrip(b"\x00")
return x


class ICMPv6NDOptUnknown(_ICMPv6NDGuessPayload, Packet):
name = "ICMPv6 Neighbor Discovery Option - Scapy Unimplemented"
fields_desc = [ByteField("type", None),
FieldLenField("len", None, length_of="data", fmt="B",
adjust=lambda pkt, x: x + 2),
StrLenField("data", "",
length_from=lambda pkt: pkt.len - 2)]
adjust=lambda pkt, x: (2 + x) // 8),
ICMPv6NDOptDataField("data", "", strip_zeros=False,
length_from=lambda pkt:
8 * max(pkt.len, 1) - 2)]

# NOTE: len includes type and len field. Expressed in unit of 8 bytes
# TODO: Revoir le coup du ETHER_ANY
Expand Down Expand Up @@ -2059,34 +2082,14 @@ def mysummary(self):
return self.sprintf("%name% ") + ", ".join(self.searchlist)


# URI MUST be padded with NUL (0x00) to make the total option length
# (including the Type and Length fields) a multiple of 8 bytes.
# https://www.rfc-editor.org/rfc/rfc8910.html#name-the-captive-portal-ipv6-ra-
class CaptivePortalURI(StrLenField):
def i2len(self, pkt, x):
return len(self.i2m(pkt, x))

def i2m(self, pkt, x):
r = (len(x) + 2) % 8
if r:
x += b"\x00" * (8 - r)
return x

def m2i(self, pkt, x):
return x.rstrip(b"\x00")


class ICMPv6NDOptCaptivePortal(_ICMPv6NDGuessPayload, Packet): # RFC 8910
name = "ICMPv6 Neighbor Discovery Option - Captive-Portal Option"
fields_desc = [ByteField("type", 37),
FieldLenField("len", None, length_of="URI", fmt="B",
adjust=lambda pkt, x: (2 + x) // 8),

# Zero length is nonsensical but it's treated as 1 here to
# let the dissector skip bogus options more or less gracefully
CaptivePortalURI("URI", "",
length_from=lambda pkt: 8 * max(pkt.len, 1) - 2)
]
ICMPv6NDOptDataField("URI", "", strip_zeros=True,
length_from=lambda pkt:
8 * max(pkt.len, 1) - 2)]

def mysummary(self):
return self.sprintf("%name% %URI%")
Expand Down
42 changes: 33 additions & 9 deletions test/scapy/layers/inet6.uts
Original file line number Diff line number Diff line change
Expand Up @@ -780,18 +780,40 @@ b.answers(a)
+ ICMPv6NDOptUnknown Class Test

= ICMPv6NDOptUnknown - Basic Instantiation
raw(ICMPv6NDOptUnknown()) == b'\x00\x02'
b = b'\x00\x01\x00\x00\x00\x00\x00\x00'

raw(ICMPv6NDOptUnknown()) == b

= ICMPv6NDOptUnknown - Instantiation with specific values
raw(ICMPv6NDOptUnknown(len=4, data="somestring")) == b'\x00\x04somestring'
raw(ICMPv6NDOptUnknown(data="somestring")) == b'\x00\x02somestring\x00\x00\x00\x00'

= ICMPv6NDOptUnknown - Basic Dissection
a=ICMPv6NDOptUnknown(b'\x00\x02')
a.type == 0 and a.len == 2
b = b'\x00\x01\x00\x00\x00\x00\x00\x00'

p = ICMPv6NDOptUnknown(b)
p.type == 0 and p.len == 1 and p.data == b'\x00' * 6

p = ICMPv6NDOptUnknown(b + b'\x00')
assert Raw in p and raw(p[Raw]) == b'\x00'

p = ICMPv6NDOptUnknown(b + b'\x00\x00')
assert raw(p[ICMPv6NDOptUnknown:2]) == b'\x00\x00'

= ICMPv6NDOptUnknown - Dissection with specific values
a=ICMPv6NDOptUnknown(b'\x00\x04somerawing')
a.type == 0 and a.len==4 and a.data == b"so" and isinstance(a.payload, Raw) and a.payload.load == b"merawing"
p = ICMPv6NDOptUnknown(b'\x00\x01string')
assert p.type == 0 and p.len == 1 and p.data == b'string'

p = ICMPv6NDOptUnknown(b'\x00\x04somestring')
assert p.type == 0 and p.len == 4 and p.data == b'somestring'

= ICMPv6NDOptUnknown - Instantiation/Dissection with unknown option in the middle
b = b'\x01\x01\x00\x00\x00\x00\x00\x00\x00\x02somestring\x00\x00\x00\x00%\x01\x00\x00\x00\x00\x00\x00'

p = ICMPv6NDOptSrcLLAddr()/ICMPv6NDOptUnknown(data='somestring')/ICMPv6NDOptCaptivePortal()
assert raw(p) == b

p = ICMPv6NDOptSrcLLAddr(b)[ICMPv6NDOptUnknown]
assert p.type == 0 and p.len == 2 and p.data == b'somestring\x00\x00\x00\x00'


############
Expand Down Expand Up @@ -1231,8 +1253,10 @@ p = ICMPv6NDOptCaptivePortal(b"\x25\x03https://example.com\x00\x00\x00")
p.type == 37 and p.len == 3 and p.URI == b"https://example.com"

= ICMPv6NDOptCaptivePortal - Dissection with zero length
p = ICMPv6NDOptCaptivePortal(b"\x25\x00abcdefgh")
p.type == 37 and p.len == 0 and p.URI == b"abcdef" and Raw in p and len(p[Raw]) == 2
p = ICMPv6NDOptCaptivePortal(b"\x25\x00abcdef\x00\x01")
p.type == 37 and p.len == 0 and p.URI == b"abcdef"
pay = p.payload
assert pay.type == 0 and pay.len == 1 and pay.data == b""

= ICMPv6NDOptCaptivePortal - Summary Output
ICMPv6NDOptCaptivePortal(URI="https://example.com").mysummary() == "ICMPv6 Neighbor Discovery Option - Captive-Portal Option b'https://example.com'"
Expand Down Expand Up @@ -1269,7 +1293,7 @@ p = ICMPv6NDOptPREF64(raw(p))
assert p.type == 38 and p.len == 2 and p.scaledlifetime == 225 and p.plc == 1 and p.prefix == '2003:da8:1::'

p = ICMPv6NDOptPREF64(raw(p) + b'\x00\x00\x00\x00')
assert Raw in p and len(p[Raw]) == 4
assert ICMPv6NDOptUnknown in p and len(p[ICMPv6NDOptUnknown]) == 4

= ICMPv6NDOptPREF64 - Summary Output
ICMPv6NDOptPREF64(prefix='12:34:56::', plc='/32').mysummary() == "ICMPv6 Neighbor Discovery Option - PREF64 Option 12:34:56::/32"
Expand Down

0 comments on commit 5a1abdc

Please sign in to comment.