Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interface configuration #19

Merged
merged 4 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,10 @@ Holo supports the following IETF RFCs and Internet drafts:
| ietf-bfd@2022-09-22 | 100.00% | 100.00% | - | - | [100.00%](http://westphal.com.br/holo/ietf-bfd.html) |
| ietf-bgp-policy@2023-07-05 | 100.00% | - | - | - | [100.00%](http://westphal.com.br/holo/ietf-bgp-policy.html) |
| ietf-bgp@2023-07-05 | 32.38% | 87.86% | - | - | [61.39%](http://westphal.com.br/holo/ietf-bgp.html) |
| ietf-if-extensions@2023-01-26 | 100.00% | 0.00% | - | - | [50.00%](http://westphal.com.br/holo/ietf-if-extensions.html) |
| ietf-if-vlan-encapsulation@2023-01-26 | 42.86% | - | - | - | [42.86%](http://westphal.com.br/holo/ietf-if-vlan-encapsulation.html) |
| ietf-interfaces@2018-01-09 | 100.00% | 0.00% | - | - | [22.22%](http://westphal.com.br/holo/ietf-interfaces.html) |
| ietf-ip@2018-01-09 | 17.39% | 0.00% | - | - | [13.33%](http://westphal.com.br/holo/ietf-ip.html) |
| ietf-ip@2018-01-09 | 52.17% | 0.00% | - | - | [40.00%](http://westphal.com.br/holo/ietf-ip.html) |
| ietf-ipv4-unicast-routing@2018-03-13 | 100.00% | 100.00% | - | - | [100.00%](http://westphal.com.br/holo/ietf-ipv4-unicast-routing.html) |
| ietf-ipv6-unicast-routing@2018-03-13 | 40.62% | 100.00% | - | - | [45.71%](http://westphal.com.br/holo/ietf-ipv6-unicast-routing.html) |
| ietf-key-chain@2017-04-18 | 100.00% | 100.00% | - | - | [100.00%](http://westphal.com.br/holo/ietf-key-chain.html) |
Expand Down
2 changes: 2 additions & 0 deletions holo-interface/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ edition.workspace = true

[dependencies]
async-trait.workspace = true
bitflags.workspace = true
capctl.workspace = true
derive-new.workspace = true
enum-as-inner.workspace = true
futures.workspace = true
Expand Down
8 changes: 4 additions & 4 deletions holo-interface/src/ibus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ pub(crate) fn process_msg(master: &mut Master, msg: IbusMsg) {
notify_interface_update(
&master.ibus_tx,
iface.name.clone(),
iface.ifindex,
iface.mtu,
iface.ifindex.unwrap_or(0),
iface.mtu.unwrap_or(0),
iface.flags,
);

Expand All @@ -44,8 +44,8 @@ pub(crate) fn process_msg(master: &mut Master, msg: IbusMsg) {
notify_interface_update(
&master.ibus_tx,
iface.name.clone(),
iface.ifindex,
iface.mtu,
iface.ifindex.unwrap_or(0),
iface.mtu.unwrap_or(0),
iface.flags,
);

Expand Down
151 changes: 137 additions & 14 deletions holo-interface/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
use std::collections::{BTreeMap, HashMap};
use std::net::{IpAddr, Ipv4Addr};

use bitflags::bitflags;
use generational_arena::{Arena, Index};
use holo_utils::ibus::IbusSender;
use holo_utils::ip::Ipv4NetworkExt;
use holo_utils::southbound::{AddressFlags, InterfaceFlags};
use ipnetwork::{IpNetwork, Ipv4Network};

use crate::ibus;
use crate::northbound::configuration::InterfaceCfg;
use crate::{ibus, netlink};

#[derive(Debug, Default)]
pub struct Interfaces {
Expand All @@ -30,10 +32,12 @@ pub struct Interfaces {
#[derive(Debug)]
pub struct Interface {
pub name: String,
pub ifindex: u32,
pub mtu: u32,
pub config: InterfaceCfg,
pub ifindex: Option<u32>,
pub mtu: Option<u32>,
pub flags: InterfaceFlags,
pub addresses: BTreeMap<IpNetwork, InterfaceAddress>,
pub owner: Owner,
}

#[derive(Debug)]
Expand All @@ -42,25 +46,110 @@ pub struct InterfaceAddress {
pub flags: AddressFlags,
}

bitflags! {
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Owner: u8 {
const CONFIG = 0x01;
const SYSTEM = 0x02;
}
}

// ===== impl Interface =====

impl Interface {
// Applies the interface configuration.
//
// This method should only be called after the interface has been created
// at the OS-level.
async fn apply_config(
&self,
ifindex: u32,
netlink_handle: &rtnetlink::Handle,
interfaces: &Interfaces,
) {
// Set administrative status.
netlink::admin_status_change(
netlink_handle,
ifindex,
self.config.enabled,
)
.await;

// Create VLAN subinterface.
if let Some(vlan_id) = self.config.vlan_id
&& self.ifindex.is_none()
&& let Some(parent) = &self.config.parent
&& let Some(parent) = interfaces.get_by_name(parent)
&& let Some(parent_ifindex) = parent.ifindex
{
netlink::vlan_create(
netlink_handle,
self.name.clone(),
parent_ifindex,
vlan_id,
)
.await;
}

// Set MTU.
if let Some(mtu) = self.config.mtu {
netlink::mtu_change(netlink_handle, ifindex, mtu).await;
}

// Install interface addresses.
for addr in &self.config.addr_list {
netlink::addr_install(netlink_handle, ifindex, addr).await;
}
}
}

// ===== impl Interfaces =====

impl Interfaces {
// Adds an interface.
pub(crate) fn add(&mut self, ifname: String) {
if let Some(iface) = self.get_mut_by_name(&ifname) {
iface.owner.insert(Owner::CONFIG);
return;
}

// If the interface does not exist, create a new entry.
let iface = Interface {
name: ifname.clone(),
config: Default::default(),
ifindex: None,
mtu: None,
flags: InterfaceFlags::default(),
addresses: Default::default(),
owner: Owner::CONFIG,
};

let iface_idx = self.arena.insert(iface);
self.name_tree.insert(ifname.clone(), iface_idx);
}

// Adds or updates the interface with the specified attributes.
pub(crate) fn update(
pub(crate) async fn update(
&mut self,
ifname: String,
ifindex: u32,
mtu: u32,
flags: InterfaceFlags,
netlink_handle: &rtnetlink::Handle,
ibus_tx: Option<&IbusSender>,
) {
match self.ifindex_tree.get(&ifindex).copied() {
match self
.ifindex_tree
.get(&ifindex)
.or_else(|| self.name_tree.get(&ifname))
.copied()
{
Some(iface_idx) => {
let iface = &mut self.arena[iface_idx];

// If nothing of interest has changed, return early.
if iface.name == ifname
&& iface.mtu == mtu
&& iface.mtu == Some(mtu)
&& iface.flags == flags
{
return;
Expand All @@ -72,17 +161,30 @@ impl Interfaces {
iface.name.clone_from(&ifname);
self.name_tree.insert(ifname.clone(), iface_idx);
}
iface.mtu = mtu;
iface.owner.insert(Owner::SYSTEM);
iface.mtu = Some(mtu);
iface.flags = flags;

// In case the interface exists only in the configuration,
// initialize its ifindex and apply any pre-existing
// configuration options.
if iface.ifindex.is_none() {
iface.ifindex = Some(ifindex);

let iface = &self.arena[iface_idx];
iface.apply_config(ifindex, netlink_handle, self).await;
}
}
None => {
// If the interface does not exist, create a new entry.
let iface = Interface {
name: ifname.clone(),
ifindex,
mtu,
config: Default::default(),
ifindex: Some(ifindex),
mtu: Some(mtu),
flags,
addresses: Default::default(),
owner: Owner::SYSTEM,
};

let iface_idx = self.arena.insert(iface);
Expand All @@ -98,24 +200,45 @@ impl Interfaces {
}

// Removes the specified interface identified by its ifindex.
pub(crate) fn remove(
pub(crate) async fn remove(
&mut self,
ifindex: u32,
ifname: &str,
owner: Owner,
netlink_handle: &rtnetlink::Handle,
ibus_tx: Option<&IbusSender>,
) {
let Some(iface_idx) = self.ifindex_tree.get(&ifindex).copied() else {
let Some(iface_idx) = self.name_tree.get(ifname).copied() else {
return;
};
let iface = &mut self.arena[iface_idx];

// When the interface is unconfigured, uninstall all configured
// addresses associated with it.
if owner == Owner::CONFIG
&& let Some(ifindex) = iface.ifindex
{
for addr in &iface.config.addr_list {
netlink::addr_uninstall(netlink_handle, ifindex, addr).await;
}
}

// Remove interface only when it's both not present in the configuration
// and not available in the kernel.
iface.owner.remove(owner);
if !iface.owner.is_empty() {
return;
}

// Notify protocol instances.
let iface = &self.arena[iface_idx];
if let Some(ibus_tx) = ibus_tx {
ibus::notify_interface_del(ibus_tx, iface.name.clone());
}

// Remove interface.
self.name_tree.remove(&iface.name);
self.ifindex_tree.remove(&iface.ifindex);
if let Some(ifindex) = iface.ifindex {
self.ifindex_tree.remove(&ifindex);
}
self.arena.remove(iface_idx);

// Check if the Router ID needs to be updated.
Expand Down
29 changes: 20 additions & 9 deletions holo-interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// SPDX-License-Identifier: MIT
//

#![feature(lazy_cell)]
#![feature(lazy_cell, let_chains)]

mod ibus;
mod interface;
Expand All @@ -21,13 +21,16 @@ use tokio::sync::mpsc;
use tracing::Instrument;

use crate::interface::Interfaces;
use crate::netlink::NetlinkMonitor;

#[derive(Debug)]
pub struct Master {
// Northbound Tx channel.
pub nb_tx: NbProviderSender,
// Internal bus Tx channel.
pub ibus_tx: IbusSender,
// Netlink socket.
pub netlink_handle: rtnetlink::Handle,
// List of interfaces.
pub interfaces: Interfaces,
}
Expand All @@ -39,12 +42,10 @@ impl Master {
&mut self,
mut nb_rx: NbDaemonReceiver,
mut ibus_rx: IbusReceiver,
mut netlink_rx: NetlinkMonitor,
) {
let mut resources = vec![];

// Netlink initialization.
let mut netlink_monitor = netlink::init(self).await;

loop {
tokio::select! {
Some(request) = nb_rx.recv() => {
Expand All @@ -55,12 +56,12 @@ impl Master {
)
.await;
}
Some((msg, _)) = netlink_monitor.next() => {
netlink::process_msg(self, msg);
}
Ok(msg) = ibus_rx.recv() => {
ibus::process_msg(self, msg);
}
Some((msg, _)) = netlink_rx.next() => {
netlink::process_msg(self, msg).await;
}
}
}
}
Expand All @@ -76,15 +77,25 @@ pub fn start(
let (nb_daemon_tx, nb_daemon_rx) = mpsc::channel(4);

tokio::spawn(async move {
let span = Master::debug_span("");
// Initialize netlink socket.
let (netlink_handle, netlink_rx) = netlink::init().await;

let mut master = Master {
nb_tx,
ibus_tx,
netlink_handle,
interfaces: Default::default(),
};

// Fetch interface information from the kernel.
netlink::start(&mut master).await;

// Run task main loop.
master.run(nb_daemon_rx, ibus_rx).instrument(span).await;
let span = Master::debug_span("");
master
.run(nb_daemon_rx, ibus_rx, netlink_rx)
.instrument(span)
.await;
});

nb_daemon_tx
Expand Down
Loading
Loading