SatCat5
router2_dispatch.cc
1 // Copyright 2024-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_sw_log.h>
7 #include <satcat5/ip_dispatch.h>
8 #include <satcat5/ip_icmp.h>
9 #include <satcat5/ip_table.h>
10 #include <satcat5/log.h>
11 #include <satcat5/router2_deferfwd.h>
12 #include <satcat5/router2_dispatch.h>
13 #include <satcat5/router2_offload.h>
14 #include <satcat5/utils.h>
15 
20 using satcat5::ip::checksum;
23 using satcat5::log::Log;
26 
27 // Set verbosity level for debugging (0/1/2)
28 static constexpr unsigned DEBUG_VERBOSE = 0;
29 
30 Dispatch::Dispatch(u8* buff, unsigned nbytes)
31  : SwitchCore(buff, nbytes)
32  , m_defer_fwd(0)
33  , m_local_port(this)
34  , m_local_iface(0)
35  , m_offload(0)
36  , m_port_shdn(0)
37 {
38  // Nothing else to initialize.
39 }
40 
41 satcat5::ip::Addr Dispatch::ipaddr() const {
42  return m_local_iface ? m_local_iface->ipaddr() : satcat5::ip::ADDR_NONE;
43 }
44 
45 satcat5::eth::MacAddr Dispatch::macaddr() const {
46  return m_local_iface ? m_local_iface->macaddr() : satcat5::eth::MACADDR_NONE;
47 }
48 
49 void Dispatch::set_ipaddr(const satcat5::ip::Addr& addr) {
50  if (m_local_iface) m_local_iface->set_ipaddr(addr);
51  if (m_offload) m_offload->reconfigure();
52 }
53 
55  // Attempt to read the Ethernet and partial IPv4 headers.
56  PluginPacket meta{};
57  if (!meta.read_from(packet)) {
58  debug_log(packet, SwitchLogMessage::DROP_BADFRM);
59  return 0;
60  }
61 
62  // If successful, attempt router processing and log errors.
63  unsigned result = deliver_pkt(meta);
64  if (!result) {
65  u8 reason = meta.reason()
66  ? meta.reason() : SwitchLogMessage::DROP_UNKNOWN;
67  debug_log(meta.pkt, reason);
68  }
69  return result;
70 }
71 
72 unsigned Dispatch::deliver_pkt(satcat5::eth::PluginPacket& meta) {
73  // Update statistics before additional rule checks.
74  process_stats(meta);
75 
76  // Enforce various drop-silently rules from IETF RFC-1812.
77  // Note: Ignore fragmentation, since all ports have the same MTU.
78  if (meta.hdr.dst.is_l2multicast()) return 0;
79  if (meta.hdr.dst.is_swcontrol()) return 0;
80  if (meta.hdr.src.is_multicast()) return 0;
81  if (meta.hdr.src.is_swcontrol()) return 0;
82  if (meta.is_ip()) {
83  if (meta.ip.src().is_multicast()) return 0;
84  if (meta.ip.src().is_reserved()) return 0;
85  if (meta.ip.dst().is_reserved()) return 0;
86  if (meta.hdr.dst.is_multicast() && !meta.ip.dst().is_multicast()) return 0;
87  }
88 
89  // Query applicable plugins (PluginPort or PluginCore).
90  // (In particular, we rely on this for VLAN rules enforcement.)
91  auto plg_result = process_plugins(meta);
92  if (plg_result) return plg_result.value();
93 
94  // Further processing based on EtherType:
95  if (meta.is_arp() && meta.src_port() == m_local_port.port_index()) {
96  // Forward ARP messages from the internal stack based on the target address.
97  if (DEBUG_VERBOSE > 1) Log(DEBUG, "router.deliver.arp_out");
98  return deliver_arp(meta);
99  } else if (meta.is_arp()) {
100  // Forward ARP messages from external ports to the internal stack.
101  // (ARP messages are never forwarded from one port to another.)
102  if (DEBUG_VERBOSE > 1) Log(DEBUG, "router.deliver.arp_from").write10((u32)meta.src_port());
103  return deliver_local(meta);
104  } else if (meta.is_ip() && meta.ip.dst() == ipaddr()) {
105  // IPv4 packets sent to the router itself.
106  if (DEBUG_VERBOSE > 1) Log(DEBUG, "router.deliver.ip_self").write10((u32)meta.src_port());
107  return deliver_local(meta);
108  } else if (meta.is_ip()) {
109  // IPv4 packets sent to other destinations.
110  if (DEBUG_VERBOSE > 1) Log(DEBUG, "router.deliver.ip_from").write10((u32)meta.src_port());
111  return process_gateway(meta);
112  } else {
113  // Drop all other packets.
114  // TODO: Add DMZ support for non-IPv4 traffic?
115  if (DEBUG_VERBOSE > 1) Log(DEBUG, "router.deliver.drop").write10((u32)meta.src_port());
116  return 0;
117  }
118 }
119 
120 unsigned Dispatch::deliver_arp(satcat5::eth::PluginPacket& meta) {
121  // Sanity check this is a valid Ethernet/IPv4 ARP message.
122  if (!meta.is_arp()) return 0;
123 
124  // Read the Ethernet and ARP message headers.
125  // (All required packet headers are in the first 44 bytes.)
126  auto rd = meta.pkt->peek();
129  bool ok = rd.read_obj(eth) && rd.read_obj(arp);
130  if (!ok) return 0;
131 
132  // Route lookup based on the "target protocol address" (TPA) field.
133  if (DEBUG_VERBOSE > 1) Log(DEBUG, "router.arp.tpa").write(arp.tpa);
134  if (!m_local_iface) return 0;
135  auto route = m_local_iface->route_lookup(arp.tpa);
136  if (DEBUG_VERBOSE > 0) Log(DEBUG, "router.arp_to").write10((u32)route.port);
137 
138  // Forward to the requested destination(s).
139  meta.dst_mask &= satcat5::eth::idx2mask(route.port);
140  if (route.port == m_local_port.port_index()) return 0;
141  return deliver_offload(meta) + deliver_switch(meta);
142 }
143 
144 unsigned Dispatch::deliver_defer(const satcat5::eth::PluginPacket& meta) {
145  // Unknown next-hop MAC address, handoff to the deferred forwarding system.
146  // (If that queue is full, silently drop the packet.)
147  if (DEBUG_VERBOSE > 0) Log(DEBUG, "router.defer").write(meta.ip.dst());
148  return (m_defer_fwd && m_defer_fwd->accept(meta)) ? 1 : 0;
149 }
150 
151 unsigned Dispatch::deliver_local(const PluginPacket& meta) {
152  // Write this packet to the local port adapter.
153  // This eventually delivers it to the local IP/ICMP/UDP stack.
154  // (If that queue is full, silently drop the packet.)
155  if (DEBUG_VERBOSE > 0) Log(DEBUG, "router.local").write(meta.hdr.type.value);
156  return m_local_port.accept(meta.dst_mask, meta.pkt) ? 1 : 0;
157 }
158 
159 unsigned Dispatch::deliver_offload(const PluginPacket& meta) {
160  // Write this packet to the hardware-accelerated offload port.
161  // (If that queue is full, silently drop the packet.)
162  if (DEBUG_VERBOSE > 1) Log(DEBUG, "router.offload").write(meta.hdr.type.value);
163  if (m_offload) m_offload->deliver(meta);
164  return 0; // Data is already copied, so returned refcount is always zero.
165 }
166 
167 unsigned Dispatch::process_gateway(PluginPacket& meta) {
168  // Read and validate the full IPv4 header, including options.
169  // (Initial parsing hasn't validated the IPv4 checksum.)
170  // TODO: How to preserve VLAN metadata in the hardware-accelerated case?
171  MultiPacket::Reader rd(meta.pkt);
172  if (!rd.read_obj(meta.hdr)) return 0;
173  if (!rd.read_obj(meta.ip)) return 0;
174  if (DEBUG_VERBOSE > 1) Log(DEBUG, "router.gateway.start").write(meta.ip.dst());
175 
176  // Decrement TTL if possible, otherwise reply with an error.
177  // (This response is required for "tracert", among other things.)
178  if (!decrement_ttl(meta)) {
179  icmp_reply(satcat5::ip::ICMP_TTL_EXPIRED, 0, meta);
180  return 0; // Discard the original packet.
181  }
182 
183  // Lookup destination address in the routing table.
184  if (!m_local_iface) return 0;
185  auto route = m_local_iface->route_lookup(meta.ip.dst());
186  if (DEBUG_VERBOSE > 1) Log(DEBUG, "router.gateway.route\r\n ").write_obj(route);
187 
188  // Warn for packet at gateway with incorrect dst mac address
189  if(meta.hdr.dst != m_local_iface->macaddr()){
190  if (DEBUG_VERBOSE > 1) Log(WARNING, "Packet received at gateway with dest").write(meta.hdr.dst);
191  }
192 
193  // Update the destination mask if applicable.
194  // Multicast support is provided by the igmp::Server plugin.
195  // Do not allow loopback of multicast or broadcast packets.
196  if (route.is_unicast()) meta.dst_mask &= satcat5::eth::idx2mask(route.port);
197  else meta.dst_mask &= ~meta.src_mask();
198 
199  // Is this packet deliverable?
200  if (!route.is_deliverable()) {
201  icmp_reply(satcat5::ip::ICMP_UNREACHABLE_NET, 0, meta);
202  return 0; // Discard the original packet.
203  } else if (!meta.dst_mask) {
204  icmp_reply(satcat5::ip::ICMP_NET_PROHIBITED, 0, meta);
205  return 0; // Discarded due to plugin rules.
206  }
207 
208  // Check if destination port(s) are in shutdown.
209  meta.dst_mask &= link_up_mask();
210  if (!meta.dst_mask) {
211  icmp_reply(satcat5::ip::ICMP_UNREACHABLE_NET, 0, meta);
212  return 0; // Discard the original packet.
213  }
214 
215  // Multicast packets from the offload port should disable loopback.
216  // (FPGA logic has already forwarded this packet to hardware ports.)
217  bool multi_offload = route.is_multicast() && is_from_offload(meta);
218  if (multi_offload) clr_mask(meta.dst_mask, m_offload->port_mask_all());
219  if (!meta.dst_mask) return 0; // Already forwarded by FPGA logic?
220 
221  // If the destination port is the same as the source, let the sender
222  // know a more direct path is available. Packets from the offload port
223  // stop here, all others continue forwarding the original packet.
224  if (route.port == meta.src_port()) {
225  icmp_reply(satcat5::ip::ICMP_REDIRECT_HOST, route.gateway.value, meta);
226  if (is_from_offload(meta)) return 0; // Already forwarded by FPGA logic?
227  }
228 
229  // Can this packet be delivered immediately?
230  if (route.has_dstmac()) {
231  // Forward directly to the next-hop MAC address and port(s).
232  if (DEBUG_VERBOSE > 0) Log(DEBUG, "router.gateway.fwd_to").write10((u32)route.port);
233  adjust_mac(route.dstmac, meta);
234  if (DEBUG_VERBOSE > 0 && m_debug) meta.pkt->copy_to(m_debug);
235  return deliver_offload(meta) + deliver_switch(meta);
236  } else {
237  // MAC unknown, must wait for ARP response from next-hop IP address.
238  if (DEBUG_VERBOSE > 1) Log(DEBUG, "router.gateway.defer").write10((u32)route.port);
239  return deliver_defer(meta);
240  }
241 }
242 
243 void Dispatch::adjust_mac(const MacAddr& dst, PluginPacket& meta) {
244  // In-place replacement of the destination and source MAC address.
245  // (Both fields are guaranteed to be in the first MultiPacket "chunk".)
246  u8* const dptr = meta.pkt->m_chunks.head()->m_data;
247  satcat5::io::ArrayWrite wr(dptr, 12); // Just enough for DST + SRC
248  wr.write_obj(dst); // Destination MAC address
249  wr.write_obj(macaddr()); // Source MAC address
250 }
251 
253  // If time-to-live (TTL) is zero, abort.
254  if (meta.ip.ttl() == 0) return false;
255 
257  unsigned iphdr = meta.hdr.vtag.value ? 18 : 14;
258  unsigned ipttl = iphdr + 8;
259  unsigned ipchk = iphdr + 10;
260 
261  // Decrement the TTL field and update the IP-header checksum, using
262  // the method discussed in IETF RFC-1141 in light of RFC-1624.
263  // (Both fields are guaranteed to be in the first MultiPacket "chunk".)
264  u8* const dptr = meta.pkt->m_chunks.head()->m_data;
265  u8 incr = (dptr[ipchk+1] == 0xFF) ? 2 : 1; // RFC-1624 edge-case?
266  dptr[ipttl] -= 1; // Decrement TTL
267  dptr[ipchk] += incr; // Update checksum
268  return true; // Continue processing
269 }
270 
271 // Note: The ICMP messages we care about are Time Exceeded, Destination
272 // Unreachable, and Redirect, which all have more-or-less the same format.
273 // https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol#Destination_unreachable
274 static constexpr unsigned ICMP_WORDS = 4;
275 static constexpr unsigned ECHO_WORDS = satcat5::ip::ICMP_ECHO_BYTES / 2;
276 
277 bool Dispatch::icmp_reply(u16 errtyp, u32 arg, const PluginPacket& meta) {
278  satcat5::eth::Header rx_eth;
279  satcat5::ip::Header rx_ip, tx_ip;
280  u16 tx_icmp[ICMP_WORDS];
281  u16 tx_echo[ECHO_WORDS];
282 
283  // Don't send errors to ourselves (potential for loops).
284  if (meta.ip.dst() == ipaddr()) return false;
285 
286  // Prohibit ICMP replies to certain packet types.
287  if (meta.ip.frg()) return false;
288  if (meta.ip.dst().is_multicast()) return false;
289 
290  // Make sure packet was intended for the gateway
291  if(meta.hdr.dst != m_local_iface->macaddr()) return false;
292 
293  // Read the full Eth+IPv4 header and the first 8 bytes of the datagram contents.
294  MultiPacket::Reader rd(meta.pkt);
295  if (!rd.read_obj(rx_eth)) return false;
296  if (!rd.read_obj(rx_ip)) return false;
297  for (unsigned a = 0 ; a < ECHO_WORDS ; ++a)
298  tx_echo[a] = rd.read_u16();
299 
300  // Construct the ICMP header, including checksum.
301  // TODO: I think there is a checksum bug somewhere here,
302  // wireshark shows incorrect checksum on icmp redirects
303  u16 chk_echo = checksum(ECHO_WORDS, tx_echo, rx_ip.chk());
304  tx_icmp[0] = errtyp; // Reply type + subtype
305  tx_icmp[1] = 0; // Placeholder for checksum
306  tx_icmp[2] = (u16)(arg >> 16); // Reply argument (varies)
307  tx_icmp[3] = (u16)(arg >> 0);
308  tx_icmp[1] = checksum(ICMP_WORDS, tx_icmp, chk_echo);
309 
310  // Is the reply interface ready to go?
311  // Injecting back into router stack up -> use router macaddr
312  satcat5::io::Writeable* wr = m_local_iface->iface()
313  ->open_write(m_local_iface->macaddr(), satcat5::eth::ETYPE_IPV4, rx_eth.vtag);
314  if (!wr) return false;
315 
316  // Construct the IPv4 header for the reply.
317  unsigned tx_bytes = 2*ICMP_WORDS + 4*rx_ip.ihl() + 2*ECHO_WORDS;
318  tx_ip = m_local_iface->next_header(satcat5::ip::PROTO_ICMP, rx_ip.src(), tx_bytes);
319 
320  // Formulate and send the response.
321  wr->write_obj(tx_ip);
322  for (unsigned a = 0 ; a < ICMP_WORDS ; ++a)
323  wr->write_u16(tx_icmp[a]);
324  wr->write_obj(rx_ip);
325  for (unsigned a = 0 ; a < ECHO_WORDS ; ++a)
326  wr->write_u16(tx_echo[a]);
327  return wr->write_finalize();
328 }
329 
330 bool Dispatch::is_from_offload(const PluginPacket& meta) {
331  // Check the source mask against the offload port, if it's enabled.
332  return m_offload && !!(m_offload->port_mask_all() & meta.src_mask());
333 }
334 
335 SATCAT5_PMASK_TYPE Dispatch::link_up_mask() {
336  // Any ports currently in shutdown? Poll offload ports if connected.
337  SATCAT5_PMASK_TYPE link_up = ~m_port_shdn;
338  if (m_offload) clr_mask(link_up, m_offload->link_shdn_sw());
339  return link_up;
340 }
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
unsigned deliver_switch(const satcat5::eth::PluginPacket &pkt)
Internal event-handlers called from deliver(...).
Definition: eth_switch.cc:248
satcat5::util::optional< unsigned > process_plugins(satcat5::eth::PluginPacket &pkt)
Internal event-handlers called from deliver(...).
Definition: eth_switch.cc:187
void process_stats(const satcat5::eth::PluginPacket &pkt)
Internal event-handlers called from deliver(...).
Definition: eth_switch.cc:176
void debug_log(const satcat5::io::MultiPacket *pkt, u8 reason, SATCAT5_PMASK_TYPE dst=0) const
If logging is enabled, record the outcome for this packet.
Definition: eth_switch.cc:147
unsigned port_index() const
Port number for attachment to the parent SwitchCore.
Definition: eth_switch.h:292
bool accept(SATCAT5_PMASK_TYPE dst_mask, satcat5::io::MultiPacket *packet)
Accept delivery of a given packet?
Definition: eth_switch.cc:291
Ephemeral Writeable interface for a simple array.
Definition: io_writeable.h:127
Abstract API for writing byte-streams and packets.
Definition: io_writeable.h:24
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
Routing table shortcuts.
Definition: ip_dispatch.h:108
satcat5::ip::Header next_header(u8 protocol, const satcat5::ip::Addr &dst, unsigned inner_bytes, u8 ttl=SATCAT5_IP_TTL)
Create a basic IPv4 header with the specified information.
Definition: ip_dispatch.cc:79
The Log class creates and formats one log message.
Definition: log.h:195
Log & write10(s32 val)
Print integer as a decimal value with no leading zeros.
Definition: log.cc:261
Log & write_obj(const T &obj)
Templated wrapper for custom output formatting.
Definition: log.h:251
Log & write(const char *str)
Formatting methods for various data types.
Definition: log.cc:198
bool accept(const satcat5::eth::PluginPacket &meta)
Accept this packet into the queue?
Packet-processing pipeline for the IPv4 router.
unsigned deliver(satcat5::io::MultiPacket *packet) override
Override the MultiBuffer::deliver() method.
bool decrement_ttl(satcat5::eth::PluginPacket &meta)
u32 reconfigure()
Reload router IP address and MAC address.
SATCAT5_PMASK_TYPE port_mask_all() const
Return a port-mask containing all connected ports.
void deliver(const satcat5::eth::PluginPacket &meta)
Deliver a given packet to the hardware queue.
SATCAT5_PMASK_TYPE link_shdn_sw()
Mask indicating software-defined ports in the shutdown state.
Diagnostic logging system for the Ethernet switch.
#define SATCAT5_PMASK_TYPE
Set the integer type used to identify source and destination ports.
Definition: eth_switch.h:41
constexpr SATCAT5_PMASK_TYPE idx2mask(unsigned idx)
Macro converting port-index to a bit-mask.
Definition: eth_switch.h:53
Internet Protocol v4 (IPv4) forwarding table.
Diagnostic logging to UART and/or Ethernet ports.
constexpr s8 WARNING
Define basic priority codes for log messages.
Definition: log.h:111
constexpr s8 DEBUG
Define basic priority codes for log messages.
Definition: log.h:109
Address Resolution Protocol header.
Definition: eth_arp.h:23
satcat5::ip::Addr tpa
ARP header fields:
Definition: eth_arp.h:28
An Ethernet frame header.
Definition: eth_header.h:167
satcat5::eth::MacType type
Ethernet header fields.
Definition: eth_header.h:172
satcat5::eth::VlanTag vtag
Ethernet header fields.
Definition: eth_header.h:173
satcat5::eth::MacAddr src
Ethernet header fields.
Definition: eth_header.h:171
satcat5::eth::MacAddr dst
Ethernet header fields.
Definition: eth_header.h:170
An Ethernet MAC address (with serializable interface).
Definition: eth_header.h:29
bool is_l2multicast() const
L2 multicast (01:80:C2:*:*:* except link-local)
Definition: eth_header.cc:39
bool is_swcontrol() const
Link-local control (01:80:C2:00:00:*)
Definition: eth_header.cc:63
bool is_multicast() const
Broadcast, L2 multicast, or L3 multicast.
Definition: eth_header.cc:57
u16 value
The 16-bit value is stored in processor-native order.
Definition: eth_header.h:98
Ephemeral data structure provided to plugin callbacks.
Definition: eth_plugin.h:48
satcat5::io::MultiPacket * pkt
Complete packet contents.
Definition: eth_plugin.h:51
satcat5::ip::Header ip
Copy of additional header fields, if present.
Definition: eth_plugin.h:62
bool is_ip() const
Accessors and shortcuts for packet metadata.
Definition: eth_plugin.h:129
unsigned src_port() const
Accessors and shortcuts for packet metadata.
Definition: eth_plugin.h:141
bool is_arp() const
Accessors and shortcuts for packet metadata.
Definition: eth_plugin.h:131
SATCAT5_PMASK_TYPE dst_mask
Destination mask for which port(s) receive this packet.
Definition: eth_plugin.h:75
SATCAT5_PMASK_TYPE src_mask() const
Accessors and shortcuts for packet metadata.
Definition: eth_plugin.h:139
satcat5::eth::Header hdr
Copy of Ethernet header fields.
Definition: eth_plugin.h:55
A single 24-byte packet-log message.
Definition: eth_sw_log.h:74
u16 value
The 16-bit value holds VID, DEI, and PCP fields.
Definition: eth_header.h:126
A packet is a linked-list of memory blocks, plus metadata.
Definition: multi_buffer.h:91
bool copy_to(satcat5::io::Writeable *wr) const
Copy the packet contents to the specified destination.
Definition: multi_buffer.cc:36
satcat5::io::ArrayRead peek() const
Peek at the first chunk, up to SATCAT5_MBUFF_CHUNK bytes.
Definition: multi_buffer.cc:31
IPv4 address is a 32-bit unsigned integer.
Definition: ip_core.h:15
bool is_multicast() const
IP multicast (224.*.*.*)
Definition: ip_core.cc:41
bool is_reserved() const
Reserved blocks (0.*.*.* or 127.*.*.*)
Definition: ip_core.cc:50
Structure for holding an IPv4 Header, including options.
Definition: ip_core.h:180
constexpr u16 chk() const
< Checksum (incoming only)
Definition: ip_core.h:206
constexpr u8 ttl() const
< Time to Live (TTL)
Definition: ip_core.h:202
constexpr u16 frg() const
< Fragment offset
Definition: ip_core.h:198
constexpr unsigned ihl() const
< Header length (4-byte words)
Definition: ip_core.h:192
constexpr satcat5::ip::Addr dst() const
< Destination address
Definition: ip_core.h:210
constexpr satcat5::ip::Addr src() const
< Source address
Definition: ip_core.h:208
Miscellaneous mathematical utility functions.
void clr_mask(T &val, T mask)
Set or clear bit masks.
Definition: utils.h:30