use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
use crate::netlink::error::{Error, Result};
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
pub struct NhMsg {
pub nh_family: u8,
pub nh_scope: u8,
pub nh_protocol: u8,
pub resvd: u8,
pub nh_flags: u32,
}
impl NhMsg {
pub const SIZE: usize = std::mem::size_of::<Self>();
pub fn new() -> Self {
Self::default()
}
pub fn with_family(mut self, family: u8) -> Self {
self.nh_family = family;
self
}
pub fn with_scope(mut self, scope: u8) -> Self {
self.nh_scope = scope;
self
}
pub fn with_protocol(mut self, protocol: u8) -> Self {
self.nh_protocol = protocol;
self
}
pub fn with_flags(mut self, flags: u32) -> Self {
self.nh_flags = flags;
self
}
pub fn as_bytes(&self) -> &[u8] {
<Self as IntoBytes>::as_bytes(self)
}
pub fn from_bytes(data: &[u8]) -> Result<&Self> {
Self::ref_from_prefix(data)
.map(|(r, _)| r)
.map_err(|_| Error::Truncated {
expected: Self::SIZE,
actual: data.len(),
})
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
pub struct NexthopGrp {
pub id: u32,
pub weight: u8,
pub resvd1: u8,
pub resvd2: u16,
}
impl NexthopGrp {
pub const SIZE: usize = std::mem::size_of::<Self>();
pub fn new(id: u32, weight: u8) -> Self {
Self {
id,
weight,
resvd1: 0,
resvd2: 0,
}
}
pub fn as_bytes(&self) -> &[u8] {
<Self as IntoBytes>::as_bytes(self)
}
pub fn from_bytes(data: &[u8]) -> Option<&Self> {
Self::ref_from_prefix(data).map(|(r, _)| r).ok()
}
}
pub mod nha {
pub const UNSPEC: u16 = 0;
pub const ID: u16 = 1;
pub const GROUP: u16 = 2;
pub const GROUP_TYPE: u16 = 3;
pub const BLACKHOLE: u16 = 4;
pub const OIF: u16 = 5;
pub const GATEWAY: u16 = 6;
pub const ENCAP_TYPE: u16 = 7;
pub const ENCAP: u16 = 8;
pub const GROUPS: u16 = 9;
pub const MASTER: u16 = 10;
pub const FDB: u16 = 11;
pub const RES_GROUP: u16 = 12;
pub const RES_BUCKET: u16 = 13;
}
pub mod nhg_type {
pub const MPATH: u16 = 0;
pub const RES: u16 = 1;
}
pub mod nhf {
pub const ONLINK: u32 = 1;
pub const DEAD: u32 = 2;
pub const LINKDOWN: u32 = 4;
}
pub mod nha_res_group {
pub const UNSPEC: u16 = 0;
pub const BUCKETS: u16 = 1;
pub const IDLE_TIMER: u16 = 2;
pub const UNBALANCED_TIMER: u16 = 3;
pub const UNBALANCED_TIME: u16 = 4;
}
pub mod nha_res_bucket {
pub const UNSPEC: u16 = 0;
pub const PAD: u16 = 1;
pub const INDEX: u16 = 2;
pub const IDLE_TIME: u16 = 3;
pub const NH_ID: u16 = 4;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_nhmsg_size() {
assert_eq!(NhMsg::SIZE, 8);
}
#[test]
fn test_nexthop_grp_size() {
assert_eq!(NexthopGrp::SIZE, 8);
}
#[test]
fn test_nhmsg_builder() {
let msg = NhMsg::new()
.with_family(libc::AF_INET as u8)
.with_protocol(4) .with_flags(nhf::ONLINK);
assert_eq!(msg.nh_family, libc::AF_INET as u8);
assert_eq!(msg.nh_protocol, 4);
assert_eq!(msg.nh_flags, nhf::ONLINK);
}
#[test]
fn test_nexthop_grp() {
let grp = NexthopGrp::new(42, 10);
assert_eq!(grp.id, 42);
assert_eq!(grp.weight, 10);
assert_eq!(grp.resvd1, 0);
assert_eq!(grp.resvd2, 0);
}
#[test]
fn nhmsg_from_bytes_accepts_trailing_bytes() {
let msg = NhMsg::new()
.with_family(libc::AF_INET as u8)
.with_flags(nhf::ONLINK);
let mut buf = msg.as_bytes().to_vec();
buf.extend_from_slice(&[0xFF; 16]);
let parsed = NhMsg::from_bytes(&buf).expect("prefix parse");
assert_eq!(parsed.nh_family, libc::AF_INET as u8);
assert_eq!(parsed.nh_flags, nhf::ONLINK);
}
#[test]
fn nhmsg_from_bytes_rejects_undersized() {
let msg = NhMsg::new();
assert!(NhMsg::from_bytes(&msg.as_bytes()[..NhMsg::SIZE - 1]).is_err());
for len in 0..NhMsg::SIZE {
let _ = NhMsg::from_bytes(&vec![0u8; len]);
}
}
#[test]
fn nexthop_grp_from_bytes_accepts_larger_rejects_undersized() {
let grp = NexthopGrp::new(42, 10);
let mut buf = grp.as_bytes().to_vec();
buf.extend_from_slice(&[0x7E; 4]);
let parsed = NexthopGrp::from_bytes(&buf).expect("prefix parse");
assert_eq!(parsed.id, 42);
assert_eq!(parsed.weight, 10);
assert!(NexthopGrp::from_bytes(&grp.as_bytes()[..NexthopGrp::SIZE - 1]).is_none());
assert!(NexthopGrp::from_bytes(&[]).is_none());
}
}