46 lines
1.5 KiB
Python
46 lines
1.5 KiB
Python
|
|
"""
|
||
|
|
ARP responder — answers chip ARP requests for the synthetic gateway
|
||
|
|
and any IP the chip wants to talk to.
|
||
|
|
|
||
|
|
The cyw43 lwIP stack will do an ARP lookup for the destination MAC of
|
||
|
|
its first packet to a remote host. We don't run a real network — there's
|
||
|
|
no real MAC behind the gateway IP. So we synthesize the gateway MAC
|
||
|
|
(``GATEWAY_MAC``) for everything, exactly the way QEMU's slirp does.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from .consts import ARP_REPLY, ARP_REQUEST, GATEWAY_MAC, STA_IP, ip_to_bytes
|
||
|
|
from .protocols import Arp, Ethernet, make_frame_arp
|
||
|
|
|
||
|
|
|
||
|
|
class ArpResponder:
|
||
|
|
"""Stateless. Decides whether to answer an ARP request, and how."""
|
||
|
|
|
||
|
|
def handle(self, frame: Ethernet) -> bytes | None:
|
||
|
|
try:
|
||
|
|
arp = Arp.parse(frame.payload)
|
||
|
|
except ValueError:
|
||
|
|
return None
|
||
|
|
|
||
|
|
if arp.opcode != ARP_REQUEST:
|
||
|
|
# We don't track replies; they shouldn't happen anyway since
|
||
|
|
# we never send ARP requests to the chip.
|
||
|
|
return None
|
||
|
|
|
||
|
|
# Only answer requests that come from our own STA.
|
||
|
|
if bytes(arp.spa) != ip_to_bytes(STA_IP):
|
||
|
|
return None
|
||
|
|
|
||
|
|
target_ip = bytes(arp.tpa)
|
||
|
|
# We answer for *any* IP — the only network the chip can reach
|
||
|
|
# is via the gateway, so all IPs map to GATEWAY_MAC.
|
||
|
|
reply = Arp(
|
||
|
|
opcode=ARP_REPLY,
|
||
|
|
sha=GATEWAY_MAC,
|
||
|
|
spa=target_ip,
|
||
|
|
tha=arp.sha,
|
||
|
|
tpa=arp.spa,
|
||
|
|
)
|
||
|
|
return make_frame_arp(arp.sha, GATEWAY_MAC, reply)
|