Skip to content

Commit

Permalink
Interface configuration (#19)
Browse files Browse the repository at this point in the history
* interface: implement IP address configuration

Example configuration (CLI):
```
interfaces interface eth-rt2
 type iana-if-type:ethernetCsmacd
 !
 ipv4 address 10.0.1.1
  prefix-length 24
!
```

Example configuration (JSON):
```
{
  "ietf-interfaces:interfaces": {
    "interface": [
      {
        "name": "eth-rt2",
        "type": "iana-if-type:ethernetCsmacd",
        "ietf-ip:ipv4": {
          "address": [
            {
              "ip": "10.0.1.1",
              "prefix-length": 24
            }
          ]
        }
      }
    }
  }
}
```

This commit also includes a refactoring to allow configuring interfaces
before they exist at the OS-level. Then, once the interface becomes
available, its pre-existing configuration is applied.

Signed-off-by: Renato Westphal <renato@opensourcerouting.org>

* interface: implement admin status configuration

Signed-off-by: Renato Westphal <renato@opensourcerouting.org>

* interface: implement MTU configuration

Of particular note, the ietf-interfaces module has different leafs to
configure MTU for IPv4 and IPv6, but Linux supports a single MTU for
all L3 protocols. As such, a validation callback was added to ensure
the IPv4 and IPv6 MTUs are the same when both are configured.

Signed-off-by: Renato Westphal <renato@opensourcerouting.org>

* interface: implement VLAN subinterfaces configuration

Example configuration (CLI):
```
[snip]
interfaces interface eth-rt2.10
 type ietf-if-extensions:ethSubInterface
 encapsulation dot1q-vlan outer-tag vlan-id 10
 parent-interface eth-rt2
!
```

Example configuration (JSON):
```
{
  "ietf-interfaces:interfaces": {
    "interface": [
      [snip]
      {
        "name": "eth-rt2.10",
        "type": "ietf-if-extensions:ethSubInterface",
        "ietf-if-extensions:encapsulation": {
          "ietf-if-vlan-encapsulation:dot1q-vlan": {
            "outer-tag": {
              "vlan-id": 10
            }
          }
        },
        "ietf-if-extensions:parent-interface": "eth-rt2"
      }
    ]
  }
}
```

Signed-off-by: Renato Westphal <renato@opensourcerouting.org>

---------

Signed-off-by: Renato Westphal <renato@opensourcerouting.org>
  • Loading branch information
rwestphal committed Apr 16, 2024
1 parent 3178739 commit 75d177d
Show file tree
Hide file tree
Showing 17 changed files with 2,381 additions and 128 deletions.
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

0 comments on commit 75d177d

Please sign in to comment.