Skip to content

Commit 35b4756

Browse files
authored
Fix L2 dst address computation (very intrusive) (#4145)
* Less intrusive L2 dst computation (especially ARP) * Apply guedou suggestions
1 parent bb0164b commit 35b4756

File tree

4 files changed

+61
-14
lines changed

4 files changed

+61
-14
lines changed

scapy/layers/inet.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,6 +1099,9 @@ def mysummary(self):
10991099

11001100

11011101
def inet_register_l3(l2, l3):
1102+
"""
1103+
Resolves the default L2 destination address when IP is used.
1104+
"""
11021105
return getmacbyip(l3.dst)
11031106

11041107

scapy/layers/inet6.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,9 @@ def dispatch_hook(cls, _pkt=None, *_, **kargs):
475475

476476

477477
def inet6_register_l3(l2, l3):
478+
"""
479+
Resolves the default L2 destination address when IPv6 is used.
480+
"""
478481
return getmacbyip6(l3.dst)
479482

480483

scapy/layers/l2.py

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,12 @@ def __init__(self, name):
166166

167167
def i2h(self, pkt, x):
168168
# type: (Optional[Packet], Optional[str]) -> str
169+
if x is None and pkt is not None:
170+
x = "None (resolved on build)"
171+
return super(DestMACField, self).i2h(pkt, x)
172+
173+
def i2m(self, pkt, x):
174+
# type: (Optional[Packet], Optional[str]) -> bytes
169175
if x is None and pkt is not None:
170176
try:
171177
x = conf.neighbor.resolve(pkt, pkt.payload)
@@ -176,12 +182,10 @@ def i2h(self, pkt, x):
176182
raise ScapyNoDstMacException()
177183
else:
178184
x = "ff:ff:ff:ff:ff:ff"
179-
warning("Mac address to reach destination not found. Using broadcast.") # noqa: E501
180-
return super(DestMACField, self).i2h(pkt, x)
181-
182-
def i2m(self, pkt, x):
183-
# type: (Optional[Packet], Optional[str]) -> bytes
184-
return super(DestMACField, self).i2m(pkt, self.i2h(pkt, x))
185+
warning(
186+
"MAC address to reach destination not found. Using broadcast."
187+
)
188+
return super(DestMACField, self).i2m(pkt, x)
185189

186190

187191
class SourceMACField(MACField):
@@ -311,8 +315,10 @@ class LLC(Packet):
311315
ByteField("ctrl", 0)]
312316

313317

314-
def l2_register_l3(l2, l3):
315-
# type: (Packet, Packet) -> Optional[str]
318+
def l2_register_l3(l2: Packet, l3: Packet) -> Optional[str]:
319+
"""
320+
Delegates resolving the default L2 destination address to the payload of L3.
321+
"""
316322
neighbor = conf.neighbor # type: Neighbor
317323
return neighbor.resolve(l2, l3.payload)
318324

@@ -554,15 +560,28 @@ def mysummary(self):
554560
return self.sprintf("ARP %op% %psrc% > %pdst%")
555561

556562

557-
def l2_register_l3_arp(l2, l3):
558-
# type: (Packet, Packet) -> Optional[str]
559-
# TODO: support IPv6?
560-
plen = l3.plen if l3.plen is not None else l3.get_field("pdst").i2len(l3, l3.pdst)
563+
def l2_register_l3_arp(l2: Packet, l3: Packet) -> Optional[str]:
564+
"""
565+
Resolves the default L2 destination address when ARP is used.
566+
"""
567+
if l3.op == 1: # who-has
568+
return "ff:ff:ff:ff:ff:ff"
569+
elif l3.op == 2: # is-at
570+
log_runtime.warning(
571+
"You should be providing the Ethernet destination MAC address when "
572+
"sending an is-at ARP."
573+
)
574+
# Need ARP request to send ARP request...
575+
plen = l3.get_field("pdst").i2len(l3, l3.pdst)
561576
if plen == 4:
562577
return getmacbyip(l3.pdst)
578+
elif plen == 32:
579+
from scapy.layers.inet6 import getmacbyip6
580+
return getmacbyip6(l3.pdst)
581+
# Can't even do that
563582
log_runtime.warning(
564-
"Unable to guess L2 MAC address from an ARP packet with a "
565-
"non-IPv4 pdst. Provide it manually !"
583+
"You should be providing the Ethernet destination mac when sending this "
584+
"kind of ARP packets."
566585
)
567586
return None
568587

test/scapy/layers/inet.uts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,28 @@ assert isinstance(pkt, UDP) and pkt.dport == 5353
572572
pkt = pkt.payload
573573
assert isinstance(pkt, DNS) and isinstance(pkt.payload, NoPayload)
574574

575+
= Layer binding with show()
576+
* getmacbyip must only be called when building
577+
578+
import mock
579+
580+
def _err(*_):
581+
raise ValueError
582+
583+
with mock.patch("scapy.layers.l2.getmacbyip", side_effect=_err):
584+
with mock.patch("scapy.layers.inet.getmacbyip", side_effect=_err):
585+
# ARP who-has should never call getmacbyip
586+
pkt1 = Ether() / ARP(pdst="10.0.0.1")
587+
pkt1.show()
588+
bytes(pkt1)
589+
# IP should only call getmacbyip when building
590+
pkt2 = Ether() / IP(dst="10.0.0.1")
591+
pkt2.show()
592+
try:
593+
bytes(pkt2)
594+
assert False, "Should have called getmacbyip"
595+
except ValueError:
596+
pass
575597

576598
############
577599
############

0 commit comments

Comments
 (0)