1use log::error;
6use std::collections::HashMap;
7use {
8 fidl_fuchsia_net as fnet, fidl_fuchsia_net_neighbor as fnet_neighbor,
9 fidl_fuchsia_net_neighbor_ext as fnet_neighbor_ext,
10};
11
12use super::Id;
13
14#[derive(Debug, Clone)]
15#[cfg_attr(test, derive(PartialEq, Eq))]
16pub(crate) enum NeighborHealth {
17 Unknown,
18 Healthy { last_observed: zx::MonotonicInstant },
19 Unhealthy { last_healthy: Option<zx::MonotonicInstant> },
20}
21
22impl NeighborHealth {
23 fn last_healthy(&self) -> Option<zx::MonotonicInstant> {
24 match self {
25 NeighborHealth::Unknown => None,
26 NeighborHealth::Healthy { last_observed } => Some(*last_observed),
27 NeighborHealth::Unhealthy { last_healthy } => *last_healthy,
28 }
29 }
30
31 fn transition(&self, now: zx::MonotonicInstant, state: fnet_neighbor::EntryState) -> Self {
54 match state {
55 fnet_neighbor::EntryState::Incomplete | fnet_neighbor::EntryState::Unreachable => {
56 NeighborHealth::Unhealthy { last_healthy: self.last_healthy() }
57 }
58 fnet_neighbor::EntryState::Reachable => NeighborHealth::Healthy { last_observed: now },
59 fnet_neighbor::EntryState::Delay
60 | fnet_neighbor::EntryState::Probe
61 | fnet_neighbor::EntryState::Static
62 | fnet_neighbor::EntryState::Stale => self.clone(),
63 }
64 }
65}
66
67#[derive(Debug, Clone)]
68#[cfg_attr(test, derive(PartialEq, Eq))]
69pub(crate) struct NeighborState {
70 health: NeighborHealth,
71}
72
73impl NeighborState {
74 #[cfg(test)]
75 pub(crate) const fn new(health: NeighborHealth) -> Self {
76 Self { health }
77 }
78}
79
80#[derive(Debug, Default)]
81#[cfg_attr(test, derive(Eq, PartialEq))]
82pub struct InterfaceNeighborCache {
83 pub(crate) neighbors: HashMap<fnet::IpAddress, NeighborState>,
84}
85
86impl InterfaceNeighborCache {
87 pub(crate) fn iter_health(
88 &self,
89 ) -> impl Iterator<Item = (&'_ fnet::IpAddress, &'_ NeighborHealth)> {
90 let Self { neighbors } = self;
91 neighbors.iter().map(|(n, NeighborState { health })| (n, health))
92 }
93}
94
95#[cfg(test)]
96impl FromIterator<(fnet::IpAddress, NeighborState)> for InterfaceNeighborCache {
97 fn from_iter<T: IntoIterator<Item = (fnet::IpAddress, NeighborState)>>(iter: T) -> Self {
98 Self { neighbors: FromIterator::from_iter(iter) }
99 }
100}
101
102#[derive(Debug, Default)]
104pub struct NeighborCache {
105 interfaces: HashMap<Id, InterfaceNeighborCache>,
106}
107
108impl NeighborCache {
109 pub fn process_neighbor_event(&mut self, e: fnet_neighbor::EntryIteratorItem) {
110 let Self { interfaces } = self;
111 enum Event {
112 Added,
113 Changed,
114 Removed,
115 }
116 let (event, entry) = match e {
117 fnet_neighbor::EntryIteratorItem::Existing(entry)
118 | fnet_neighbor::EntryIteratorItem::Added(entry) => (Event::Added, entry),
119 fnet_neighbor::EntryIteratorItem::Idle(fnet_neighbor::IdleEvent {}) => {
120 return;
121 }
122 fnet_neighbor::EntryIteratorItem::Changed(entry) => (Event::Changed, entry),
123 fnet_neighbor::EntryIteratorItem::Removed(entry) => (Event::Removed, entry),
124 };
125 let fnet_neighbor_ext::Entry { interface, neighbor, state, mac: _, updated_at } =
126 match fnet_neighbor_ext::Entry::try_from(entry) {
127 Ok(entry) => entry,
128 Err(e) => {
129 error!(e:? = e; "invalid neighbor entry");
130 return;
131 }
132 };
133 let updated_at = zx::MonotonicInstant::from_nanos(updated_at);
134
135 let InterfaceNeighborCache { neighbors } =
136 interfaces.entry(interface.get()).or_insert_with(Default::default);
137
138 match event {
139 Event::Added => match neighbors.entry(neighbor) {
140 std::collections::hash_map::Entry::Occupied(occupied) => {
141 error!(entry:? = occupied; "received entry for already existing neighbor");
142 return;
143 }
144 std::collections::hash_map::Entry::Vacant(vacant) => {
145 let _: &mut _ = vacant.insert(NeighborState {
146 health: NeighborHealth::Unknown.transition(updated_at, state),
147 });
148 }
149 },
150 Event::Changed => {
151 let NeighborState { health } = match neighbors.get_mut(&neighbor) {
152 Some(s) => s,
153 None => {
154 error!(neigh:? = neighbor; "got changed event for unseen neighbor");
155 return;
156 }
157 };
158 *health = health.transition(updated_at, state);
159 }
160 Event::Removed => match neighbors.remove(&neighbor) {
161 Some(NeighborState { .. }) => {
162 if neighbors.is_empty() {
163 InterfaceNeighborCache { .. } =
167 interfaces.remove(&interface.get()).unwrap();
168 }
169 }
170 None => {
171 error!(neigh:? = neighbor; "got removed event for unseen neighbor");
172 }
173 },
174 }
175 }
176
177 pub fn get_interface_neighbors(&self, interface: Id) -> Option<&InterfaceNeighborCache> {
178 let Self { interfaces } = self;
179 interfaces.get(&interface)
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use net_declare::fidl_ip;
187
188 const IFACE1: Id = 1;
189 const IFACE2: Id = 2;
190 const NEIGH1: fnet::IpAddress = fidl_ip!("192.0.2.1");
191 const NEIGH2: fnet::IpAddress = fidl_ip!("2001:db8::1");
192
193 struct EventSource {
194 now: zx::MonotonicInstant,
195 }
196
197 impl EventSource {
198 fn new() -> Self {
199 Self { now: zx::MonotonicInstant::from_nanos(0) }
200 }
201
202 fn advance_secs(&mut self, secs: u64) {
203 self.now += zx::MonotonicDuration::from_seconds(secs.try_into().unwrap());
204 }
205
206 fn entry(
207 &self,
208 interface: Id,
209 neighbor: fnet::IpAddress,
210 state: fnet_neighbor::EntryState,
211 ) -> NeighborEntry {
212 NeighborEntry { interface, neighbor, state, updated_at: self.now }
213 }
214
215 fn reachable(&self, interface: Id, neighbor: fnet::IpAddress) -> NeighborEntry {
216 self.entry(interface, neighbor, fnet_neighbor::EntryState::Reachable)
217 }
218
219 fn probe(&self, interface: Id, neighbor: fnet::IpAddress) -> NeighborEntry {
220 self.entry(interface, neighbor, fnet_neighbor::EntryState::Probe)
221 }
222
223 fn stale(&self, interface: Id, neighbor: fnet::IpAddress) -> NeighborEntry {
224 self.entry(interface, neighbor, fnet_neighbor::EntryState::Stale)
225 }
226
227 fn delay(&self, interface: Id, neighbor: fnet::IpAddress) -> NeighborEntry {
228 self.entry(interface, neighbor, fnet_neighbor::EntryState::Delay)
229 }
230
231 fn incomplete(&self, interface: Id, neighbor: fnet::IpAddress) -> NeighborEntry {
232 self.entry(interface, neighbor, fnet_neighbor::EntryState::Incomplete)
233 }
234
235 fn unreachable(&self, interface: Id, neighbor: fnet::IpAddress) -> NeighborEntry {
236 self.entry(interface, neighbor, fnet_neighbor::EntryState::Unreachable)
237 }
238 }
239
240 struct NeighborEntry {
241 interface: Id,
242 neighbor: fnet::IpAddress,
243 state: fnet_neighbor::EntryState,
244 updated_at: zx::MonotonicInstant,
245 }
246
247 impl NeighborEntry {
248 fn into_entry(self) -> fnet_neighbor::Entry {
249 let Self { interface, neighbor, state, updated_at } = self;
250 fnet_neighbor::Entry {
251 interface: Some(interface),
252 neighbor: Some(neighbor),
253 state: Some(state),
254 updated_at: Some(updated_at.into_nanos()),
255 ..Default::default()
256 }
257 }
258
259 fn into_added(self) -> fnet_neighbor::EntryIteratorItem {
260 fnet_neighbor::EntryIteratorItem::Added(self.into_entry())
261 }
262
263 fn into_changed(self) -> fnet_neighbor::EntryIteratorItem {
264 fnet_neighbor::EntryIteratorItem::Changed(self.into_entry())
265 }
266
267 fn into_removed(self) -> fnet_neighbor::EntryIteratorItem {
268 fnet_neighbor::EntryIteratorItem::Removed(self.into_entry())
269 }
270 }
271
272 impl NeighborCache {
273 fn assert_neighbors(
274 &self,
275 interface: Id,
276 it: impl IntoIterator<Item = (fnet::IpAddress, NeighborHealth)>,
277 ) {
278 let InterfaceNeighborCache { neighbors } =
279 self.get_interface_neighbors(interface).unwrap();
280 let it = it
281 .into_iter()
282 .map(|(n, health)| (n, NeighborState { health }))
283 .collect::<HashMap<_, _>>();
284 assert_eq!(neighbors, &it);
285 }
286 }
287
288 #[fuchsia::test]
289 fn caches_healthy_neighbors_per_interface() {
290 let mut cache = NeighborCache::default();
291 let mut events = EventSource::new();
292 cache.process_neighbor_event(events.reachable(IFACE1, NEIGH1).into_added());
293 cache.assert_neighbors(
294 IFACE1,
295 [(NEIGH1, NeighborHealth::Healthy { last_observed: events.now })],
296 );
297
298 events.advance_secs(1);
299 cache.process_neighbor_event(events.reachable(IFACE2, NEIGH2).into_added());
300 cache.assert_neighbors(
301 IFACE2,
302 [(NEIGH2, NeighborHealth::Healthy { last_observed: events.now })],
303 );
304 }
305
306 #[fuchsia::test]
307 fn updates_healthy_state() {
308 let mut cache = NeighborCache::default();
309 let mut events = EventSource::new();
310 cache.process_neighbor_event(events.reachable(IFACE1, NEIGH1).into_added());
311 cache.assert_neighbors(
312 IFACE1,
313 [(NEIGH1, NeighborHealth::Healthy { last_observed: events.now })],
314 );
315
316 events.advance_secs(3);
317 cache.process_neighbor_event(events.reachable(IFACE1, NEIGH1).into_changed());
318 cache.assert_neighbors(
319 IFACE1,
320 [(NEIGH1, NeighborHealth::Healthy { last_observed: events.now })],
321 );
322 }
323
324 #[fuchsia::test]
325 fn probe_reachable_stale() {
326 let mut cache = NeighborCache::default();
327 let mut events = EventSource::new();
328
329 cache.process_neighbor_event(events.probe(IFACE1, NEIGH1).into_added());
330 cache.assert_neighbors(IFACE1, [(NEIGH1, NeighborHealth::Unknown)]);
331 events.advance_secs(1);
332
333 cache.process_neighbor_event(events.reachable(IFACE1, NEIGH1).into_changed());
334 cache.assert_neighbors(
335 IFACE1,
336 [(NEIGH1, NeighborHealth::Healthy { last_observed: events.now })],
337 );
338
339 let last_healthy = events.now;
340 events.advance_secs(1);
341 cache.process_neighbor_event(events.stale(IFACE1, NEIGH1).into_changed());
342 cache.assert_neighbors(
343 IFACE1,
344 [(NEIGH1, NeighborHealth::Healthy { last_observed: last_healthy })],
345 );
346 }
347
348 #[fuchsia::test]
349 fn stale_delay_reachable() {
350 let mut cache = NeighborCache::default();
351 let mut events = EventSource::new();
352
353 cache.process_neighbor_event(events.stale(IFACE1, NEIGH1).into_added());
354 cache.assert_neighbors(IFACE1, [(NEIGH1, NeighborHealth::Unknown)]);
355
356 events.advance_secs(1);
357 cache.process_neighbor_event(events.delay(IFACE1, NEIGH1).into_changed());
358 cache.assert_neighbors(IFACE1, [(NEIGH1, NeighborHealth::Unknown)]);
359
360 events.advance_secs(1);
361 cache.process_neighbor_event(events.reachable(IFACE1, NEIGH1).into_changed());
362 cache.assert_neighbors(
363 IFACE1,
364 [(NEIGH1, NeighborHealth::Healthy { last_observed: events.now })],
365 );
366 }
367
368 #[fuchsia::test]
369 fn reachable_unreachable() {
370 let mut cache = NeighborCache::default();
371 let mut events = EventSource::new();
372
373 cache.process_neighbor_event(events.reachable(IFACE1, NEIGH1).into_added());
374 cache.assert_neighbors(
375 IFACE1,
376 [(NEIGH1, NeighborHealth::Healthy { last_observed: events.now })],
377 );
378
379 let last_healthy = Some(events.now);
380 events.advance_secs(1);
381 cache.process_neighbor_event(events.unreachable(IFACE1, NEIGH1).into_changed());
382 cache.assert_neighbors(IFACE1, [(NEIGH1, NeighborHealth::Unhealthy { last_healthy })]);
383 }
384
385 #[fuchsia::test]
386 fn probe_incomplete() {
387 let mut cache = NeighborCache::default();
388 let mut events = EventSource::new();
389
390 cache.process_neighbor_event(events.probe(IFACE1, NEIGH1).into_added());
391 cache.assert_neighbors(IFACE1, [(NEIGH1, NeighborHealth::Unknown)]);
392
393 events.advance_secs(1);
394 cache.process_neighbor_event(events.incomplete(IFACE1, NEIGH1).into_changed());
395 cache
396 .assert_neighbors(IFACE1, [(NEIGH1, NeighborHealth::Unhealthy { last_healthy: None })]);
397 }
398
399 #[fuchsia::test]
400 fn stale_unreachable_probe_incomplete() {
401 let mut cache = NeighborCache::default();
402 let mut events = EventSource::new();
403
404 cache.process_neighbor_event(events.stale(IFACE1, NEIGH1).into_added());
405 cache.assert_neighbors(IFACE1, [(NEIGH1, NeighborHealth::Unknown)]);
406
407 events.advance_secs(1);
408 cache.process_neighbor_event(events.unreachable(IFACE1, NEIGH1).into_changed());
409 cache
410 .assert_neighbors(IFACE1, [(NEIGH1, NeighborHealth::Unhealthy { last_healthy: None })]);
411
412 events.advance_secs(1);
413 cache.process_neighbor_event(events.probe(IFACE1, NEIGH1).into_changed());
414 cache
415 .assert_neighbors(IFACE1, [(NEIGH1, NeighborHealth::Unhealthy { last_healthy: None })]);
416
417 events.advance_secs(1);
418 cache.process_neighbor_event(events.incomplete(IFACE1, NEIGH1).into_changed());
419 cache
420 .assert_neighbors(IFACE1, [(NEIGH1, NeighborHealth::Unhealthy { last_healthy: None })]);
421 }
422
423 #[fuchsia::test]
424 fn removing_last_neighbor_clears_interface_state() {
425 let mut cache = NeighborCache::default();
426 let events = EventSource::new();
427
428 cache.process_neighbor_event(events.probe(IFACE1, NEIGH1).into_added());
429 cache.assert_neighbors(IFACE1, [(NEIGH1, NeighborHealth::Unknown)]);
430
431 cache.process_neighbor_event(events.probe(IFACE1, NEIGH1).into_removed());
432 assert_eq!(cache.get_interface_neighbors(IFACE1), None);
433 }
434}