use std::net::IpAddr;
use super::{
builder::MessageBuilder,
connection::Connection,
error::{Error, Result},
interface_ref::InterfaceRef,
message::{NLM_F_ACK, NLM_F_DUMP, NLM_F_REQUEST, NlMsgType},
messages::NeighborMessage,
protocol::Route,
types::neigh::{NdMsg, NdaAttr, NeighborState},
};
const NLM_F_CREATE: u16 = 0x400;
const NLM_F_EXCL: u16 = 0x200;
const NLM_F_REPLACE: u16 = 0x100;
const AF_BRIDGE: u8 = 7;
mod ntf {
pub const SELF: u8 = 0x02;
pub const MASTER: u8 = 0x04;
pub const EXT_LEARNED: u8 = 0x10;
}
mod nud {
pub const PERMANENT: u16 = 0x80;
pub const REACHABLE: u16 = 0x02;
}
#[derive(Debug, Clone)]
pub struct FdbEntry {
pub ifindex: u32,
pub mac: [u8; 6],
pub vlan: Option<u16>,
pub dst: Option<IpAddr>,
pub vni: Option<u32>,
pub state: NeighborState,
pub flags: u8,
pub master: Option<u32>,
}
impl FdbEntry {
pub fn from_neighbor(msg: &NeighborMessage) -> Option<Self> {
let lladdr = msg.lladdr()?;
if lladdr.len() != 6 {
return None;
}
let mut mac = [0u8; 6];
mac.copy_from_slice(lladdr);
Some(Self {
ifindex: msg.ifindex(),
mac,
vlan: msg.vlan(),
dst: msg.destination().cloned(),
vni: msg.vni(),
state: msg.state(),
flags: msg.flags(),
master: msg.master(),
})
}
pub fn is_permanent(&self) -> bool {
self.state == NeighborState::Permanent
}
pub fn is_dynamic(&self) -> bool {
!self.is_permanent()
}
pub fn is_self(&self) -> bool {
self.flags & ntf::SELF != 0
}
pub fn is_master(&self) -> bool {
self.flags & ntf::MASTER != 0
}
pub fn is_extern_learn(&self) -> bool {
self.flags & ntf::EXT_LEARNED != 0
}
pub fn mac_str(&self) -> String {
format!(
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
self.mac[0], self.mac[1], self.mac[2], self.mac[3], self.mac[4], self.mac[5]
)
}
}
#[derive(Debug, Clone, Default)]
#[must_use = "builders do nothing unless used"]
pub struct FdbEntryBuilder {
mac: [u8; 6],
dev: Option<InterfaceRef>,
vlan: Option<u16>,
dst: Option<IpAddr>,
vni: Option<u32>,
master: Option<InterfaceRef>,
permanent: bool,
self_flag: bool,
}
impl FdbEntryBuilder {
pub fn new(mac: [u8; 6]) -> Self {
Self {
mac,
permanent: true,
..Default::default()
}
}
pub fn parse_mac(mac_str: &str) -> Result<[u8; 6]> {
crate::util::addr::parse_mac(mac_str)
.map_err(|e| Error::InvalidMessage(format!("invalid MAC: {}", e)))
}
pub fn dev(mut self, dev: impl Into<String>) -> Self {
self.dev = Some(InterfaceRef::Name(dev.into()));
self
}
pub fn ifindex(mut self, ifindex: u32) -> Self {
self.dev = Some(InterfaceRef::Index(ifindex));
self
}
pub fn device_ref(&self) -> Option<&InterfaceRef> {
self.dev.as_ref()
}
pub fn vlan(mut self, vlan: u16) -> Self {
self.vlan = Some(vlan);
self
}
pub fn dst(mut self, dst: IpAddr) -> Self {
self.dst = Some(dst);
self
}
pub fn vni(mut self, vni: u32) -> Self {
self.vni = Some(vni);
self
}
pub fn master(mut self, master: impl Into<String>) -> Self {
self.master = Some(InterfaceRef::Name(master.into()));
self
}
pub fn master_ifindex(mut self, ifindex: u32) -> Self {
self.master = Some(InterfaceRef::Index(ifindex));
self
}
pub fn master_ref(&self) -> Option<&InterfaceRef> {
self.master.as_ref()
}
pub fn permanent(mut self) -> Self {
self.permanent = true;
self
}
pub fn dynamic(mut self) -> Self {
self.permanent = false;
self
}
pub fn self_(mut self) -> Self {
self.self_flag = true;
self
}
pub(crate) fn write_add(
&self,
builder: &mut MessageBuilder,
ifindex: u32,
master_idx: Option<u32>,
) {
let state = if self.permanent {
nud::PERMANENT
} else {
nud::REACHABLE
};
let mut ntf_flags: u8 = 0;
if self.self_flag {
ntf_flags |= ntf::SELF;
}
let ndmsg = NdMsg::new()
.with_family(AF_BRIDGE)
.with_ifindex(ifindex as i32)
.with_state(state)
.with_flags(ntf_flags);
builder.append(&ndmsg);
builder.append_attr(NdaAttr::Lladdr as u16, &self.mac);
if let Some(master) = master_idx {
builder.append_attr_u32(NdaAttr::Master as u16, master);
}
if let Some(vlan) = self.vlan {
builder.append_attr_u16(NdaAttr::Vlan as u16, vlan);
}
if let Some(ref dst) = self.dst {
match dst {
IpAddr::V4(v4) => {
builder.append_attr(NdaAttr::Dst as u16, &v4.octets());
}
IpAddr::V6(v6) => {
builder.append_attr(NdaAttr::Dst as u16, &v6.octets());
}
}
}
if let Some(vni) = self.vni {
builder.append_attr_u32(NdaAttr::Vni as u16, vni);
}
}
pub(crate) fn write_delete(&self, builder: &mut MessageBuilder, ifindex: u32) {
let ndmsg = NdMsg::new()
.with_family(AF_BRIDGE)
.with_ifindex(ifindex as i32);
builder.append(&ndmsg);
builder.append_attr(NdaAttr::Lladdr as u16, &self.mac);
if let Some(vlan) = self.vlan {
builder.append_attr_u16(NdaAttr::Vlan as u16, vlan);
}
}
}
impl Connection<Route> {
#[tracing::instrument(level = "debug", skip_all, fields(method = "get_fdb"))]
pub async fn get_fdb(&self, bridge: impl Into<InterfaceRef>) -> Result<Vec<FdbEntry>> {
let bridge_idx = self.resolve_interface(&bridge.into()).await?;
self.get_fdb_by_index(bridge_idx).await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "get_fdb_by_index"))]
pub async fn get_fdb_by_index(&self, bridge_idx: u32) -> Result<Vec<FdbEntry>> {
let neighbors = self.get_bridge_neighbors().await?;
Ok(neighbors
.iter()
.filter(|n| n.master() == Some(bridge_idx) || n.ifindex() == bridge_idx)
.filter_map(FdbEntry::from_neighbor)
.collect())
}
async fn get_bridge_neighbors(&self) -> Result<Vec<NeighborMessage>> {
use super::{message::NLMSG_HDRLEN, parse::FromNetlink};
let ndmsg = NdMsg::new().with_family(AF_BRIDGE);
let mut builder = MessageBuilder::new(NlMsgType::RTM_GETNEIGH, NLM_F_REQUEST | NLM_F_DUMP);
builder.append(&ndmsg);
let responses = self.send_dump(builder).await?;
let mut parsed = Vec::new();
for response in responses {
if response.len() < NLMSG_HDRLEN {
continue;
}
let payload = &response[NLMSG_HDRLEN..];
if let Ok(msg) = NeighborMessage::from_bytes(payload) {
parsed.push(msg);
}
}
Ok(parsed)
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "get_fdb_for_port"))]
pub async fn get_fdb_for_port(
&self,
bridge: impl Into<InterfaceRef>,
port: impl Into<InterfaceRef>,
) -> Result<Vec<FdbEntry>> {
let bridge_idx = self.resolve_interface(&bridge.into()).await?;
let port_idx = self.resolve_interface(&port.into()).await?;
let neighbors = self.get_bridge_neighbors().await?;
Ok(neighbors
.iter()
.filter(|n| n.ifindex() == port_idx)
.filter(|n| n.master() == Some(bridge_idx))
.filter_map(FdbEntry::from_neighbor)
.collect())
}
async fn resolve_fdb_interfaces(&self, entry: &FdbEntryBuilder) -> Result<(u32, Option<u32>)> {
let ifindex = match entry.device_ref() {
Some(iface) => self.resolve_interface(iface).await?,
None => {
return Err(Error::InvalidMessage(
"device name or ifindex required".into(),
));
}
};
let master_idx = match entry.master_ref() {
Some(iface) => Some(self.resolve_interface(iface).await?),
None => None,
};
Ok((ifindex, master_idx))
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "add_fdb"))]
pub async fn add_fdb(&self, entry: FdbEntryBuilder) -> Result<()> {
let (ifindex, master_idx) = self.resolve_fdb_interfaces(&entry).await?;
let mut builder = MessageBuilder::new(
NlMsgType::RTM_NEWNEIGH,
NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL,
);
entry.write_add(&mut builder, ifindex, master_idx);
self.send_ack(builder)
.await
.map_err(|e| e.with_context("add_fdb"))
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "replace_fdb"))]
pub async fn replace_fdb(&self, entry: FdbEntryBuilder) -> Result<()> {
let (ifindex, master_idx) = self.resolve_fdb_interfaces(&entry).await?;
let mut builder = MessageBuilder::new(
NlMsgType::RTM_NEWNEIGH,
NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_REPLACE,
);
entry.write_add(&mut builder, ifindex, master_idx);
self.send_ack(builder)
.await
.map_err(|e| e.with_context("replace_fdb"))
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "del_fdb"))]
pub async fn del_fdb(
&self,
dev: impl Into<InterfaceRef>,
mac: [u8; 6],
vlan: Option<u16>,
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.del_fdb_by_index(ifindex, mac, vlan).await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "del_fdb_by_index"))]
pub async fn del_fdb_by_index(
&self,
ifindex: u32,
mac: [u8; 6],
vlan: Option<u16>,
) -> Result<()> {
let mut entry = FdbEntryBuilder::new(mac).ifindex(ifindex);
if let Some(v) = vlan {
entry = entry.vlan(v);
}
let mut builder = MessageBuilder::new(NlMsgType::RTM_DELNEIGH, NLM_F_REQUEST | NLM_F_ACK);
entry.write_delete(&mut builder, ifindex);
self.send_ack(builder)
.await
.map_err(|e| e.with_context("del_fdb"))
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "flush_fdb"))]
pub async fn flush_fdb(&self, bridge: impl Into<InterfaceRef>) -> Result<()> {
let entries = self.get_fdb(bridge).await?;
for entry in entries {
if entry.is_dynamic()
&& let Err(e) = self
.del_fdb_by_index(entry.ifindex, entry.mac, entry.vlan)
.await
{
if !e.is_not_found() {
return Err(e);
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_mac() {
let mac = FdbEntryBuilder::parse_mac("aa:bb:cc:dd:ee:ff").unwrap();
assert_eq!(mac, [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]);
}
#[test]
fn test_parse_mac_uppercase() {
let mac = FdbEntryBuilder::parse_mac("AA:BB:CC:DD:EE:FF").unwrap();
assert_eq!(mac, [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]);
}
#[test]
fn test_parse_mac_invalid() {
assert!(FdbEntryBuilder::parse_mac("invalid").is_err());
assert!(FdbEntryBuilder::parse_mac("aa:bb:cc:dd:ee").is_err());
assert!(FdbEntryBuilder::parse_mac("aa:bb:cc:dd:ee:ff:gg").is_err());
}
#[test]
fn test_fdb_entry_mac_str() {
let entry = FdbEntry {
ifindex: 1,
mac: [0x00, 0x11, 0x22, 0x33, 0x44, 0x55],
vlan: None,
dst: None,
vni: None,
state: NeighborState::Permanent,
flags: 0,
master: None,
};
assert_eq!(entry.mac_str(), "00:11:22:33:44:55");
}
#[test]
fn test_fdb_entry_flags() {
let entry = FdbEntry {
ifindex: 1,
mac: [0; 6],
vlan: None,
dst: None,
vni: None,
state: NeighborState::Permanent,
flags: ntf::SELF | ntf::MASTER,
master: None,
};
assert!(entry.is_self());
assert!(entry.is_master());
assert!(!entry.is_extern_learn());
}
#[test]
fn test_fdb_entry_permanent() {
let permanent = FdbEntry {
ifindex: 1,
mac: [0; 6],
vlan: None,
dst: None,
vni: None,
state: NeighborState::Permanent,
flags: 0,
master: None,
};
assert!(permanent.is_permanent());
assert!(!permanent.is_dynamic());
let dynamic = FdbEntry {
ifindex: 1,
mac: [0; 6],
vlan: None,
dst: None,
vni: None,
state: NeighborState::Reachable,
flags: 0,
master: None,
};
assert!(!dynamic.is_permanent());
assert!(dynamic.is_dynamic());
}
#[test]
fn test_builder_default() {
let builder = FdbEntryBuilder::new([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]);
assert!(builder.permanent); assert!(!builder.self_flag);
}
#[test]
fn test_builder_chain() {
let builder = FdbEntryBuilder::new([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff])
.dev("veth0")
.master("br0")
.vlan(100)
.dynamic()
.self_();
assert_eq!(builder.dev, Some(InterfaceRef::Name("veth0".to_string())));
assert_eq!(builder.master, Some(InterfaceRef::Name("br0".to_string())));
assert_eq!(builder.vlan, Some(100));
assert!(!builder.permanent);
assert!(builder.self_flag);
}
#[test]
fn test_builder_ifindex() {
let builder = FdbEntryBuilder::new([0; 6]).ifindex(5).master_ifindex(3);
assert_eq!(builder.dev, Some(InterfaceRef::Index(5)));
assert_eq!(builder.master, Some(InterfaceRef::Index(3)));
}
}