netlink/
neighbors.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! A module for managing neighbor information by receiving RTM_*NEIGH Netlink
6//! messages and maintaining neighbor table state from Netstack.
7
8use std::collections::{HashMap, HashSet};
9use std::net::IpAddr;
10use std::num::NonZeroU64;
11
12use crate::Errno;
13use crate::client::InternalClient;
14use crate::logging::{log_debug, log_warn};
15use crate::messaging::Sender;
16use crate::protocol_family::ProtocolFamily;
17use crate::protocol_family::route::NetlinkRoute;
18use crate::util::respond_to_completer;
19use derivative::Derivative;
20use futures::StreamExt as _;
21use futures::channel::oneshot;
22use net_types::ip::IpVersion;
23use netlink_packet_core::{NLM_F_MULTIPART, NetlinkMessage};
24use netlink_packet_route::neighbour::{
25    NeighbourAddress, NeighbourAttribute, NeighbourFlags, NeighbourHeader, NeighbourMessage,
26    NeighbourState,
27};
28use netlink_packet_route::route::RouteType;
29use netlink_packet_route::{AddressFamily, RouteNetlinkMessage};
30use thiserror::Error;
31
32use {
33    fidl_fuchsia_net as fnet, fidl_fuchsia_net_ext as fnet_ext,
34    fidl_fuchsia_net_neighbor as fnet_neighbor, fidl_fuchsia_net_neighbor_ext as fnet_neighbor_ext,
35};
36
37/// NetlinkNeighborMessage conversion related errors.
38#[derive(Debug, PartialEq)]
39pub(crate) enum NetlinkNeighborMessageConversionError {
40    /// Interface id could not be downcasted to fit into the expected u32.
41    InvalidInterfaceId(u64),
42}
43
44/// A wrapper type for the netlink_packet_route `NeighbourMessage` to enable conversions
45/// from [`fnet_neighbor_ext::Entry`].
46#[derive(Clone, Debug, Eq, PartialEq)]
47pub(crate) struct NetlinkNeighborMessage(pub(crate) NeighbourMessage);
48
49impl NetlinkNeighborMessage {
50    pub(crate) fn optionally_from(
51        neighbor: fnet_neighbor_ext::Entry,
52    ) -> Option<NetlinkNeighborMessage> {
53        match neighbor.try_into() {
54            Ok(message) => Some(message),
55            Err(NetlinkNeighborMessageConversionError::InvalidInterfaceId(id)) => {
56                log_warn!("Invalid interface id found in neighbor table entry: {}", id);
57                None
58            }
59        }
60    }
61
62    /// Wrap the inner [`NeighbourMessage`] in an [`RtnlMessage::NewNeighbour`].
63    pub(crate) fn into_rtnl_new_neighbor(
64        self,
65        sequence_number: u32,
66        is_dump: bool,
67    ) -> NetlinkMessage<RouteNetlinkMessage> {
68        let NetlinkNeighborMessage(message) = self;
69        let mut msg: NetlinkMessage<RouteNetlinkMessage> =
70            RouteNetlinkMessage::NewNeighbour(message).into();
71        msg.header.sequence_number = sequence_number;
72        if is_dump {
73            msg.header.flags |= NLM_F_MULTIPART;
74        }
75        msg.finalize();
76        msg
77    }
78}
79
80impl TryFrom<fnet_neighbor_ext::Entry> for NetlinkNeighborMessage {
81    type Error = NetlinkNeighborMessageConversionError;
82
83    fn try_from(
84        neighbor: fnet_neighbor_ext::Entry,
85    ) -> Result<NetlinkNeighborMessage, NetlinkNeighborMessageConversionError> {
86        let mut header = NeighbourHeader::default();
87        let fnet_ext::IpAddress(addr) = neighbor.neighbor.into();
88        header.family = match addr {
89            IpAddr::V4(_) => AddressFamily::Inet,
90            IpAddr::V6(_) => AddressFamily::Inet6,
91        };
92        header.ifindex = neighbor.interface.try_into().map_err(|_| {
93            NetlinkNeighborMessageConversionError::InvalidInterfaceId(neighbor.interface)
94        })?;
95        header.state = match neighbor.state {
96            fnet_neighbor::EntryState::Delay => NeighbourState::Delay,
97            fnet_neighbor::EntryState::Incomplete => NeighbourState::Incomplete,
98            fnet_neighbor::EntryState::Probe => NeighbourState::Probe,
99            fnet_neighbor::EntryState::Reachable => NeighbourState::Reachable,
100            fnet_neighbor::EntryState::Stale => NeighbourState::Stale,
101            fnet_neighbor::EntryState::Static => NeighbourState::Permanent,
102            fnet_neighbor::EntryState::Unreachable => NeighbourState::Failed,
103        };
104        // TODO(https://fxbug.dev/285127384): Can this sometimes be inferred from `addr`?
105        header.kind = RouteType::Unspec;
106
107        let mut attributes = vec![];
108        attributes.push(NeighbourAttribute::Destination(match addr {
109            IpAddr::V4(addr) => addr.into(),
110            IpAddr::V6(addr) => addr.into(),
111        }));
112        if let Some(mac) = neighbor.mac {
113            attributes.push(NeighbourAttribute::LinkLocalAddress(mac.octets.into()));
114        }
115        // TODO(https://fxbug.dev/285127384): Determine whether it's necessary
116        // to populate `CacheInfo`.
117
118        let mut msg = NeighbourMessage::default();
119        msg.header = header;
120        msg.attributes = attributes;
121        Ok(NetlinkNeighborMessage(msg))
122    }
123}
124
125/// Arguments for an RTM_GETNEIGH [`Request`].
126#[derive(Copy, Clone, Debug, PartialEq, Eq)]
127pub(crate) enum GetNeighborArgs {
128    Dump { ip_version: Option<IpVersion>, interface: Option<NonZeroU64> },
129    Get { ip: fnet::IpAddress, interface: NonZeroU64 },
130}
131
132impl GetNeighborArgs {
133    // Attempts to convert a netlink_packet_route `NeighbourMessage` into
134    // `GetNeighborArgs`.
135    pub(crate) fn try_from_rtnl_neighbor(
136        message: &NeighbourMessage,
137        is_dump: bool,
138    ) -> Result<Self, RequestError> {
139        if is_dump {
140            Self::dump_request_from_rtnl_neighbor(message)
141        } else {
142            Self::get_request_from_rtnl_neighbor(message)
143        }
144    }
145
146    fn dump_request_from_rtnl_neighbor(message: &NeighbourMessage) -> Result<Self, RequestError> {
147        let NeighbourHeader { family, flags, .. } = &message.header;
148        if flags.contains(NeighbourFlags::Proxy) {
149            // Netstack3 does not support ARP/NDP proxying.
150            log_warn!("unsupported flags in header for dump neighbor request: {flags:?}");
151            return Err(RequestError::UnsupportedRequest);
152        }
153        // TODO(https://fxbug.dev/456508664): Support strict validation of dump
154        // requests.
155        let ip_version = match family {
156            AddressFamily::Unspec => None,
157            AddressFamily::Inet => Some(IpVersion::V4),
158            AddressFamily::Inet6 => Some(IpVersion::V6),
159            family => {
160                log_warn!("invalid address family ({family:?}) in dump neighbors request");
161                return Err(RequestError::InvalidRequest);
162            }
163        };
164        // Note that the interface index is pulled from the attribute here,
165        // whereas it's pulled from the header for get requests. This is
166        // intentional, in order to maintain consistency with Linux's behavior.
167        let interface = message
168            .attributes
169            .iter()
170            .find_map(|attr| match attr {
171                NeighbourAttribute::IfIndex(ifindex) => Some(u64::from(*ifindex).try_into()),
172                _ => None,
173            })
174            .transpose()
175            // 0 is treated as a lack of filter.
176            .unwrap_or(None);
177        Ok(GetNeighborArgs::Dump { ip_version, interface })
178    }
179
180    fn get_request_from_rtnl_neighbor(message: &NeighbourMessage) -> Result<Self, RequestError> {
181        let NeighbourHeader { ifindex, family, state, flags, kind } = &message.header;
182        if *state != NeighbourState::None {
183            log_warn!("invalid state in header for get neighbor request: {state:?}");
184            return Err(RequestError::InvalidRequest);
185        }
186        if *kind != RouteType::Unspec {
187            log_warn!("invalid kind in header for get neighbor request: {kind:?}");
188            return Err(RequestError::InvalidRequest);
189        }
190        if flags.intersects(!NeighbourFlags::Proxy) {
191            log_warn!("invalid flags in header for get neighbor request: {flags:?}");
192            return Err(RequestError::InvalidRequest);
193        }
194        if flags.contains(NeighbourFlags::Proxy) {
195            // Netstack3 does not support ARP/NDP proxying.
196            log_warn!("unsupported flags in header for get neighbor request: {flags:?}");
197            return Err(RequestError::UnsupportedRequest);
198        }
199
200        let (address, unsupported) = message.attributes.iter().fold(
201            (None, false),
202            |(address_acc, unsupported_acc), attr| {
203                match attr {
204                    NeighbourAttribute::Destination(addr) => {
205                        // Note: In the event the Destination attribute is
206                        // provided multiple times, keep the first.
207                        (address_acc.or(Some(addr)), unsupported_acc)
208                    }
209                    _ => {
210                        if !unsupported_acc {
211                            // Only log for the first invalid attribute to avoid spamming.
212                            log_warn!(
213                                "unsupported request attribute: {attr:?} in get neighbor\
214                                request; only `DST` is supported"
215                            );
216                        }
217                        (address_acc, true)
218                    }
219                }
220            },
221        );
222        if unsupported {
223            return Err(RequestError::InvalidRequest);
224        }
225        let ip = match address {
226            Some(NeighbourAddress::Inet(addr)) => fnet_ext::IpAddress(IpAddr::V4(*addr)).into(),
227            Some(NeighbourAddress::Inet6(addr)) => fnet_ext::IpAddress(IpAddr::V6(*addr)).into(),
228            Some(_) => {
229                log_warn!("invalid neighbor address: {address:?} in get neighbor request");
230                return Err(RequestError::InvalidRequest);
231            }
232            None => {
233                log_warn!("get neighbor request missing required `DST` attribute");
234                return Err(RequestError::InvalidRequest);
235            }
236        };
237        let expected_family = match ip {
238            fnet::IpAddress::Ipv4(_) => AddressFamily::Inet,
239            fnet::IpAddress::Ipv6(_) => AddressFamily::Inet6,
240        };
241        if *family != expected_family {
242            log_warn!(
243                "address family mismatch in get neighbor request;\
244                destination={ip:?} family={family:?}"
245            );
246            return Err(RequestError::InvalidRequest);
247        }
248        // Note that the interface index is pulled from the header here, whereas
249        // it's pulled from the attribute for dump requests. This is
250        // intentional, in order to maintain consistency with Linux's behavior.
251        let interface = u64::from(*ifindex).try_into().map_err(|_| {
252            log_warn!("get neighbor request header must specify interface");
253            RequestError::InvalidRequest
254        })?;
255        Ok(GetNeighborArgs::Get { ip, interface })
256    }
257}
258
259/// [`Request`] arguments associated with neighbors.
260#[derive(Copy, Clone, Debug, PartialEq, Eq)]
261pub(crate) enum NeighborRequestArgs {
262    /// RTM_GETNEIGH
263    Get(GetNeighborArgs),
264}
265
266/// An error encountered while handling a [`Request`].
267#[derive(Copy, Clone, Debug, PartialEq, Eq)]
268pub(crate) enum RequestError {
269    /// Unsupported request.
270    UnsupportedRequest,
271    /// Invalid request.
272    InvalidRequest,
273    /// No such neighbor.
274    NotFound,
275}
276
277impl From<RequestError> for Errno {
278    fn from(value: RequestError) -> Self {
279        match value {
280            RequestError::UnsupportedRequest => Errno::ENOTSUP,
281            RequestError::InvalidRequest => Errno::EINVAL,
282            RequestError::NotFound => Errno::ENOENT,
283        }
284    }
285}
286
287/// A request associated with neighbors.
288#[derive(Derivative)]
289#[derivative(Debug(bound = ""))]
290pub(crate) struct Request<S: Sender<<NetlinkRoute as ProtocolFamily>::Response>> {
291    /// The resource and operation-specific argument(s) for this request.
292    pub args: NeighborRequestArgs,
293    /// The request's sequence number.
294    ///
295    /// This value will be copied verbatim into any message sent as a result of
296    /// this request.
297    pub sequence_number: u32,
298    /// The client that made the request.
299    pub client: InternalClient<NetlinkRoute, S>,
300    /// A completer that will have the result of the request sent over.
301    pub completer: oneshot::Sender<Result<(), RequestError>>,
302}
303
304/// Errors related to handling neighbor events from Netstack.
305#[derive(Debug, Error, PartialEq)]
306pub(crate) enum HandleWatchEventError {
307    /// An event indicated a neighbor was removed that was not previously known.
308    #[error("Netstack reported removal of an unknown neighbor: {0:?}")]
309    UnknownNeighborRemoved(fnet_neighbor_ext::Entry),
310    /// An event indicated a neighbor was changed that was not previously known.
311    #[error("Netstack reported change of an unknown neighbor: {0:?}")]
312    UnknownNeighborChanged(fnet_neighbor_ext::Entry),
313    /// An event indicated a neighbor was added that conflicts with a known
314    /// neighbor.
315    #[error(
316        "Netstack reported addition of a neighbor that already exists: \
317        existing={existing:?}, new={new:?}"
318    )]
319    ConflictingNeighborAdded { existing: fnet_neighbor_ext::Entry, new: fnet_neighbor_ext::Entry },
320    /// An `Existing` or `Idle` event was received after collecting the initial
321    /// neighbors from the event stream.
322    #[error("Netstack reported unexpected event: {0:?}")]
323    UnexpectedEventReceived(fnet_neighbor_ext::Event),
324}
325
326#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
327struct NeighborKey {
328    interface: u64,
329    neighbor: fnet::IpAddress,
330}
331
332impl From<&fnet_neighbor_ext::Entry> for NeighborKey {
333    fn from(
334        fnet_neighbor_ext::Entry { interface, neighbor, .. }: &fnet_neighbor_ext::Entry,
335    ) -> NeighborKey {
336        NeighborKey { interface: *interface, neighbor: *neighbor }
337    }
338}
339
340/// Handles asynchronous work related to RTM_*NEIGH messages.
341///
342/// Can respond to RTM_*NEIGH message requests.
343pub(crate) struct NeighborsWorker {
344    neighbor_table: HashMap<NeighborKey, fnet_neighbor_ext::Entry>,
345}
346
347impl NeighborsWorker {
348    /// Create the Netlink Neighbors Worker.
349    ///
350    /// Panics if the existing neighbors cannot be retrieved from
351    /// `neighbors_view` or if the response contains conflicting neighbors.
352    pub(crate) async fn create(
353        neighbors_view: &fnet_neighbor::ViewProxy,
354    ) -> (
355        Self,
356        impl futures::Stream<
357            Item = Result<fnet_neighbor_ext::Event, fnet_neighbor_ext::EntryIteratorError>,
358        > + Unpin
359        + 'static,
360    ) {
361        let mut neighbor_event_stream = Box::pin(
362            fnet_neighbor_ext::event_stream_from_view(neighbors_view)
363                .expect("connecting to fuchsia.net.neighbors.View FIDL should succeed"),
364        );
365        let existing_neighbors: HashSet<fnet_neighbor_ext::Entry> =
366            fnet_neighbor_ext::collect_neighbors_until_idle(neighbor_event_stream.by_ref())
367                .await
368                .expect("determining existing neighbors should succeed");
369        let existing_count = existing_neighbors.len();
370        let neighbor_table = existing_neighbors
371            .into_iter()
372            .map(|e| (NeighborKey::from(&e), e))
373            .collect::<HashMap<_, _>>();
374        assert_eq!(
375            neighbor_table.len(),
376            existing_count,
377            "conflicting existing entry in neighbor table"
378        );
379        (Self { neighbor_table }, neighbor_event_stream)
380    }
381
382    pub(crate) fn handle_neighbor_watcher_event(
383        &mut self,
384        event: fnet_neighbor_ext::Event,
385    ) -> Result<(), HandleWatchEventError> {
386        match event {
387            fnet_neighbor_ext::Event::Removed(entry) => {
388                match self.neighbor_table.remove(&(&entry).into()) {
389                    Some(_) => Ok(()),
390                    None => Err(HandleWatchEventError::UnknownNeighborRemoved(entry)),
391                }
392            }
393            fnet_neighbor_ext::Event::Added(entry) => {
394                match self.neighbor_table.insert((&entry).into(), entry.clone()) {
395                    Some(existing) => Err(HandleWatchEventError::ConflictingNeighborAdded {
396                        existing,
397                        new: entry,
398                    }),
399                    None => Ok(()),
400                }
401            }
402            fnet_neighbor_ext::Event::Changed(entry) => {
403                match self.neighbor_table.insert((&entry).into(), entry.clone()) {
404                    Some(_) => Ok(()),
405                    None => Err(HandleWatchEventError::UnknownNeighborChanged(entry)),
406                }
407            }
408            e @ fnet_neighbor_ext::Event::Existing(_) | e @ fnet_neighbor_ext::Event::Idle => {
409                Err(HandleWatchEventError::UnexpectedEventReceived(e))
410            }
411        }
412    }
413
414    pub(crate) fn handle_request<S: Sender<<NetlinkRoute as ProtocolFamily>::Response>>(
415        &mut self,
416        Request { args, mut client, sequence_number, completer }: Request<S>,
417    ) {
418        let result = match args {
419            NeighborRequestArgs::Get(args) => match args {
420                GetNeighborArgs::Dump { ip_version, interface } => {
421                    self.neighbor_table
422                        .values()
423                        .filter(|n| {
424                            ip_version.map_or(true, |ip_version| match n.neighbor {
425                                fnet::IpAddress::Ipv4(_) => ip_version == IpVersion::V4,
426                                fnet::IpAddress::Ipv6(_) => ip_version == IpVersion::V6,
427                            })
428                        })
429                        .filter(|n| interface.map_or(true, |i| n.interface == i.get()))
430                        .filter_map(|e| NetlinkNeighborMessage::optionally_from(e.clone()))
431                        .for_each(|m| {
432                            client.send_unicast(m.into_rtnl_new_neighbor(sequence_number, true));
433                        });
434                    Ok(())
435                }
436                GetNeighborArgs::Get { ip, interface } => {
437                    let neighbor = self
438                        .neighbor_table
439                        .get(&NeighborKey { interface: interface.get(), neighbor: ip })
440                        .map(|e| NetlinkNeighborMessage::optionally_from(e.clone()))
441                        .flatten();
442                    match neighbor {
443                        Some(msg) => {
444                            client.send_unicast(msg.into_rtnl_new_neighbor(sequence_number, false));
445                            Ok(())
446                        }
447                        None => Err(RequestError::NotFound),
448                    }
449                }
450            },
451        };
452
453        log_debug!("handled request {args:?} from {client} with result = {result:?}");
454        respond_to_completer(client, completer, result, args);
455    }
456}
457
458#[cfg(test)]
459mod tests {
460    use crate::client::testutil::{CLIENT_ID_1, new_fake_client};
461    use crate::interfaces::testutil::FakeInterfacesHandler;
462    use crate::messaging::testutil::FakeSender;
463    use crate::route_eventloop::{
464        EventLoopComponent, EventLoopInputs, EventLoopSpec, IncludedWorkers, Optional, Required,
465        UnifiedRequest,
466    };
467
468    use super::*;
469
470    use assert_matches::assert_matches;
471    use fidl_fuchsia_net as fnet;
472    use fidl_fuchsia_net_neighbor::ViewRequest;
473    use fidl_fuchsia_net_neighbor_ext::testutil::EventSpec;
474    use futures::channel::mpsc;
475    use futures::{FutureExt, SinkExt};
476    use net_declare::{fidl_ip, std_ip_v4, std_ip_v6};
477    use netlink_packet_core::NetlinkPayload;
478    use netlink_packet_route::neighbour::{NeighbourAddress, NeighbourFlags};
479    use test_case::test_case;
480
481    fn valid_neighbor_entry() -> fnet_neighbor_ext::Entry {
482        fnet_neighbor_ext::Entry {
483            interface: 1,
484            neighbor: fidl_ip!("192.168.0.1"),
485            state: fnet_neighbor::EntryState::Reachable,
486            mac: Some(fnet::MacAddress { octets: [0, 1, 2, 3, 4, 5] }),
487            updated_at: 123456,
488        }
489    }
490
491    #[test]
492    fn netlink_neighbor_message_from_entry_invalid_iface_id() {
493        let entry = fnet_neighbor_ext::Entry { interface: u64::MAX, ..valid_neighbor_entry() };
494
495        assert_eq!(
496            NetlinkNeighborMessage::try_from(entry),
497            Err(NetlinkNeighborMessageConversionError::InvalidInterfaceId(u64::MAX))
498        );
499    }
500
501    #[test]
502    fn netlink_neighbor_message_from_entry_valid_iface_id() {
503        assert_matches!(
504            NetlinkNeighborMessage::try_from(fnet_neighbor_ext::Entry {
505                interface: 1,
506                ..valid_neighbor_entry()
507            }),
508            Ok(NetlinkNeighborMessage(NeighbourMessage {
509                header: NeighbourHeader { ifindex: 1, .. },
510                ..
511            }))
512        );
513    }
514
515    #[test_case(fnet_neighbor::EntryState::Delay, NeighbourState::Delay; "delay")]
516    #[test_case(fnet_neighbor::EntryState::Incomplete, NeighbourState::Incomplete; "incomplete")]
517    #[test_case(fnet_neighbor::EntryState::Probe, NeighbourState::Probe; "probe")]
518    #[test_case(fnet_neighbor::EntryState::Reachable, NeighbourState::Reachable; "reachable")]
519    #[test_case(fnet_neighbor::EntryState::Stale, NeighbourState::Stale; "stale")]
520    #[test_case(fnet_neighbor::EntryState::Static, NeighbourState::Permanent; "permanent")]
521    #[test_case(fnet_neighbor::EntryState::Unreachable, NeighbourState::Failed; "failed")]
522    fn netlink_neighbor_message_from_entry_state_converted(
523        fidl_state: fnet_neighbor::EntryState,
524        expected: NeighbourState,
525    ) {
526        assert_matches!(
527            NetlinkNeighborMessage::try_from(fnet_neighbor_ext::Entry {
528                state: fidl_state,
529                ..valid_neighbor_entry()
530            }),
531            Ok(NetlinkNeighborMessage(NeighbourMessage {
532                header: NeighbourHeader { state, .. },
533                ..
534            })) if state == expected
535        );
536    }
537
538    #[test]
539    fn netlink_neighbor_message_from_entry_ipv4() {
540        let fidl_entry = fnet_neighbor_ext::Entry {
541            neighbor: fidl_ip!("192.168.0.1"),
542            ..valid_neighbor_entry()
543        };
544        let NetlinkNeighborMessage(message) =
545            fidl_entry.try_into().expect("should be able to convert valid neighbor entry");
546
547        assert_eq!(message.header.family, AddressFamily::Inet);
548        let expected_address: NeighbourAddress = std_ip_v4!("192.168.0.1").into();
549        assert_matches!(
550            &message.attributes[..],
551            [
552                NeighbourAttribute::Destination(address),
553                NeighbourAttribute::LinkLocalAddress(_)
554            ] if *address == expected_address
555        );
556    }
557
558    #[test]
559    fn netlink_neighbor_message_from_entry_ipv6() {
560        let fidl_entry =
561            fnet_neighbor_ext::Entry { neighbor: fidl_ip!("fe80::1"), ..valid_neighbor_entry() };
562        let NetlinkNeighborMessage(message) =
563            fidl_entry.try_into().expect("should be able to convert valid neighbor entry");
564
565        assert_eq!(message.header.family, AddressFamily::Inet6);
566        let expected_address: NeighbourAddress = std_ip_v6!("fe80::1").into();
567        assert_matches!(
568            &message.attributes[..],
569            [
570                NeighbourAttribute::Destination(address),
571                NeighbourAttribute::LinkLocalAddress(_)
572            ] if *address == expected_address
573        );
574    }
575
576    #[test]
577    fn netlink_neighbor_message_from_entry_address_link_local_present() {
578        let fidl_entry = fnet_neighbor_ext::Entry {
579            mac: Some(fnet::MacAddress { octets: [0, 1, 2, 3, 4, 5] }),
580            ..valid_neighbor_entry()
581        };
582        let NetlinkNeighborMessage(message) =
583            fidl_entry.try_into().expect("should be able to convert valid neighbor entry");
584
585        assert_matches!(
586            &message.attributes[..],
587            [
588                NeighbourAttribute::Destination(_),
589                NeighbourAttribute::LinkLocalAddress(addr)
590            ] if addr == &[0, 1, 2, 3, 4, 5]
591        );
592    }
593
594    #[test]
595    fn netlink_neighbor_message_from_entry_address_link_local_absent() {
596        let fidl_entry = fnet_neighbor_ext::Entry { mac: None, ..valid_neighbor_entry() };
597        let NetlinkNeighborMessage(message) =
598            fidl_entry.try_into().expect("should be able to convert valid neighbor entry");
599
600        assert_matches!(&message.attributes[..], [NeighbourAttribute::Destination(_)]);
601    }
602
603    #[test]
604    fn netlink_neighbor_message_optionally_from_failure() {
605        assert_eq!(
606            NetlinkNeighborMessage::optionally_from(fnet_neighbor_ext::Entry {
607                interface: u64::MAX,
608                ..valid_neighbor_entry()
609            }),
610            None
611        );
612    }
613
614    #[test]
615    fn netlink_neighbor_message_optionally_from_success() {
616        let fidl_entry = fnet_neighbor_ext::Entry {
617            interface: 1,
618            neighbor: fidl_ip!("192.168.0.1"),
619            state: fnet_neighbor::EntryState::Reachable,
620            mac: None,
621            updated_at: 123456,
622        };
623
624        let mut expected_message = NeighbourMessage::default();
625        expected_message.header = NeighbourHeader {
626            ifindex: 1,
627            family: AddressFamily::Inet,
628            state: NeighbourState::Reachable,
629            flags: NeighbourFlags::empty(),
630            kind: RouteType::Unspec,
631        };
632        expected_message.attributes =
633            vec![NeighbourAttribute::Destination(std_ip_v4!("192.168.0.1").into())];
634
635        assert_eq!(
636            NetlinkNeighborMessage::optionally_from(fidl_entry),
637            Some(NetlinkNeighborMessage(expected_message))
638        );
639    }
640
641    #[test]
642    fn netlink_neighbor_message_into_rtnl_new_neighbor() {
643        let message: NetlinkNeighborMessage = valid_neighbor_entry()
644            .try_into()
645            .expect("should be able to convert valid neighbor entry");
646        let NetlinkNeighborMessage(payload) = &message;
647
648        let expected_payload =
649            NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewNeighbour(payload.clone()));
650
651        let result = message.clone().into_rtnl_new_neighbor(1, true);
652        assert_eq!(result.payload, expected_payload);
653        assert_eq!(result.header.sequence_number, 1);
654        assert_eq!(result.header.flags & NLM_F_MULTIPART, NLM_F_MULTIPART);
655
656        let result = message.into_rtnl_new_neighbor(1, false);
657        assert_eq!(result.payload, expected_payload);
658        assert_ne!(result.header.flags & NLM_F_MULTIPART, NLM_F_MULTIPART);
659    }
660
661    #[test]
662    fn neighbor_keyed_by_interface_and_ip() {
663        let entry = fnet_neighbor_ext::Entry {
664            interface: 1,
665            neighbor: fidl_ip!("192.168.0.1"),
666            mac: None,
667            state: fnet_neighbor::EntryState::Reachable,
668            updated_at: 123456,
669        };
670
671        let same_iface_and_ip = fnet_neighbor_ext::Entry {
672            mac: Some(fnet::MacAddress { octets: [0, 1, 2, 3, 4, 5] }),
673            state: fnet_neighbor::EntryState::Stale,
674            updated_at: 654321,
675            ..entry
676        };
677        assert_eq!(NeighborKey::from(&entry), NeighborKey::from(&same_iface_and_ip));
678
679        let different_iface = fnet_neighbor_ext::Entry { interface: 2, ..entry };
680        assert_ne!(NeighborKey::from(&entry), NeighborKey::from(&different_iface));
681
682        let different_ip = fnet_neighbor_ext::Entry { neighbor: fidl_ip!("192.168.0.2"), ..entry };
683        assert_ne!(NeighborKey::from(&entry), NeighborKey::from(&different_ip));
684
685        let different_iface_and_ip =
686            fnet_neighbor_ext::Entry { interface: 2, neighbor: fidl_ip!("192.168.0.2"), ..entry };
687        assert_ne!(NeighborKey::from(&entry), NeighborKey::from(&different_iface_and_ip));
688    }
689
690    #[fuchsia::test]
691    #[should_panic(expected = "determining existing neighbors should succeed")]
692    async fn neighbors_worker_create_panics_on_view_protocol_error() {
693        let (view, view_server_end) = fidl::endpoints::create_proxy::<fnet_neighbor::ViewMarker>();
694        // Close the channel without responding.
695        drop(view_server_end);
696
697        let (_worker, _remaining) = NeighborsWorker::create(&view).await;
698    }
699
700    #[fuchsia::test]
701    #[should_panic(expected = "determining existing neighbors should succeed")]
702    async fn neighbors_worker_create_panics_on_event_stream_error() {
703        let (view, view_server_end) = fidl::endpoints::create_proxy::<fnet_neighbor::ViewMarker>();
704        let mut view_request_stream = view_server_end.into_stream();
705
706        let entry_iter_fut = view_request_stream
707            .next()
708            .then(|req| {
709                match req
710                    .expect("View request_stream unexpectedly ended")
711                    .expect("failed to receive `OpenEntryIterator` request")
712                {
713                    ViewRequest::OpenEntryIterator { it, .. } => {
714                        // Close the channel without responding.
715                        drop(it);
716                        futures::future::ready(())
717                    }
718                }
719            })
720            .fuse();
721
722        let worker_fut = NeighborsWorker::create(&view);
723
724        let ((), (_worker, _remaining)) = futures::join!(entry_iter_fut, worker_fut);
725    }
726
727    #[fuchsia::test]
728    #[should_panic(expected = "conflicting existing entry")]
729    async fn neighbors_worker_create_panics_on_conflicting_entry() {
730        let events: Vec<_> = [
731            // Create two neighbors with the same `NeighborKey` but differing
732            // fields; truly duplicate entries are ignored.
733            fnet_neighbor_ext::Entry {
734                state: fnet_neighbor::EntryState::Reachable,
735                ..valid_neighbor_entry()
736            },
737            fnet_neighbor_ext::Entry {
738                state: fnet_neighbor::EntryState::Stale,
739                ..valid_neighbor_entry()
740            },
741        ]
742        .into_iter()
743        .map(Into::into)
744        .map(fnet_neighbor::EntryIteratorItem::Existing)
745        .chain(std::iter::once(fnet_neighbor::EntryIteratorItem::Idle(fnet_neighbor::IdleEvent)))
746        .collect();
747        let batches = vec![events];
748        let (view, server_fut) =
749            fnet_neighbor_ext::testutil::create_fake_view(futures::stream::iter(batches));
750
751        let worker_fut = NeighborsWorker::create(&view);
752
753        let ((), (_worker, _remaining)) = futures::join!(server_fut, worker_fut);
754    }
755
756    #[fuchsia::test]
757    async fn neighbors_worker_create_success() {
758        use fnet_neighbor_ext::testutil::EventSpec::*;
759        let events = fnet_neighbor_ext::testutil::generate_events_from_spec(&[
760            Existing(1),
761            Existing(2),
762            Existing(3),
763            Idle,
764            Added(4),
765        ]);
766        let (view, server_fut) =
767            fnet_neighbor_ext::testutil::create_fake_view(futures::stream::iter(vec![
768                events.clone(),
769            ]));
770
771        let worker_fut = NeighborsWorker::create(&view);
772
773        let ((), (worker, event_stream)) = futures::join!(server_fut, worker_fut);
774
775        let remaining_events: Vec<_> = event_stream.collect().await;
776        assert_matches!(
777            &remaining_events[..],
778            [
779                Ok(fnet_neighbor_ext::Event::Added(_)),
780                Err(fnet_neighbor_ext::EntryIteratorError::Fidl(
781                    fidl::Error::ClientChannelClosed { .. }
782                ))
783            ]
784        );
785
786        for event in events {
787            match event {
788                fnet_neighbor::EntryIteratorItem::Existing(fidl_entry) => {
789                    let entry: fnet_neighbor_ext::Entry = fidl_entry.try_into().unwrap();
790                    assert_eq!(worker.neighbor_table.get(&(&entry).into()), Some(&entry));
791                }
792                _ => {}
793            }
794        }
795    }
796
797    #[test_case(
798        EventSpec::Added(2),
799        |e| matches!(e, HandleWatchEventError::ConflictingNeighborAdded { .. });
800        "conflicting added"
801    )]
802    #[test_case(
803        EventSpec::Removed(4),
804        |e| matches!(e, HandleWatchEventError::UnknownNeighborRemoved(_));
805        "unknown removed"
806    )]
807    #[test_case(
808        EventSpec::Changed(4),
809        |e| matches!(e, HandleWatchEventError::UnknownNeighborChanged(_));
810        "unknown changed"
811    )]
812    #[test_case(
813        EventSpec::Existing(4),
814        |e| matches!(e, HandleWatchEventError::UnexpectedEventReceived(_));
815        "existing after initial collection"
816    )]
817    #[test_case(
818        EventSpec::Idle,
819        |e| matches!(e, HandleWatchEventError::UnexpectedEventReceived(_));
820        "idle after initial collection"
821    )]
822    #[fuchsia::test]
823    async fn neighbors_worker_handle_watch_event_failure(
824        spec: EventSpec,
825        error_matcher: fn(&HandleWatchEventError) -> bool,
826    ) {
827        use fnet_neighbor_ext::testutil::EventSpec::*;
828        let events = fnet_neighbor_ext::testutil::generate_events_from_spec(&[
829            Existing(1),
830            Existing(2),
831            Existing(3),
832            Idle,
833            spec,
834        ]);
835        let (view, server_fut) =
836            fnet_neighbor_ext::testutil::create_fake_view(futures::stream::iter(vec![
837                events.clone(),
838            ]));
839
840        let worker_fut = NeighborsWorker::create(&view);
841
842        let ((), (mut worker, event_stream)) = futures::join!(server_fut, worker_fut);
843
844        let remaining_events: Vec<_> = event_stream.collect().await;
845        assert_eq!(remaining_events.len(), 2);
846        match &remaining_events[0] {
847            Ok(event) => {
848                assert_matches!(
849                    worker.handle_neighbor_watcher_event(event.clone()),
850                    Err(error) if error_matcher(&error)
851                );
852            }
853            _ => panic!("expected bad event in stream"),
854        }
855        match &remaining_events[1] {
856            Err(fnet_neighbor_ext::EntryIteratorError::Fidl(
857                fidl::Error::ClientChannelClosed { .. },
858            )) => {}
859            _ => panic!("expected PEER_CLOSED error at end of stream"),
860        }
861    }
862
863    #[fuchsia::test]
864    async fn neighbors_worker_handle_added_event() {
865        use fnet_neighbor_ext::testutil::EventSpec::*;
866        let events = fnet_neighbor_ext::testutil::generate_events_from_spec(&[
867            Existing(1),
868            Existing(2),
869            Existing(3),
870            Idle,
871            Added(4),
872        ]);
873        let (view, server_fut) =
874            fnet_neighbor_ext::testutil::create_fake_view(futures::stream::iter(vec![
875                events.clone(),
876            ]));
877
878        let worker_fut = NeighborsWorker::create(&view);
879
880        let ((), (mut worker, event_stream)) = futures::join!(server_fut, worker_fut);
881
882        let remaining_events: Vec<_> = event_stream.collect().await;
883        assert_eq!(remaining_events.len(), 2);
884        match &remaining_events[0] {
885            Ok(e @ fnet_neighbor_ext::Event::Added(entry)) => {
886                let key = NeighborKey::from(entry);
887                assert_eq!(worker.neighbor_table.get(&key), None);
888                assert_matches!(worker.handle_neighbor_watcher_event(e.clone()), Ok(_));
889                assert_eq!(worker.neighbor_table.get(&key), Some(entry));
890            }
891            _ => panic!("expected Added event in stream"),
892        }
893        match &remaining_events[1] {
894            Err(fnet_neighbor_ext::EntryIteratorError::Fidl(
895                fidl::Error::ClientChannelClosed { .. },
896            )) => {}
897            _ => panic!("expected PEER_CLOSED error at end of stream"),
898        }
899    }
900
901    #[fuchsia::test]
902    async fn neighbors_worker_handle_removed_event() {
903        use fnet_neighbor_ext::testutil::EventSpec::*;
904        let events = fnet_neighbor_ext::testutil::generate_events_from_spec(&[
905            Existing(1),
906            Existing(2),
907            Existing(3),
908            Idle,
909            Removed(2),
910        ]);
911        let (view, server_fut) =
912            fnet_neighbor_ext::testutil::create_fake_view(futures::stream::iter(vec![
913                events.clone(),
914            ]));
915
916        let worker_fut = NeighborsWorker::create(&view);
917
918        let ((), (mut worker, event_stream)) = futures::join!(server_fut, worker_fut);
919
920        let remaining_events: Vec<_> = event_stream.collect().await;
921        assert_eq!(remaining_events.len(), 2);
922        match &remaining_events[0] {
923            Ok(e @ fnet_neighbor_ext::Event::Removed(entry)) => {
924                let key = NeighborKey::from(entry);
925                assert_eq!(worker.neighbor_table.get(&key), Some(entry));
926                assert_matches!(worker.handle_neighbor_watcher_event(e.clone()), Ok(_));
927                assert_eq!(worker.neighbor_table.get(&key), None);
928            }
929            _ => panic!("expected Removed event in stream"),
930        }
931        match &remaining_events[1] {
932            Err(fnet_neighbor_ext::EntryIteratorError::Fidl(
933                fidl::Error::ClientChannelClosed { .. },
934            )) => {}
935            _ => panic!("expected PEER_CLOSED error at end of stream"),
936        }
937    }
938
939    #[fuchsia::test]
940    async fn neighbors_worker_handle_changed_event() {
941        use fnet_neighbor_ext::testutil::EventSpec::*;
942        let mut events = fnet_neighbor_ext::testutil::generate_events_from_spec(&[
943            Existing(1),
944            Existing(2),
945            Existing(3),
946            Idle,
947            Changed(2),
948        ]);
949        match &mut events[1] {
950            fnet_neighbor::EntryIteratorItem::Existing(entry) => {
951                entry.updated_at = Some(1234);
952            }
953            _ => panic!("expected Existing event in stream"),
954        }
955        match &mut events[4] {
956            fnet_neighbor::EntryIteratorItem::Changed(entry) => {
957                entry.updated_at = Some(5678);
958            }
959            _ => panic!("expected Changed event in stream"),
960        }
961
962        let (view, server_fut) =
963            fnet_neighbor_ext::testutil::create_fake_view(futures::stream::iter(vec![
964                events.clone(),
965            ]));
966
967        let worker_fut = NeighborsWorker::create(&view);
968
969        let ((), (mut worker, event_stream)) = futures::join!(server_fut, worker_fut);
970
971        let remaining_events: Vec<_> = event_stream.collect().await;
972        assert_eq!(remaining_events.len(), 2);
973        match &remaining_events[0] {
974            Ok(e @ fnet_neighbor_ext::Event::Changed(entry)) => {
975                let key = NeighborKey::from(entry);
976                assert_matches!(
977                    worker.neighbor_table.get(&key),
978                    Some(fnet_neighbor_ext::Entry { updated_at: 1234, .. })
979                );
980                assert_matches!(worker.handle_neighbor_watcher_event(e.clone()), Ok(_));
981                assert_matches!(
982                    worker.neighbor_table.get(&key),
983                    Some(fnet_neighbor_ext::Entry { updated_at: 5678, .. })
984                );
985            }
986            _ => panic!("expected Changed event in stream"),
987        }
988        match &remaining_events[1] {
989            Err(fnet_neighbor_ext::EntryIteratorError::Fidl(
990                fidl::Error::ClientChannelClosed { .. },
991            )) => {}
992            _ => panic!("expected PEER_CLOSED error at end of stream"),
993        }
994    }
995
996    #[fuchsia::test]
997    async fn neighbors_worker_handle_get_neighbor_not_found() {
998        let (mut sender_sink, client, _async_work_drain_task) =
999            new_fake_client(CLIENT_ID_1, vec![]);
1000        let (completer, completer_rcv) = oneshot::channel();
1001        let request = Request {
1002            args: NeighborRequestArgs::Get(GetNeighborArgs::Get {
1003                ip: fidl_ip!("192.168.0.1"),
1004                interface: NonZeroU64::new(1).unwrap(),
1005            }),
1006            sequence_number: 1,
1007            client,
1008            completer,
1009        };
1010
1011        let events: Vec<_> = [
1012            fnet_neighbor_ext::Entry {
1013                interface: 2,
1014                neighbor: fidl_ip!("192.168.0.1"),
1015                ..valid_neighbor_entry()
1016            },
1017            fnet_neighbor_ext::Entry {
1018                interface: 1,
1019                neighbor: fidl_ip!("fe80::2"),
1020                ..valid_neighbor_entry()
1021            },
1022        ]
1023        .into_iter()
1024        .map(Into::into)
1025        .map(fnet_neighbor::EntryIteratorItem::Existing)
1026        .chain(std::iter::once(fnet_neighbor::EntryIteratorItem::Idle(fnet_neighbor::IdleEvent)))
1027        .collect();
1028
1029        let batches = vec![events];
1030        let (view, server_fut) =
1031            fnet_neighbor_ext::testutil::create_fake_view(futures::stream::iter(batches));
1032
1033        let worker_fut = NeighborsWorker::create(&view);
1034        let ((), (mut worker, _event_stream)) = futures::join!(server_fut, worker_fut);
1035
1036        worker.handle_request(request);
1037
1038        let result = completer_rcv.await.expect("completer channel should not be closed");
1039        assert_matches!(result, Err(RequestError::NotFound));
1040        assert_eq!(&sender_sink.take_messages()[..], &[]);
1041    }
1042
1043    #[test_case(
1044        GetNeighborArgs::Dump{ ip_version: None, interface: None },
1045        &[1, 2, 3, 4];
1046        "dump all"
1047    )]
1048    #[test_case(
1049        GetNeighborArgs::Dump{ ip_version: Some(IpVersion::V4), interface: None },
1050        &[1, 3];
1051        "dump ipv4 only"
1052    )]
1053    #[test_case(
1054        GetNeighborArgs::Dump{ ip_version: Some(IpVersion::V6), interface: None },
1055        &[2, 4];
1056        "dump ipv6 only"
1057    )]
1058    #[test_case(
1059        GetNeighborArgs::Dump{
1060            ip_version: Some(IpVersion::V6),
1061            interface: Some(NonZeroU64::new(4).unwrap())
1062        },
1063        &[4];
1064        "dump interface 4 ipv6"
1065    )]
1066    #[test_case(
1067        GetNeighborArgs::Dump{
1068            ip_version: Some(IpVersion::V4),
1069            interface: Some(NonZeroU64::new(4).unwrap())
1070        },
1071        &[];
1072        "dump interface 4 ipv4"
1073    )]
1074    #[test_case(
1075        GetNeighborArgs::Get{ ip: fidl_ip!("192.168.0.1"), interface: NonZeroU64::new(1).unwrap() },
1076        &[1];
1077        "get ipv4"
1078    )]
1079    #[test_case(
1080        GetNeighborArgs::Get{ ip: fidl_ip!("fe80::2"), interface: NonZeroU64::new(2).unwrap() },
1081        &[2];
1082        "get ipv6"
1083    )]
1084    #[fuchsia::test]
1085    async fn neighbors_worker_handle_get_request(
1086        get_args: GetNeighborArgs,
1087        expected_ifindexes: &[u32],
1088    ) {
1089        let (mut sender_sink, client, _async_work_drain_task) =
1090            new_fake_client(CLIENT_ID_1, vec![]);
1091        let (completer, completer_rcv) = oneshot::channel();
1092        let request = Request {
1093            args: NeighborRequestArgs::Get(get_args),
1094            sequence_number: 1,
1095            client,
1096            completer,
1097        };
1098
1099        let events: Vec<_> = [
1100            fnet_neighbor_ext::Entry {
1101                interface: 1,
1102                neighbor: fidl_ip!("192.168.0.1"),
1103                ..valid_neighbor_entry()
1104            },
1105            fnet_neighbor_ext::Entry {
1106                interface: 2,
1107                neighbor: fidl_ip!("fe80::2"),
1108                ..valid_neighbor_entry()
1109            },
1110            fnet_neighbor_ext::Entry {
1111                interface: 3,
1112                neighbor: fidl_ip!("192.168.0.3"),
1113                ..valid_neighbor_entry()
1114            },
1115            fnet_neighbor_ext::Entry {
1116                interface: 4,
1117                neighbor: fidl_ip!("fe80::4"),
1118                ..valid_neighbor_entry()
1119            },
1120        ]
1121        .into_iter()
1122        .map(Into::into)
1123        .map(fnet_neighbor::EntryIteratorItem::Existing)
1124        .chain(std::iter::once(fnet_neighbor::EntryIteratorItem::Idle(fnet_neighbor::IdleEvent)))
1125        .collect();
1126
1127        let batches = vec![events];
1128        let (view, server_fut) =
1129            fnet_neighbor_ext::testutil::create_fake_view(futures::stream::iter(batches));
1130
1131        let worker_fut = NeighborsWorker::create(&view);
1132        let ((), (mut worker, _event_stream)) = futures::join!(server_fut, worker_fut);
1133
1134        worker.handle_request(request);
1135
1136        completer_rcv
1137            .await
1138            .expect("completer channel should not be closed")
1139            .expect("request handling result should be OK");
1140
1141        let mut ifindexes_seen = Vec::new();
1142        for sent_message in sender_sink.take_messages() {
1143            match sent_message.message.payload {
1144                NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewNeighbour(
1145                    NeighbourMessage { header: NeighbourHeader { ifindex, .. }, .. },
1146                )) => {
1147                    ifindexes_seen.push(ifindex);
1148                }
1149                _ => panic!("unexpected message sent"),
1150            }
1151        }
1152        ifindexes_seen.sort();
1153        assert_eq!(&ifindexes_seen[..], expected_ifindexes);
1154    }
1155
1156    #[test_case(
1157        false,
1158        NeighbourHeader {
1159            ifindex: 1,
1160            family: AddressFamily::Inet,
1161            ..Default::default()
1162        },
1163        &[
1164            NeighbourAttribute::Destination(std_ip_v4!("192.168.0.1").into()),
1165        ] => Ok(GetNeighborArgs::Get {
1166            ip: fidl_ip!("192.168.0.1"),
1167            interface: NonZeroU64::new(1).unwrap(),
1168        });
1169        "get ipv4 success"
1170    )]
1171    #[test_case(
1172        false,
1173        NeighbourHeader {
1174            ifindex: 1,
1175            family: AddressFamily::Inet6,
1176            ..Default::default()
1177        },
1178        &[
1179            NeighbourAttribute::Destination(std_ip_v6!("fe80::1").into()),
1180        ] => Ok(GetNeighborArgs::Get {
1181            ip: fidl_ip!("fe80::1"),
1182            interface: NonZeroU64::new(1).unwrap(),
1183        });
1184        "get ipv6 success"
1185    )]
1186    #[test_case(
1187        false,
1188        NeighbourHeader {
1189            ifindex: 1,
1190            family: AddressFamily::Inet,
1191            state: NeighbourState::Reachable,
1192            ..Default::default()
1193        },
1194        &[
1195            NeighbourAttribute::Destination(std_ip_v4!("192.168.0.1").into()),
1196        ] => Err(RequestError::InvalidRequest);
1197        "get invalid request state"
1198    )]
1199    #[test_case(
1200        false,
1201        NeighbourHeader {
1202            ifindex: 1,
1203            family: AddressFamily::Inet,
1204            kind: RouteType::Broadcast,
1205            ..Default::default()
1206        },
1207        &[
1208            NeighbourAttribute::Destination(std_ip_v4!("192.168.0.1").into()),
1209        ] => Err(RequestError::InvalidRequest);
1210        "get invalid request kind"
1211    )]
1212    #[test_case(
1213        false,
1214        NeighbourHeader {
1215            ifindex: 1,
1216            family: AddressFamily::Inet,
1217            flags: NeighbourFlags::Router,
1218            ..Default::default()
1219        },
1220        &[
1221            NeighbourAttribute::Destination(std_ip_v4!("192.168.0.1").into()),
1222        ] => Err(RequestError::InvalidRequest);
1223        "get invalid request flag"
1224    )]
1225    #[test_case(
1226        false,
1227        NeighbourHeader {
1228            ifindex: 1,
1229            family: AddressFamily::Inet,
1230            flags: NeighbourFlags::Proxy,
1231            ..Default::default()
1232        },
1233        &[
1234            NeighbourAttribute::Destination(std_ip_v4!("192.168.0.1").into()),
1235        ] => Err(RequestError::UnsupportedRequest);
1236        "get unsupported request flag"
1237    )]
1238    #[test_case(
1239        false,
1240        NeighbourHeader {
1241            ifindex: 1,
1242            family: AddressFamily::Inet6,
1243            ..Default::default()
1244        },
1245        &[
1246            NeighbourAttribute::Destination(std_ip_v4!("192.168.0.1").into()),
1247        ] => Err(RequestError::InvalidRequest);
1248        "get address family mismatch"
1249    )]
1250    #[test_case(
1251        false,
1252        NeighbourHeader {
1253            family: AddressFamily::Inet,
1254            ..Default::default()
1255        },
1256        &[
1257            NeighbourAttribute::Destination(std_ip_v4!("192.168.0.1").into()),
1258        ] => Err(RequestError::InvalidRequest);
1259        "get interface unspecified"
1260    )]
1261    #[test_case(
1262        false,
1263        NeighbourHeader {
1264            ifindex: 1,
1265            family: AddressFamily::Inet,
1266            ..Default::default()
1267        },
1268        &[] => Err(RequestError::InvalidRequest);
1269        "get destination unspecified"
1270    )]
1271    #[test_case(
1272        false,
1273        NeighbourHeader {
1274            ifindex: 1,
1275            family: AddressFamily::Inet,
1276            ..Default::default()
1277        },
1278        &[
1279            NeighbourAttribute::Destination(std_ip_v4!("192.168.0.1").into()),
1280            NeighbourAttribute::LinkLocalAddress(vec![0, 1, 2, 3, 4, 5]),
1281        ] => Err(RequestError::InvalidRequest);
1282        "get invalid attribute"
1283    )]
1284    #[test_case(
1285        true,
1286        NeighbourHeader::default(),
1287        &[] => Ok(GetNeighborArgs::Dump {
1288            ip_version: None,
1289            interface: None,
1290        });
1291        "dump all"
1292    )]
1293    #[test_case(
1294        true,
1295        NeighbourHeader {
1296            family: AddressFamily::Inet,
1297            ..Default::default()
1298        },
1299        &[] => Ok(GetNeighborArgs::Dump {
1300            ip_version: Some(IpVersion::V4),
1301            interface: None,
1302        });
1303        "dump ipv4 only"
1304    )]
1305    #[test_case(
1306        true,
1307        NeighbourHeader {
1308            family: AddressFamily::Inet6,
1309            ..Default::default()
1310        },
1311        &[] => Ok(GetNeighborArgs::Dump {
1312            ip_version: Some(IpVersion::V6),
1313            interface: None,
1314        });
1315        "dump ipv6 only"
1316    )]
1317    #[test_case(
1318        true,
1319        NeighbourHeader::default(),
1320        &[
1321            NeighbourAttribute::IfIndex(1),
1322        ] => Ok(GetNeighborArgs::Dump {
1323            ip_version: None,
1324            interface: Some(NonZeroU64::new(1).unwrap()),
1325        });
1326        "dump interface 1"
1327    )]
1328    #[test_case(
1329        true,
1330        NeighbourHeader::default(),
1331        &[
1332            NeighbourAttribute::IfIndex(0),
1333        ] => Ok(GetNeighborArgs::Dump {
1334            ip_version: None,
1335            interface: None,
1336        });
1337        "dump interface 0 treated as all interfaces"
1338    )]
1339    #[test_case(
1340        true,
1341        NeighbourHeader {
1342            family: AddressFamily::Local,
1343            ..Default::default()
1344        },
1345        &[] => Err(RequestError::InvalidRequest);
1346        "dump invalid address family"
1347    )]
1348    #[test_case(
1349        true,
1350        NeighbourHeader::default(),
1351        &[
1352            NeighbourAttribute::LinkLocalAddress(vec![0, 1, 2, 3, 4, 5]),
1353        ] => Ok(GetNeighborArgs::Dump {
1354            ip_version: None,
1355            interface: None,
1356        });
1357        "dump unsupported attribute ignored (non-strict)"
1358    )]
1359    #[test_case(
1360        true,
1361        NeighbourHeader {
1362            flags: NeighbourFlags::Proxy,
1363            ..Default::default()
1364        },
1365        &[] => Err(RequestError::UnsupportedRequest);
1366        "dump unsupported request flag"
1367    )]
1368    #[fuchsia::test]
1369    fn get_neighbor_args_try_from_rtnl_neighbor(
1370        is_dump: bool,
1371        header: NeighbourHeader,
1372        attrs: &[NeighbourAttribute],
1373    ) -> Result<GetNeighborArgs, RequestError> {
1374        let mut message = NeighbourMessage::default();
1375        message.header = header;
1376        message.attributes = attrs.to_vec();
1377        GetNeighborArgs::try_from_rtnl_neighbor(&message, is_dump)
1378    }
1379
1380    #[test_case(RequestError::UnsupportedRequest => Errno::ENOTSUP; "unsupported request")]
1381    #[test_case(RequestError::InvalidRequest => Errno::EINVAL; "invalid request")]
1382    #[test_case(RequestError::NotFound => Errno::ENOENT; "not found")]
1383    #[fuchsia::test]
1384    fn request_error_into_errno(error: RequestError) -> Errno {
1385        error.into()
1386    }
1387
1388    enum OnlyNeighbors {}
1389    impl EventLoopSpec for OnlyNeighbors {
1390        type NeighborWorker = Required;
1391
1392        type InterfacesProxy = Optional;
1393        type InterfacesStateProxy = Optional;
1394        type V4RoutesState = Optional;
1395        type V6RoutesState = Optional;
1396        type V4RoutesSetProvider = Optional;
1397        type V6RoutesSetProvider = Optional;
1398        type V4RouteTableProvider = Optional;
1399        type V6RouteTableProvider = Optional;
1400        type InterfacesHandler = Optional;
1401        type RouteClients = Optional;
1402
1403        type RoutesV4Worker = Optional;
1404        type RoutesV6Worker = Optional;
1405        type InterfacesWorker = Optional;
1406        type RuleV4Worker = Optional;
1407        type RuleV6Worker = Optional;
1408        type NduseroptWorker = Optional;
1409    }
1410
1411    const TEST_SEQUENCE_NUMBER: u32 = 1234;
1412
1413    #[fuchsia::test]
1414    async fn event_loop_with_watch_events_and_get_request() {
1415        let included_workers = IncludedWorkers {
1416            routes_v4: EventLoopComponent::Absent(Optional),
1417            routes_v6: EventLoopComponent::Absent(Optional),
1418            interfaces: EventLoopComponent::Absent(Optional),
1419            rules_v4: EventLoopComponent::Absent(Optional),
1420            rules_v6: EventLoopComponent::Absent(Optional),
1421            neighbors: EventLoopComponent::Present(()),
1422            nduseropt: EventLoopComponent::Absent(Optional),
1423        };
1424        let (mut request_sink, request_stream) = mpsc::channel(1);
1425
1426        // Configure the fake watch events.
1427
1428        let scope = fuchsia_async::Scope::new();
1429        let (neighbors_view, event_sender) = {
1430            use fnet_neighbor_ext::testutil::EventSpec::*;
1431            let events = fnet_neighbor_ext::testutil::generate_events_from_spec(&[
1432                Existing(1),
1433                Existing(2),
1434                Existing(3),
1435                Idle,
1436                Added(4),
1437            ]);
1438            let (event_sender, event_receiver) = mpsc::unbounded();
1439            event_sender.unbounded_send(events).expect("failed to send events");
1440            let (neighbors_view, neighbors_fut) =
1441                fnet_neighbor_ext::testutil::create_fake_view(event_receiver);
1442            let _join_handle = scope.spawn(neighbors_fut);
1443            (neighbors_view, event_sender)
1444        };
1445
1446        // Set up the event loop.
1447
1448        let (_async_work_sink, async_work_receiver) = mpsc::unbounded();
1449        let base_inputs: EventLoopInputs<
1450            FakeInterfacesHandler,
1451            FakeSender<RouteNetlinkMessage>,
1452            OnlyNeighbors,
1453        > = EventLoopInputs {
1454            neighbors_view: EventLoopComponent::Present(neighbors_view),
1455
1456            async_work_receiver,
1457
1458            interfaces_handler: EventLoopComponent::Absent(Optional),
1459            route_clients: EventLoopComponent::Absent(Optional),
1460            interfaces_proxy: EventLoopComponent::Absent(Optional),
1461            interfaces_state_proxy: EventLoopComponent::Absent(Optional),
1462            v4_routes_state: EventLoopComponent::Absent(Optional),
1463            v6_routes_state: EventLoopComponent::Absent(Optional),
1464            v4_main_route_table: EventLoopComponent::Absent(Optional),
1465            v6_main_route_table: EventLoopComponent::Absent(Optional),
1466            v4_route_table_provider: EventLoopComponent::Absent(Optional),
1467            v6_route_table_provider: EventLoopComponent::Absent(Optional),
1468            v4_rule_table: EventLoopComponent::Absent(Optional),
1469            v6_rule_table: EventLoopComponent::Absent(Optional),
1470            ndp_option_watcher_provider: EventLoopComponent::Absent(Optional),
1471
1472            unified_request_stream: request_stream,
1473        };
1474
1475        let mut state = base_inputs.initialize(included_workers).await;
1476        // Wait for `Added` event to be processed.
1477        state.run_one_step_in_tests().await;
1478
1479        // Send a dump request and check the response.
1480
1481        let (mut response_sink, neighbor_client, async_work_drain_task) =
1482            crate::client::testutil::new_fake_client::<NetlinkRoute>(
1483                crate::client::testutil::CLIENT_ID_1,
1484                [],
1485            );
1486        let _join_handle = scope.spawn(async_work_drain_task);
1487
1488        let (completer, waiter) = oneshot::channel();
1489        let get_request: UnifiedRequest<FakeSender<RouteNetlinkMessage>> =
1490            UnifiedRequest::NeighborsRequest(Request {
1491                args: NeighborRequestArgs::Get(GetNeighborArgs::Dump {
1492                    ip_version: None,
1493                    interface: None,
1494                }),
1495                sequence_number: TEST_SEQUENCE_NUMBER,
1496                client: neighbor_client.clone(),
1497                completer,
1498            });
1499        request_sink.send(get_request).await.unwrap();
1500
1501        // Wait for client request to be processed.
1502        state.run_one_step_in_tests().await;
1503        assert_matches!(waiter.await.unwrap(), Ok(()));
1504
1505        let responses = response_sink.take_messages();
1506        assert_eq!(responses.len(), 4); // 3 existing + 1 added.
1507        for response in responses {
1508            assert_matches!(
1509                response.message.payload,
1510                NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewNeighbour(_))
1511            );
1512        }
1513
1514        event_sender.close_channel();
1515        drop(neighbor_client);
1516        scope.join().await;
1517    }
1518}