Skip to content

Commit

Permalink
nib: New module to add neighbor cache access, including tests
Browse files Browse the repository at this point in the history
Merges: #117
  • Loading branch information
chrysn authored Aug 30, 2024
2 parents 3d280a4 + 2358800 commit 410b6a0
Show file tree
Hide file tree
Showing 9 changed files with 286 additions and 4 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ jobs:
echo
echo "Testing ${D}"
echo
if make BOARD=native info-modules |grep -q netdev_tap; then
# 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
else
Expand Down
1 change: 1 addition & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ fn main() {
"gcoap",
"gnrc",
"gnrc_icmpv6",
"gnrc_ipv6_nib",
"gnrc_pktbuf",
"gnrc_udp",
"ipv6",
Expand Down
2 changes: 2 additions & 0 deletions src/gnrc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
183 changes: 183 additions & 0 deletions src/gnrc/nib.rs
Original file line number Diff line number Diff line change
@@ -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<Self> {
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<Self> {
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 {
/// Iterate over the Neighbor Cache.
#[doc(alias = "gnrc_ipv6_nib_nc_iter")]
pub fn all() -> impl Iterator<Item = Self> {
// 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]
}

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<core::num::NonZero<usize>> {
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<NudState> {
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<ArState> {
let result = ArState::from_c(unsafe {
riot_sys::inline::gnrc_ipv6_nib_nc_get_ar_state(crate::inline_cast_ref(&self.0))
});
result
}
}

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<Self::Item> {
let mut nc_entry = core::mem::MaybeUninit::<riot_sys::gnrc_ipv6_nib_nc_t>::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<Item = NcEntry> {
NcIterator {
interface,
state: core::ptr::null_mut(),
}
}
6 changes: 2 additions & 4 deletions src/gnrc_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -110,7 +108,7 @@ impl<N: RoundtripData> RoundtripData for UDPRoundtripDataFull<N> {
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_ }),
Expand Down
15 changes: 15 additions & 0 deletions tests/network-properties/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "riot-wrappers-test-networkproperties"
version = "0.1.0"
authors = ["Christian Amsüss <[email protected]>"]
edition = "2021"
publish = false

[lib]
crate-type = ["staticlib"]

[profile.release]
panic = "abort"

[dependencies]
riot-wrappers = { path = "../..", features = [ "set_panic_handler", "panic_handler_format" ] }
21 changes: 21 additions & 0 deletions tests/network-properties/Makefile
Original file line number Diff line number Diff line change
@@ -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
41 changes: 41 additions & 0 deletions tests/network-properties/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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));
}
}
15 changes: 15 additions & 0 deletions tests/network-properties/tests/01-run.py
Original file line number Diff line number Diff line change
@@ -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))

0 comments on commit 410b6a0

Please sign in to comment.