From 899bffc6327c5c9d3a28c028b36f84c91dcc01d9 Mon Sep 17 00:00:00 2001 From: chrysn Date: Fri, 30 Aug 2024 01:11:10 +0200 Subject: [PATCH 1/6] nib: New module to add neighbor cache access --- build.rs | 1 + src/gnrc/mod.rs | 2 + src/gnrc/nib.rs | 183 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 src/gnrc/nib.rs diff --git a/build.rs b/build.rs index f50c6a4..90e1ca2 100644 --- a/build.rs +++ b/build.rs @@ -74,6 +74,7 @@ fn main() { "gcoap", "gnrc", "gnrc_icmpv6", + "gnrc_ipv6_nib", "gnrc_pktbuf", "gnrc_udp", "ipv6", diff --git a/src/gnrc/mod.rs b/src/gnrc/mod.rs index 0255226..41c5296 100644 --- a/src/gnrc/mod.rs +++ b/src/gnrc/mod.rs @@ -5,6 +5,8 @@ pub mod ipv6; pub mod netapi; pub mod netreg; +#[cfg(riot_module_gnrc_ipv6_nib)] +pub mod nib; #[deprecated(note = "Internally, use gnrc_pktbuf directly")] pub(crate) use crate::gnrc_pktbuf as pktbuf; diff --git a/src/gnrc/nib.rs b/src/gnrc/nib.rs new file mode 100644 index 0000000..548e45c --- /dev/null +++ b/src/gnrc/nib.rs @@ -0,0 +1,183 @@ +/// A single entry in the neighbor cache. +/// +/// These can be obtained by iterating +pub struct NcEntry(riot_sys::gnrc_ipv6_nib_nc_t); + +/// Neighbor Unreachability Detection state +/// +/// See +/// https://doc.riot-os.org/group__net__gnrc__ipv6__nib__nc.html +/// for more detailed semantics +// FIXME can we pull doc from riot_sys? +#[derive(Debug)] +pub enum NudState { + Unmanaged, + Unreachable, + Incomplete, + Stale, + Delay, + Probe, + Reachable, +} + +impl NudState { + fn from_c(input: riot_sys::libc::c_uint) -> Option { + Some(match input { + riot_sys::GNRC_IPV6_NIB_NC_INFO_NUD_STATE_UNMANAGED => NudState::Unmanaged, + riot_sys::GNRC_IPV6_NIB_NC_INFO_NUD_STATE_UNREACHABLE => NudState::Unreachable, + riot_sys::GNRC_IPV6_NIB_NC_INFO_NUD_STATE_INCOMPLETE => NudState::Incomplete, + riot_sys::GNRC_IPV6_NIB_NC_INFO_NUD_STATE_STALE => NudState::Stale, + riot_sys::GNRC_IPV6_NIB_NC_INFO_NUD_STATE_DELAY => NudState::Delay, + riot_sys::GNRC_IPV6_NIB_NC_INFO_NUD_STATE_PROBE => NudState::Probe, + riot_sys::GNRC_IPV6_NIB_NC_INFO_NUD_STATE_REACHABLE => NudState::Reachable, + _ => return None, + }) + } + + /// Returns a plain text label of the state. + /// + /// This is equivalent to debug (except for capitalization), but more versatile in its use due + /// to its type. + pub fn label(&self) -> &'static str { + match self { + NudState::Unmanaged => "managed", + NudState::Unreachable => "unreachable", + NudState::Incomplete => "incomplete", + NudState::Stale => "stale", + NudState::Delay => "delay", + NudState::Probe => "probe", + NudState::Reachable => "reachable", + } + } +} + +/// 6LoWPAN address registration (6Lo-AR) state +/// +/// See +/// https://doc.riot-os.org/group__net__gnrc__ipv6__nib__nc.html +/// for more detailed semantics +// FIXME can we pull doc from riot_sys? +#[derive(Debug)] +pub enum ArState { + Gc, + Tentative, + Registered, + Manual, +} + +impl ArState { + fn from_c(input: riot_sys::libc::c_uint) -> Option { + Some(match input { + riot_sys::GNRC_IPV6_NIB_NC_INFO_AR_STATE_GC => ArState::Gc, + riot_sys::GNRC_IPV6_NIB_NC_INFO_AR_STATE_TENTATIVE => ArState::Tentative, + riot_sys::GNRC_IPV6_NIB_NC_INFO_AR_STATE_REGISTERED => ArState::Registered, + riot_sys::GNRC_IPV6_NIB_NC_INFO_AR_STATE_MANUAL => ArState::Manual, + _ => return None, + }) + } + + /// Returns a plain text label of the state. + /// + /// This is equivalent to debug (except for capitalization), but more versatile in its use due + /// to its type. + pub fn label(&self) -> &'static str { + match self { + ArState::Gc => "GC", + ArState::Tentative => "tentative", + ArState::Registered => "registered", + ArState::Manual => "manual", + } + } +} + +impl NcEntry { + pub fn l2addr(&self) -> &[u8] { + &self.0.l2addr[..self.0.l2addr_len as usize] + } + + pub fn ipv6_addr(&self) -> &crate::gnrc::ipv6::Address { + // unsafe: It's repr(transparent) around it + unsafe { core::mem::transmute(&self.0.ipv6) } + } + + #[doc(alias = "gnrc_ipv6_nib_nc_get_iface")] + pub fn iface(&self) -> Option> { + const { + assert!(riot_sys::KERNEL_PID_UNDEF == 0, "Interface lookup mixes unspecified interface with PIDs and thus relies on the unspecified latter being 0.") + }; + let interface = unsafe { + riot_sys::inline::gnrc_ipv6_nib_nc_get_iface(crate::inline_cast_ref(&self.0)) + }; + // Let's not get into size discussions + let interface = interface as usize; + interface.try_into().ok() + } + + #[doc(alias = "gnrc_ipv6_nib_nc_is_router")] + pub fn is_router(&self) -> bool { + unsafe { riot_sys::inline::gnrc_ipv6_nib_nc_is_router(crate::inline_cast_ref(&self.0)) } + } + + /// Access the entry's Neighbor Unreachability Detection (NUD) state + /// + /// This is None if the interface's NUD state is invalid (including values introduced to RIOT + /// OS but not known to riot-wrappers). + pub fn nud_state(&self) -> Option { + let result = NudState::from_c(unsafe { + riot_sys::inline::gnrc_ipv6_nib_nc_get_nud_state(crate::inline_cast_ref(&self.0)) + }); + result + } + + /// Access the entry's 6LoWPAN address registration (6Lo-AR) state + /// + /// This is None if the interface's neighbor state is invalid (including values + /// introduced to RIOT OS but not known to riot-wrappers). + pub fn ar_state(&self) -> Option { + let result = ArState::from_c(unsafe { + riot_sys::inline::gnrc_ipv6_nib_nc_get_ar_state(crate::inline_cast_ref(&self.0)) + }); + result + } +} + +/// Iterate over the Neighbor Cache. +#[doc(alias = "gnrc_ipv6_nib_nc_iter")] +pub fn all_nc_entries() -> impl Iterator { + // If we add anything like all_nc_entries_on_interface(): + // // Interfaces are positive numbers; MAX is clearly out of range and allows us to have an easier + // // input type + // let interface = interface.map(|i| { + // riot_sys::libc::c_uint::try_from(usize::from(i)).unwrap_or(riot_sys::libc::c_uint::MAX) + // }); + + any_nc_query(0) +} + +struct NcIterator { + interface: riot_sys::libc::c_uint, + state: *mut riot_sys::libc::c_void, +} + +impl Iterator for NcIterator { + type Item = NcEntry; + + fn next(&mut self) -> Option { + let mut nc_entry = core::mem::MaybeUninit::::uninit(); + if unsafe { + riot_sys::gnrc_ipv6_nib_nc_iter(self.interface, &mut self.state, nc_entry.as_mut_ptr()) + } { + let nc_entry = NcEntry(unsafe { nc_entry.assume_init() }); + Some(nc_entry) + } else { + None + } + } +} + +fn any_nc_query(interface: riot_sys::libc::c_uint) -> impl Iterator { + NcIterator { + interface, + state: core::ptr::null_mut(), + } +} From 986e11e8c3a3b523735731000e43ee21af4add29 Mon Sep 17 00:00:00 2001 From: chrysn Date: Fri, 30 Aug 2024 14:04:32 +0200 Subject: [PATCH 2/6] nib: Use iterator construction like in netif --- src/gnrc/nib.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/gnrc/nib.rs b/src/gnrc/nib.rs index 548e45c..ac56f26 100644 --- a/src/gnrc/nib.rs +++ b/src/gnrc/nib.rs @@ -91,6 +91,19 @@ impl ArState { } impl NcEntry { + /// Iterate over the Neighbor Cache. + #[doc(alias = "gnrc_ipv6_nib_nc_iter")] + pub fn all() -> impl Iterator { + // If we add anything like all_nc_entries_on_interface(): + // // Interfaces are positive numbers; MAX is clearly out of range and allows us to have an easier + // // input type + // let interface = interface.map(|i| { + // riot_sys::libc::c_uint::try_from(usize::from(i)).unwrap_or(riot_sys::libc::c_uint::MAX) + // }); + + any_nc_query(0) + } + pub fn l2addr(&self) -> &[u8] { &self.0.l2addr[..self.0.l2addr_len as usize] } @@ -141,19 +154,6 @@ impl NcEntry { } } -/// Iterate over the Neighbor Cache. -#[doc(alias = "gnrc_ipv6_nib_nc_iter")] -pub fn all_nc_entries() -> impl Iterator { - // If we add anything like all_nc_entries_on_interface(): - // // Interfaces are positive numbers; MAX is clearly out of range and allows us to have an easier - // // input type - // let interface = interface.map(|i| { - // riot_sys::libc::c_uint::try_from(usize::from(i)).unwrap_or(riot_sys::libc::c_uint::MAX) - // }); - - any_nc_query(0) -} - struct NcIterator { interface: riot_sys::libc::c_uint, state: *mut riot_sys::libc::c_void, From 7eff52aceaf1c5ea51e685522b654be1f2415d0a Mon Sep 17 00:00:00 2001 From: chrysn Date: Fri, 30 Aug 2024 14:26:21 +0200 Subject: [PATCH 3/6] tests: Add some netif/nib processing --- tests/network-properties/Cargo.toml | 15 +++++++++ tests/network-properties/Makefile | 21 ++++++++++++ tests/network-properties/src/lib.rs | 41 ++++++++++++++++++++++++ tests/network-properties/tests/01-run.py | 15 +++++++++ 4 files changed, 92 insertions(+) create mode 100644 tests/network-properties/Cargo.toml create mode 100644 tests/network-properties/Makefile create mode 100644 tests/network-properties/src/lib.rs create mode 100755 tests/network-properties/tests/01-run.py diff --git a/tests/network-properties/Cargo.toml b/tests/network-properties/Cargo.toml new file mode 100644 index 0000000..02831c7 --- /dev/null +++ b/tests/network-properties/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "riot-wrappers-test-networkproperties" +version = "0.1.0" +authors = ["Christian Amsüss "] +edition = "2021" +publish = false + +[lib] +crate-type = ["staticlib"] + +[profile.release] +panic = "abort" + +[dependencies] +riot-wrappers = { path = "../..", features = [ "set_panic_handler", "panic_handler_format" ] } diff --git a/tests/network-properties/Makefile b/tests/network-properties/Makefile new file mode 100644 index 0000000..f8ef21f --- /dev/null +++ b/tests/network-properties/Makefile @@ -0,0 +1,21 @@ +# name of your application +APPLICATION = riot-wrappers-test-networkproperties +BOARD ?= native +APPLICATION_RUST_MODULE = riot_wrappers_test_networkproperties +BASELIBS += $(APPLICATION_RUST_MODULE).module +FEATURES_REQUIRED += rust_target + +# This may not be a GNRC netdev in all cases; when it is not, the test will +# break, and that will be the time to split it. So far, also IPv6 support can +# just be assumed. (Really, anyone writing an IoT application without IPv6 +# support may want to look into Cobol). +USEMODULE += netdev_default +USEMODULE += auto_init_gnrc_netif +USEMODULE += gnrc_ipv6_default +# This is an easy way to visibly populate entries into the neighbor cache: ping +# the RIOT instance. +USEMODULE += gnrc_icmpv6_echo + +USEMODULE += ztimer_msec + +include $(RIOTBASE)/Makefile.include diff --git a/tests/network-properties/src/lib.rs b/tests/network-properties/src/lib.rs new file mode 100644 index 0000000..0de78f7 --- /dev/null +++ b/tests/network-properties/src/lib.rs @@ -0,0 +1,41 @@ +#![no_std] + +use riot_wrappers::println; +use riot_wrappers::riot_main; + +riot_main!(main); + +fn main() { + use riot_wrappers::ztimer::*; + + let msec = Clock::msec(); + + loop { + for netif in riot_wrappers::gnrc::Netif::all() { + println!( + "Netif at PID {:?} with link-layer addr {:?}", + netif.pid(), + netif.l2addr() + ); + for addr in &netif.ipv6_addrs().unwrap() { + println!("- Address {:?}", addr); + } + } + + println!("Cache entries:"); + for cache_entry in riot_wrappers::gnrc::nib::NcEntry::all() { + println!( + "- on interface {:?}: {:02x?} <=> {:?}, router? {:?}, NUD {:?}, AR {:?}", + cache_entry.iface(), + cache_entry.l2addr(), + cache_entry.ipv6_addr(), + cache_entry.is_router(), + cache_entry.nud_state(), + cache_entry.ar_state() + ); + } + println!(""); + + msec.sleep(Ticks(300)); + } +} diff --git a/tests/network-properties/tests/01-run.py b/tests/network-properties/tests/01-run.py new file mode 100755 index 0000000..028aab6 --- /dev/null +++ b/tests/network-properties/tests/01-run.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 + +import os +import sys +from testrunner import run + +def test(child): + # Cant' make any predictions about network addresses, but showing them + # should not crash. + for _ in range(3): + child.expect("Netif at ") + child.expect("Cache entries") + +if __name__ == "__main__": + sys.exit(run(test)) From 35171c36f1624f30502e04f5d21a372061aefb24 Mon Sep 17 00:00:00 2001 From: chrysn Date: Fri, 30 Aug 2024 14:35:36 +0200 Subject: [PATCH 4/6] gnrc_util: Fix clippy warning in configs without UDP --- src/gnrc_util.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/gnrc_util.rs b/src/gnrc_util.rs index f468730..f0f5182 100644 --- a/src/gnrc_util.rs +++ b/src/gnrc_util.rs @@ -11,9 +11,7 @@ use crate::thread::KernelPID; #[cfg(riot_module_gnrc_udp)] use riot_sys::gnrc_nettype_t_GNRC_NETTYPE_UDP as GNRC_NETTYPE_UDP; -use riot_sys::{ - gnrc_netif_hdr_t, gnrc_nettype_t_GNRC_NETTYPE_NETIF as GNRC_NETTYPE_NETIF, udp_hdr_t, -}; +use riot_sys::{gnrc_netif_hdr_t, gnrc_nettype_t_GNRC_NETTYPE_NETIF as GNRC_NETTYPE_NETIF}; /// Trait of data structures that store all the information needed to respond to a Pktsnip in some /// way; the data (typically address and port information) is copied into the trait implementation @@ -110,7 +108,7 @@ impl RoundtripData for UDPRoundtripDataFull { let (src, dst) = incoming .search_type(GNRC_NETTYPE_UDP) .map(|s| { - let hdr: &udp_hdr_t = unsafe { &*(s.data.as_ptr() as *const _) }; + let hdr: &riot_sys::udp_hdr_t = unsafe { &*(s.data.as_ptr() as *const _) }; ( u16::from_be_bytes(unsafe { (*hdr).src_port.u8_ }), u16::from_be_bytes(unsafe { (*hdr).dst_port.u8_ }), From b1c6cad34f127e4b68fc659ed00b76ba1293b696 Mon Sep 17 00:00:00 2001 From: chrysn Date: Fri, 30 Aug 2024 15:09:12 +0200 Subject: [PATCH 5/6] CI: Make tap0 available The network-properties test needs this. --- .github/workflows/test.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1001b6c..8b79550 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -122,6 +122,14 @@ jobs: echo echo "Testing ${D}" echo + + if make BOARD=native info-modules |grep -q netdev_tap; then + apt-get update + apt-get install -y iproute2 + # Not setting up any particular addresses; tests don't expect any + # route so far. + ip tuntap add dev tap0 mode tap + fi make all test BOARD=native fi else From 235880068ec49bdd95533c143bafeaeafb3c2f0f Mon Sep 17 00:00:00 2001 From: chrysn Date: Fri, 30 Aug 2024 15:30:03 +0200 Subject: [PATCH 6/6] CI: Skip tests that need tap0 --- .github/workflows/test.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8b79550..09935e6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -124,11 +124,9 @@ jobs: echo if make BOARD=native info-modules |grep -q netdev_tap; then - apt-get update - apt-get install -y iproute2 - # Not setting up any particular addresses; tests don't expect any - # route so far. - ip tuntap add dev tap0 mode tap + # Seems we can't have tap interfaces on GitHub actions, aborting. + echo "Board requires tap interface, skipping." + exit 0 fi make all test BOARD=native fi