SatCat5
eth_arp.cc
1 // Copyright 2021-2025 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 
6 #include <satcat5/eth_arp.h>
7 #include <satcat5/eth_dispatch.h>
8 #include <satcat5/ip_table.h>
9 #include <satcat5/log.h>
10 
11 namespace eth = satcat5::eth;
12 namespace ip = satcat5::ip;
13 namespace log = satcat5::log;
18 
19 // Set log verbosity (0/1/2)
20 static const unsigned DEBUG_VERBOSE = 0;
21 
22 // Protocol-related constants:
23 static const u16 ARP_HTYPE_ETHERNET = 0x0001;
24 static const u16 ARP_PTYPE_IPV4 = 0x0800;
25 static const u8 ARP_HLEN_ETHERNET = 6;
26 static const u8 ARP_PLEN_IPV4 = 4;
27 static const u16 ARP_OPER_QUERY = 0x0001;
28 static const u16 ARP_OPER_REPLY = 0x0002;
29 
30 bool ArpHeader::read_from(satcat5::io::Readable* src) {
31  // Reject anything that's too short to be a valid ARP packet.
32  if (src->get_read_ready() < 28) return false;
33 
34  // Read the packet contents:
35  // Reference: IETF-RFC-826 https://datatracker.ietf.org/doc/html/rfc826
36  // See also: https://en.wikipedia.org/wiki/Address_Resolution_Protocol
37  u16 htype = src->read_u16(); // Hardware type (Ethernet = 1)
38  u16 ptype = src->read_u16(); // Protocol type (IPv4 = 0x0800)
39  u8 hlen = src->read_u8(); // Hardware address length (Ethernet = 6)
40  u8 plen = src->read_u8(); // Protocol address length (IPv4 = 4)
41  oper = src->read_u16(); // Operation (1 = request, 2 = reply)
42  src->read_obj(sha); // Sender hardware address (MAC)
43  src->read_obj(spa); // Sender protocol address (IPv4)
44  src->read_obj(tha); // Target hardware address (MAC)
45  src->read_obj(tpa); // Target protocol address (IPv4)
46 
47  // Log the received message?
48  if (DEBUG_VERBOSE > 0)
49  log::Log(log::INFO, "ProtoArp: Rcvd from").write(spa.value);
50 
51  // Ignore anything that's not a IPv4-to-MAC response/query.
52  if (htype != ARP_HTYPE_ETHERNET) return false;
53  if (ptype != ARP_PTYPE_IPV4) return false;
54  if (hlen != ARP_HLEN_ETHERNET) return false;
55  if (plen != ARP_PLEN_IPV4) return false;
56 
57  // Sanity check for valid source addresses.
58  if (sha == eth::MACADDR_NONE) return false;
59  if (sha == eth::MACADDR_BROADCAST) return false;
60  return true; // Success!
61 }
62 
64  wr->write_u16(ARP_HTYPE_ETHERNET); // Hardware type (Ethernet = 1)
65  wr->write_u16(ARP_PTYPE_IPV4); // Protocol type (IPv4 = 0x0800)
66  wr->write_u8(ARP_HLEN_ETHERNET); // Hardware address length (Ethernet = 6)
67  wr->write_u8(ARP_PLEN_IPV4); // Protocol address length (IPv4 = 4)
68  wr->write_u16(oper); // Operation (1 = request, 2 = reply)
69  wr->write_obj(sha); // Sender hardware address (MAC)
70  wr->write_obj(spa); // Sender protocol address (IPv4)
71  wr->write_obj(tha); // Target hardware address (MAC)
72  wr->write_obj(tpa); // Target protocol address (IPv4)
73 }
74 
76  eth::Dispatch* dispatch,
77  const ip::Addr& ipaddr)
78  : eth::Protocol(dispatch, eth::ETYPE_ARP)
79  , m_ipaddr(ipaddr)
80  , m_table(0)
81 {
82  // Nothing else to initialize.
83 }
84 
85 bool ProtoArp::send_announce(const VlanTag& vtag) const {
86  // Psuedo-request method, preferred per RFC5227:
87  // https://datatracker.ietf.org/doc/html/rfc5227#section-3
88  return send_internal(
89  ARP_OPER_QUERY, // ARP query
90  vtag, // User-specified VLAN tag
91  eth::MACADDR_BROADCAST, // Destination = Broadcast
92  m_iface->macaddr(), // Announce SHA = Our MAC
93  m_ipaddr, // Announce SPA = Our IP
94  eth::MACADDR_NONE, // Announce THA = Zero (Required)
95  m_ipaddr); // Announce TPA = Our IP
96 }
97 
98 bool ProtoArp::send_probe(const ip::Addr& target, const VlanTag& vtag) {
99  // Probe-request method from RFC5227:
100  // https://www.rfc-editor.org/rfc/rfc5227#section-2.1
101  // (i.e., Check if someone else is using our IP address.)
102  return send_internal(
103  ARP_OPER_QUERY, // ARP query
104  vtag, // User-specified VLAN tag
105  eth::MACADDR_BROADCAST, // Destination = Broadcast
106  m_iface->macaddr(), // Probe SHA = Our MAC
107  ip::ADDR_NONE, // Probe SPA = Zero (Required)
108  eth::MACADDR_NONE, // Probe THA = Zero (Required)
109  target); // Probe TPA = Target IP
110 }
111 
112 bool ProtoArp::send_query(const ip::Addr& target, const VlanTag& vtag) {
113  // Send a query for the designated target:
114  return send_internal(
115  ARP_OPER_QUERY, // ARP query
116  vtag, // User-specified VLAN tag
117  eth::MACADDR_BROADCAST, // Destination = Broadcast
118  m_iface->macaddr(), // Query SHA = Our MAC
119  m_ipaddr, // Query SPA = Our IP
120  eth::MACADDR_BROADCAST, // Query THA = Placeholder
121  target); // Query TPA = Target IP
122 }
123 
124 void ProtoArp::gateway_change(const ip::Addr& dstaddr, const ip::Addr& gateway) {
125  ArpListener* item = m_listeners.head();
126  while (item) {
127  item->gateway_change(dstaddr, gateway);
128  item = m_listeners.next(item);
129  }
130 }
131 
133  if (DEBUG_VERBOSE > 1)
134  log::Log(log::DEBUG, "ProtoArp: frame_rcvd");
135 
136  // Attempt to read the incoming message.
137  // Proceed only if it's a valid Ethernet/IPv4 message.
138  ArpHeader hdr;
139  if (!hdr.read_from(&src)) return;
140 
141  // Does this query have a valid SHA/SPA pair?
142  if (hdr.sha.is_unicast() && hdr.spa.is_unicast()) {
143  // Send notifications to any registered ARP event listeners.
144  ArpListener* item = m_listeners.head();
145  while (item) {
146  item->arp_event(hdr.sha, hdr.spa);
147  item = m_listeners.next(item);
148  }
149  }
150  // Note: Replies have a valid THA/TPA pair, but we ignore it.
151  // Normal replies have our own address, which we already know.
152  // Broadcast replies are discouraged by RFC5227.
153 
154  // Query for our address or a matching proxy? Send a response.
155  eth::MacAddr reply_spa = match(hdr);
156  if (reply_spa != eth::MACADDR_NONE) {
157  // Target is an echo of the SHA/SPA fields from the request.
158  // Note: Per RFC5225 Section 2, reply to the requester only.
159  if (DEBUG_VERBOSE > 0)
160  log::Log(log::DEBUG, "ProtoArp: Sending reply");
161  auto vtag = m_iface->reply_vtag();
162  send_internal(ARP_OPER_REPLY, vtag, hdr.sha, reply_spa, hdr.tpa, hdr.sha, hdr.spa);
163  } else if (DEBUG_VERBOSE > 1) {
164  log::Log(log::DEBUG, "ProtoArp: No reply").write(hdr.tpa);
165  }
166 }
167 
168 eth::MacAddr ProtoArp::match(const ArpHeader& hdr) const {
169  // Simple check: Is this a query for our IP address?
170  if (hdr.oper != ARP_OPER_QUERY) return eth::MACADDR_NONE;
171  if (hdr.tpa == m_ipaddr) return m_iface->macaddr();
172 
173  // If a routing table is configured, lookup the target address.
174  // Proxy may be disabled, routed to self, or routed to a specific address.
175  if (!m_table) return eth::MACADDR_NONE;
176  auto route = m_table->route_lookup(hdr.tpa);
177  if (!route.is_proxy_arp()) return eth::MACADDR_NONE;
178  return (route.dstmac == eth::MACADDR_NONE) ? m_iface->macaddr() : route.dstmac;
179 }
180 
181 bool ProtoArp::send_internal(u16 opcode,
182  const eth::VlanTag& vtag,
183  const eth::MacAddr& dst,
184  const eth::MacAddr& sha,
185  const ip::Addr& spa,
186  const eth::MacAddr& tha,
187  const ip::Addr& tpa) const
188 {
189  // Sanity check: TPA should always be a valid unicast address, except
190  // for replies to a host with no valid IP address (e.g., DHCP setup).
191  if (!(opcode == ARP_OPER_REPLY || tpa.is_unicast())) {
192  if (DEBUG_VERBOSE > 0)
193  log::Log(log::DEBUG, "ProtoArp: Invalid TPA").write(tpa);
194  return false;
195  }
196 
197  // Start with the Ethernet frame header...
199  m_iface->open_write(dst, eth::ETYPE_ARP, vtag);
200  if (!wr) return false; // Unable to proceed?
201 
202  // Write packet contents and finalize.
203  wr->write_u16(ARP_HTYPE_ETHERNET);
204  wr->write_u16(ARP_PTYPE_IPV4);
205  wr->write_u8(ARP_HLEN_ETHERNET);
206  wr->write_u8(ARP_PLEN_IPV4);
207  wr->write_u16(opcode);
208  wr->write_obj(sha); // SHA = Our MAC-address (usually)
209  wr->write_obj(spa); // SPA = Source IP (varies)
210  wr->write_obj(tha); // THA = Target MAC (varies)
211  wr->write_obj(tpa); // TPA = Target IP (varies)
212  return wr->write_finalize(); // Send OK?
213 }
Callback interface for responding to ARP and ICMP events.
Definition: eth_arp.h:46
virtual void gateway_change(const satcat5::ip::Addr &dstaddr, const satcat5::ip::Addr &gateway)
Callback for changes to gateway configuration.
Definition: eth_arp.h:56
virtual void arp_event(const satcat5::eth::MacAddr &mac, const satcat5::ip::Addr &ip)=0
Callback for any announced MAC/IP address pair.
Implemention of "net::Dispatch" for Ethernet frames.
Definition: eth_dispatch.h:21
satcat5::io::Writeable * open_write(const satcat5::eth::MacAddr &dst, const satcat5::eth::MacType &type, satcat5::eth::VlanTag vtag=satcat5::eth::VTAG_NONE)
Send a frame to the designated Ethernet address/VLAN.
Definition: eth_dispatch.cc:74
Protocol handler for Ethernet-to-IPv4 ARP queries and replies.
Definition: eth_arp.h:68
bool send_query(const satcat5::ip::Addr &target, const satcat5::eth::VlanTag &vtag=satcat5::eth::VTAG_NONE)
Send a query for a given IP address.
Definition: eth_arp.cc:112
ProtoArp(satcat5::eth::Dispatch *dispatcher, const satcat5::ip::Addr &ipaddr=satcat5::ip::ADDR_NONE)
Attach this ARP handler to an eth::Dispatch interface.
Definition: eth_arp.cc:75
void gateway_change(const satcat5::ip::Addr &dstaddr, const satcat5::ip::Addr &gateway)
Notify all listeners of a change in gateway configuration.
Definition: eth_arp.cc:124
bool send_probe(const satcat5::ip::Addr &target, const satcat5::eth::VlanTag &vtag=satcat5::eth::VTAG_NONE)
Send a probe to test if a given address is occupied.
Definition: eth_arp.cc:98
void frame_rcvd(satcat5::io::LimitedRead &src) override
New-frame notifications from the parent interface.
Definition: eth_arp.cc:132
bool send_announce(const satcat5::eth::VlanTag &vtag=satcat5::eth::VTAG_NONE) const
Send an unsolicited ARP announcement.
Definition: eth_arp.cc:85
Accept incoming Ethernet packets by EtherType.
Definition: eth_protocol.h:24
satcat5::eth::Dispatch *const m_iface
Parent interface (e.g., for address and I/O)
Definition: eth_protocol.h:38
Limited read of next N bytes.
Definition: io_readable.h:255
Abstract API for reading byte-streams and packets.
Definition: io_readable.h:68
bool read_obj(T &t)
Templated wrapper for any object with the following method: bool read_from(satcat5::io::Readable* rd)...
Definition: io_readable.h:140
u8 read_u8()
One of many functions for reading integer/floating point values, see details.
Definition: io_readable.cc:44
virtual unsigned get_read_ready() const =0
How many bytes can be read without blocking?
Abstract API for writing byte-streams and packets.
Definition: io_writeable.h:24
void write_u8(u8 data)
One of many functions for writing integer/floating point values, see details.
Definition: io_writeable.cc:20
virtual bool write_finalize()
Mark end of frame and release temporary working data.
void write_obj(const T &obj)
Templated wrapper for any object with the following method: void write_to(satcat5::io::Writeable* wr)...
Definition: io_writeable.h:104
satcat5::ip::Route route_lookup(const satcat5::ip::Addr &dstaddr) const
Next-hop routing lookup for the given destination address.
Definition: ip_table.cc:199
The Log class creates and formats one log message.
Definition: log.h:195
Log & write(const char *str)
Formatting methods for various data types.
Definition: log.cc:198
T * next(const T *item) const
Fetch pointer to the next item.
Definition: list.h:261
Protocol handler for the Address Resolution Protocol (ARP)
Internet Protocol v4 (IPv4) forwarding table.
Diagnostic logging to UART and/or Ethernet ports.
Address Resolution Protocol header.
Definition: eth_arp.h:23
satcat5::eth::MacAddr sha
ARP header fields:
Definition: eth_arp.h:27
void write_to(satcat5::io::Writeable *wr) const
Write header contents to the specified destination.
Definition: eth_arp.cc:63
satcat5::ip::Addr spa
ARP header fields:
Definition: eth_arp.h:28
satcat5::ip::Addr tpa
ARP header fields:
Definition: eth_arp.h:28
bool read_from(satcat5::io::Readable *rd)
Attempt to read and validate the header.
Definition: eth_arp.cc:30
u16 oper
ARP header fields:
Definition: eth_arp.h:26
satcat5::eth::MacAddr tha
ARP header fields:
Definition: eth_arp.h:27
An Ethernet MAC address (with serializable interface).
Definition: eth_header.h:29
bool is_unicast() const
Any normal single-destination address.
Definition: eth_header.cc:74
Header contents for an 802.1Q Virtual-LAN tag.
Definition: eth_header.h:124
IPv4 address is a 32-bit unsigned integer.
Definition: ip_core.h:15
u32 value
Raw access to the underlying representation.
Definition: ip_core.h:17
bool is_unicast() const
Any normal single-destination address.
Definition: ip_core.cc:59