SatCat5
ip_table.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/ip_table.h>
7 #include <satcat5/log.h>
8 #include <satcat5/utils.h>
9 
11 using satcat5::eth::MACADDR_BROADCAST;
12 using satcat5::eth::MACADDR_NONE;
13 using satcat5::ip::Route;
15 using satcat5::ip::Table;
18 namespace ip = satcat5::ip;
19 
20 static constexpr Route ROUTE_NONE = {
21  ip::DEFAULT_ROUTE, ip::ADDR_NONE, MACADDR_NONE, 0, 0};
22 
23 static constexpr Route ROUTE_LOCAL = {
24  ip::DEFAULT_ROUTE, ip::ADDR_BROADCAST, MACADDR_NONE, 0, 0};
25 
26 inline constexpr Route simple_route(
27  const ip::Addr& addr, const MacAddr& dstmac, u8 port=0, u8 flags=0)
28  { return {{addr, ip::MASK_32}, addr, dstmac, port, flags}; }
29 
31  subnet.log_to(wr);
32  if (gateway == ip::ADDR_BROADCAST) {
33  wr.wr_str(" is Local");
34  } else {
35  wr.wr_str(" to ");
36  gateway.log_to(wr);
37  }
38  if (dstmac.is_valid()) {
39  wr.wr_str(" = ");
40  dstmac.log_to(wr);
41  }
42  if (port) {
43  wr.wr_str(", p");
44  wr.wr_d32(port);
45  }
46  if (flags) {
47  wr.wr_str(", f");
48  wr.wr_h32(flags, 2);
49  }
50 }
51 
53  : m_wridx_static(0)
54  , m_wridx_ephemeral(SATCAT5_ROUTING_TABLE-1)
55 {
56  // Nothing else to initialize
57 }
58 
60  wr.wr_str("Static routes");
61  if (route_rddef().gateway != ip::ADDR_NONE) {
62  wr.wr_str("\r\n D: ");
63  route_rddef().log_to(wr);
64  }
65  for (unsigned a = 0 ; a < m_wridx_static ; ++a) {
66  wr.wr_str("\r\n ");
67  wr.wr_d32(a);
68  wr.wr_str(": ");
69  route_read(a).log_to(wr);
70  }
71 }
72 
73 void Table::route_clear(bool lockdown) {
74  m_wridx_static = 0;
75  m_wridx_ephemeral = SATCAT5_ROUTING_TABLE-1;
76  route_default(lockdown ? ip::ADDR_NONE : ip::ADDR_BROADCAST);
77  for (unsigned a = 0 ; a < SATCAT5_ROUTING_TABLE ; ++a)
78  route_write(a, ROUTE_NONE);
79 }
80 
82  m_wridx_ephemeral = SATCAT5_ROUTING_TABLE-1;
83  for (unsigned a = 0 ; a < SATCAT5_ROUTING_TABLE ; ++a) {
84  // Rows with a user-provided MAC address are left as-is.
85  if (route_read(a).flags & Route::FLAG_MAC_FIXED) continue;
86  // Otherwise: static routes clear MAC address, ephemeral are deleted.
87  if (a < m_wridx_static) {
88  Route temp = route_read(a);
89  temp.dstmac = MACADDR_NONE;
90  route_write(a, temp);
91  } else {
92  route_write(a, ROUTE_NONE);
93  }
94  }
95 }
96 
98  const ip::Addr& gateway, const MacAddr& dstmac, u8 port, u8 flags)
99 {
100  // Routes with a user-provided MAC address are ineligible for cache updates.
101  if (dstmac.is_valid()) set_mask_u8(flags, Route::FLAG_MAC_FIXED);
102  return route_wrdef({satcat5::ip::DEFAULT_ROUTE, gateway, dstmac, port, flags});
103 }
104 
105 bool Table::route_simple(const ip::Addr& gateway, const ip::Mask& subnet) {
106  route_clear(); // Clear existing table contents.
107  route_default(gateway); // Default gateway for everything...
108  return route_local({gateway, subnet}); // ...except local connections.
109 }
110 
112  const ip::Subnet& subnet, const ip::Addr& gateway, const MacAddr& dstmac, u8 port, u8 flags)
113 {
114  // Routes with a user-provided MAC address are ineligible for cache updates.
115  if (dstmac.is_valid()) set_mask_u8(flags, Route::FLAG_MAC_FIXED);
116 
117  // Are we trying to set the default route?
118  if (subnet == ip::DEFAULT_ROUTE) {
119  return route_default(gateway, dstmac, port, flags);
120  }
121 
122  // Is this an update to an existing static route?
123  for (unsigned a = 0 ; a < m_wridx_static ; ++a) {
124  if (subnet == route_read(a).subnet) {
125  return route_write(a, {subnet, gateway, dstmac, port, flags});
126  }
127  }
128 
129  // Attempt to add a new table entry.
130  // (This may overwrite an ephemeral entry, if present.)
131  if (m_wridx_static < SATCAT5_ROUTING_TABLE) {
132  return route_write(m_wridx_static++, {subnet, gateway, dstmac, port, flags});
133  } else {
134  return false; // Table is full.
135  }
136 }
137 
138 bool Table::route_cache(const ip::Addr& gateway, const MacAddr& dstmac) {
139  // Sanity check: Ignore invalid or multicast addresses.
140  if (!gateway.is_unicast()) return false;
141  if (!dstmac.is_unicast()) return false;
142 
143  // Update the gateway MAC address for matching cache-eligible entries.
144  // As we perform this search, find the narrowest matching subnet.
145  // TODO: Should we do anything to detect or mitigate ARP spoofing?
146  bool self_match = false;
147  for (unsigned a = 0 ; a < SATCAT5_ROUTING_TABLE ; ++a) {
148  const Route& tmp = route_read(a);
149  if (tmp.gateway == gateway) { // Matching gateway?
150  if (tmp.subnet.contains(gateway)) self_match = true;
151  if (!(tmp.flags & Route::FLAG_MAC_FIXED)) // Eligible for cache?
152  route_write(a, {tmp.subnet, tmp.gateway, dstmac, tmp.port, tmp.flags});
153  }
154  }
155 
156  // If there was a matching route, and that route includes the gateway
157  // itself, then we're done. Otherwise, create a new cache entry.
158  if (self_match) return true;
159 
160  // If table is completely full of static routes, no action is possible.
161  if (m_wridx_static >= SATCAT5_ROUTING_TABLE) return false;
162 
163  // Port number and other flags are copied from the best matching route,
164  // except that ephemeral routes cannot set the fixed-MAC-address flag.
165  Route best = route_lookup(gateway);
166  u8 flags = best.flags;
167  clr_mask_u8(flags, Route::FLAG_MAC_FIXED);
168 
169  // Otherwise, create a new entry or overwrite the oldest ephemeral entry.
170  // TODO: Do we need a proper LRU cache? This evicts by order of creation, not usage.
171  // TODO: How to implement LRU for a hardware-accelerated implementation?
172  if (m_wridx_ephemeral < m_wridx_static || m_wridx_ephemeral >= SATCAT5_ROUTING_TABLE)
173  m_wridx_ephemeral = SATCAT5_ROUTING_TABLE - 1; // Wraparound?
174  route_write(m_wridx_ephemeral--, simple_route(gateway, dstmac, best.port, flags));
175  return true;
176 }
177 
178 bool Table::route_remove(const ip::Subnet& subnet) {
179  // Search the static routing table for an exact match.
180  // If we find a match, swap it with the last table entry before
181  // deleting that row, so we don't leave a gap in the table.
182  for (unsigned a = 0 ; a < m_wridx_static ; ++a) {
183  if (route_read(a).subnet == subnet) {
184  unsigned last = --m_wridx_static;
185  if (a != last) route_write(a, route_read(last));
186  return route_write(last, ROUTE_NONE);
187  }
188  }
189 
190  // If we find an exact match in the dynamic entries, nullify it in place.
191  for (unsigned a = m_wridx_static ; a < SATCAT5_ROUTING_TABLE ; ++a) {
192  if (route_read(a).subnet == subnet) {
193  return route_write(a, ROUTE_NONE);
194  }
195  }
196  return false; // No match found
197 }
198 
199 Route Table::route_lookup(const ip::Addr& dstaddr) const {
200  // Handle multicast addresses and other special cases.
201  if (dstaddr.is_multicast()) return simple_route(dstaddr, MACADDR_BROADCAST);
202  if (dstaddr == ip::ADDR_NONE) return simple_route(ip::ADDR_NONE, MACADDR_NONE);
203 
204  // Scan the table to find the narrowest match ("longest prefix").
205  // Note: A narrow mask (e.g., /24 = 0xFFFFFF00) is numerically
206  // greater than a wide mask (e.g., /8 = 0xFF000000).
207  // Search always covers the entire table, static and ephemeral.
208  Route best = route_rddef();
209  for (unsigned a = 0 ; a < SATCAT5_ROUTING_TABLE ; ++a) {
210  const Route& tmp = route_read(a);
211  if (tmp.subnet.mask.value > best.subnet.mask.value &&
212  tmp.subnet.contains(dstaddr)) {
213  best = tmp;
214  }
215  }
216 
217  // Local routes are sent directly to the final hop.
218  if (best.gateway == ip::ADDR_BROADCAST) best.gateway = dstaddr;
219  return best;
220 }
221 
223  : m_route_default(ROUTE_LOCAL)
224  , m_route_table{ROUTE_NONE}
225 {
226  // Nothing else to initialize
227 }
228 
229 bool RouteArray::route_wrdef(const Route& route) {
230  m_route_default = route;
231  return true;
232 }
233 
234 bool RouteArray::route_write(unsigned idx, const Route& route) {
235  // The base method simply writes the new table contents.
236  // Children may override this method to take further action.
237  m_route_table[idx] = route;
238  return true;
239 }
An array of routes with read and write accessors.
Definition: ip_table.h:111
RouteArray()
Create an empty table.
Definition: ip_table.cc:222
virtual bool route_wrdef(const satcat5::ip::Route &route)
Internal methods used to access "m_route_default".
Definition: ip_table.cc:229
virtual bool route_write(unsigned idx, const satcat5::ip::Route &route)
Internal methods used to access "m_route_table".
Definition: ip_table.cc:234
const satcat5::ip::Route & route_rddef() const
Internal methods used to access "m_route_default".
Definition: ip_table.h:119
const satcat5::ip::Route & route_read(unsigned idx) const
Internal methods used to access "m_route_table".
Definition: ip_table.h:127
IPv4 forwarding table.
Definition: ip_table.h:144
Table()
Construct an empty table with a local default route.
Definition: ip_table.cc:52
bool route_default(const satcat5::ip::Addr &gateway, const satcat5::eth::MacAddr &dstmac=satcat5::eth::MACADDR_NONE, u8 port=0, u8 flags=0)
Set the default behavior when no other routes match.
Definition: ip_table.cc:97
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
void route_clear(bool lockdown=true)
Clear all routes, including the default.
Definition: ip_table.cc:73
bool route_static(const satcat5::ip::Subnet &subnet, const satcat5::ip::Addr &gateway, const satcat5::eth::MacAddr &dstmac=satcat5::eth::MACADDR_NONE, u8 port=0, u8 flags=0)
Create or update a single static route.
Definition: ip_table.cc:111
void log_to(satcat5::log::LogBuffer &wr) const
Create a log entry with the full contents of this table.
Definition: ip_table.cc:59
bool route_local(const satcat5::ip::Subnet &subnet, u8 port=0, u8 flags=0)
Create or update a local static route.
Definition: ip_table.h:192
bool route_cache(const satcat5::ip::Addr &gateway, const satcat5::eth::MacAddr &dstmac)
Update matching MAC address cache entries.
Definition: ip_table.cc:138
bool route_remove(const satcat5::ip::Subnet &subnet)
Remove a single static route.
Definition: ip_table.cc:178
bool route_simple(const satcat5::ip::Addr &gateway, const satcat5::ip::Mask &subnet=satcat5::ip::MASK_24)
Simplified one-step setup for a typical home network.
Definition: ip_table.cc:105
void route_flush()
Flush cached MAC-addresses.
Definition: ip_table.cc:81
Internal buffer used by the Log class.
Definition: log.h:134
void wr_h32(u32 val, unsigned nhex=8)
Write an integer (u32) in hexadecimal format.
Definition: log.cc:303
void wr_str(const char *str)
Write a null-terminated UTF-8 string.
Definition: log.cc:297
void wr_d32(u32 val, unsigned zpad=0)
Write an unsigned integer (u32) in decimal format.
Definition: log.cc:317
Internet Protocol v4 (IPv4) forwarding table.
Diagnostic logging to UART and/or Ethernet ports.
An Ethernet MAC address (with serializable interface).
Definition: eth_header.h:29
bool is_valid() const
Any nonzero address.
Definition: eth_header.cc:80
void log_to(satcat5::log::LogBuffer &wr) const
Format this field as a human-readable string.
Definition: eth_header.cc:86
bool is_unicast() const
Any normal single-destination address.
Definition: eth_header.cc:74
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_multicast() const
IP multicast (224.*.*.*)
Definition: ip_core.cc:41
bool is_unicast() const
Any normal single-destination address.
Definition: ip_core.cc:59
void log_to(satcat5::log::LogBuffer &wr) const
Format this IP address in standard form, e.g., "192.168.1.2".
Definition: ip_core.cc:12
IPv4 subnet masks share functionality with a basic address, but are constructed differently to match ...
Definition: ip_core.h:66
A single entry in the static routing table.
Definition: ip_table.h:57
satcat5::eth::MacAddr dstmac
Next-hop MAC address.
Definition: ip_table.h:61
u8 flags
Additional flags.
Definition: ip_table.h:63
void log_to(satcat5::log::LogBuffer &wr) const
Format a one-line log entry containing all route parameters.
Definition: ip_table.cc:30
static constexpr u8 FLAG_MAC_FIXED
Fixed MAC-address for this route? If this flag is set, then the user specified the MAC address.
Definition: ip_table.h:74
satcat5::ip::Subnet subnet
Subnet address + mask.
Definition: ip_table.h:59
u8 port
Next-hop port number.
Definition: ip_table.h:62
satcat5::ip::Addr gateway
Next-hop IPv4 address.
Definition: ip_table.h:60
An IPv4 subnet consists of a base address and a subnet mask.
Definition: ip_core.h:87
void log_to(satcat5::log::LogBuffer &wr) const
Format this subnet in CIDR form, e.g., "192.168.1.0/24".
Definition: ip_core.cc:29
satcat5::ip::Mask mask
Subnet mask.
Definition: ip_core.h:90
constexpr bool contains(const satcat5::ip::Addr &other) const
Does this subnet contain the given address?
Definition: ip_core.h:97
Miscellaneous mathematical utility functions.
void clr_mask_u8(volatile u8 &val, u8 mask)
Set or clear bit masks.
Definition: utils.h:22
void set_mask_u8(volatile u8 &val, u8 mask)
Set or clear bit masks.
Definition: utils.h:21