Skip to content

Commit

Permalink
[netdog] Add version 3 of network configuration
Browse files Browse the repository at this point in the history
This adds version 3 of network configuration along with bonding and vlan
support end to end from net.toml to wicked xml. The support now works as
intended for both vlan virtual devices and bonding devices on metal.
This also moves around some of the device files into a new module so
that future expansion of devices is seperated from expansion of network
configuration versions. This also changes the validation logic to add in
traits that allow a generic IP Addressing validation function to run on
any network device that accepts addressing.
  • Loading branch information
yeazelm committed Dec 13, 2022
1 parent 9118ab0 commit 9e43a1a
Show file tree
Hide file tree
Showing 12 changed files with 624 additions and 82 deletions.
27 changes: 27 additions & 0 deletions sources/api/netdog/src/net_config/devices/interface.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use super::validate_addressing;
use super::{Dhcp4ConfigV1, Dhcp6ConfigV1, Result, Validate};
use crate::net_config::devices::generate_addressing_validation;
use crate::net_config::{RouteV1, StaticConfigV1};
use serde::Deserialize;

#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct NetInterfaceV2 {
// Use this interface as the primary interface for the system
pub(crate) primary: Option<bool>,
pub(crate) dhcp4: Option<Dhcp4ConfigV1>,
pub(crate) dhcp6: Option<Dhcp6ConfigV1>,
pub(crate) static4: Option<StaticConfigV1>,
pub(crate) static6: Option<StaticConfigV1>,
#[serde(rename = "route")]
pub(crate) routes: Option<Vec<RouteV1>>,
}

impl Validate for NetInterfaceV2 {
fn validate(&self) -> Result<()> {
validate_addressing(self)
}
}

// Generate the traits for IP Address validation
generate_addressing_validation!(&NetInterfaceV2);
128 changes: 128 additions & 0 deletions sources/api/netdog/src/net_config/devices/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
//! The devices module contains all the types of network devices that `netdog` supports. These are
//! intended to be the structs used for net.toml deserialization including the validation logic for
//! each device.

pub(crate) mod bonding;
pub(crate) mod interface;
pub(crate) mod vlan;

use super::{error, Result, Validate};
use crate::net_config::{Dhcp4ConfigV1, Dhcp6ConfigV1, RouteV1, StaticConfigV1};
use bonding::NetBondV1;
use interface::NetInterfaceV2;
use serde::Deserialize;
use vlan::NetVlanV1;

#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub(crate) enum NetworkDeviceV1 {
Interface(NetInterfaceV2),
BondDevice(NetBondV1),
VlanDevice(NetVlanV1),
}

impl NetworkDeviceV1 {
pub(crate) fn primary(&self) -> Option<bool> {
match self {
Self::Interface(i) => i.primary,
Self::BondDevice(i) => i.primary,
Self::VlanDevice(i) => i.primary,
}
}
}

impl Validate for NetworkDeviceV1 {
fn validate(&self) -> Result<()> {
match self {
Self::Interface(config) => config.validate()?,
Self::BondDevice(config) => config.validate()?,
Self::VlanDevice(config) => config.validate()?,
}
Ok(())
}
}

#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub(crate) enum DeviceType {
#[serde(rename = "bond")]
Bond,
#[serde(rename = "vlan")]
Vlan,
}

pub(crate) trait HasIpAddressing {
fn has_static(&self) -> bool;

fn validate_static4(&self) -> Result<()>;
fn validate_static6(&self) -> Result<()>;

fn has_dhcp(&self) -> bool;
fn has_routes(&self) -> bool;
}

pub(crate) fn validate_addressing<D>(device: D) -> Result<()>
where
D: HasIpAddressing,
{
if !device.has_dhcp() && !device.has_static() {
return error::InvalidNetConfigSnafu {
reason: "each interface must configure dhcp and/or static addresses",
}
.fail();
}

// wicked doesn't support static routes with dhcp
if device.has_dhcp() && device.has_routes() {
return error::InvalidNetConfigSnafu {
reason: "static routes are not supported with dhcp",
}
.fail();
}

if device.has_routes() && !device.has_static() {
return error::InvalidNetConfigSnafu {
reason: "interfaces must set static addresses in order to use routes",
}
.fail();
}

// call into struct for access to fields for validation
device.validate_static4()?;
device.validate_static6()?;

Ok(())
}

// For all devices that have IP Addressing available, generate the trait implementation
macro_rules! generate_addressing_validation {
($name:ty) => {
use crate::net_config::devices::HasIpAddressing;
impl HasIpAddressing for $name {
fn has_static(&self) -> bool {
self.static4.is_some() || self.static6.is_some()
}
fn validate_static4(&self) -> Result<()> {
if let Some(config) = &self.static4 {
config.validate()?
}
Ok(())
}

fn validate_static6(&self) -> Result<()> {
if let Some(config) = &self.static6 {
config.validate()?
}
Ok(())
}

fn has_dhcp(&self) -> bool {
self.dhcp4.is_some() || self.dhcp6.is_some()
}
fn has_routes(&self) -> bool {
self.routes.is_some()
}
}
};
}
pub(crate) use generate_addressing_validation;
7 changes: 6 additions & 1 deletion sources/api/netdog/src/net_config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
//! the kernel command line.
//!
//! These structures are the user-facing options for configuring one or more network interfaces.

pub(crate) mod devices;
mod dhcp;
mod error;
mod static_address;
mod v1;
mod v2;
mod v3;

use crate::wicked::WickedInterface;
pub(crate) use dhcp::{Dhcp4ConfigV1, Dhcp4OptionsV1, Dhcp6ConfigV1, Dhcp6OptionsV1};
Expand Down Expand Up @@ -98,11 +101,12 @@ fn deserialize_config(config_str: &str) -> Result<Box<dyn Interfaces>> {
let net_config: Box<dyn Interfaces> = match version {
1 => validate_config::<v1::NetConfigV1>(interface_config)?,
2 => validate_config::<v2::NetConfigV2>(interface_config)?,
3 => validate_config::<v3::NetConfigV3>(interface_config)?,
_ => {
return error::InvalidNetConfigSnafu {
reason: format!("Unknown network config version: {}", version),
}
.fail()
.fail();
}
};

Expand Down Expand Up @@ -152,6 +156,7 @@ where

#[cfg(test)]
mod test_macros;

#[cfg(test)]
mod tests {
use std::path::PathBuf;
Expand Down
17 changes: 16 additions & 1 deletion sources/api/netdog/src/net_config/static_address.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use super::error::{InvalidNetConfigSnafu, Result as ValidateResult};
use crate::net_config::Validate;
use ipnet::IpNet;
use serde::Deserialize;
use snafu::ResultExt;
use snafu::{ensure, ResultExt};
use std::collections::BTreeSet;
use std::convert::TryFrom;
use std::net::IpAddr;
Expand Down Expand Up @@ -48,6 +50,19 @@ impl TryFrom<String> for RouteTo {
}
}

impl Validate for StaticConfigV1 {
fn validate(&self) -> ValidateResult<()> {
ensure!(
self.addresses.iter().all(|a| matches!(a, IpNet::V4(_)))
|| self.addresses.iter().all(|a| matches!(a, IpNet::V6(_))),
InvalidNetConfigSnafu {
reason: "static configuration must only contain all IPv4 or all IPv6 addresses"
}
);
Ok(())
}
}

mod error {
use snafu::Snafu;

Expand Down
81 changes: 81 additions & 0 deletions sources/api/netdog/src/net_config/test_macros/bonding.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
macro_rules! bonding_tests {
($version:expr) => {
mod bonding {
use $crate::net_config::deserialize_config;
use $crate::net_config::test_macros::gen_boilerplate;

gen_boilerplate!($version, "bonding");

#[test]
fn ok_config() {
let ok = net_config().join("net_config.toml");
let rendered = render_config_template(ok);
assert!(deserialize_config(&rendered).is_ok())
}

#[test]
fn missing_kind() {
let bad = net_config().join("missing_kind.toml");
let rendered = render_config_template(bad);
assert!(deserialize_config(&rendered).is_err())
}

#[test]
fn no_monitoring() {
let bad = net_config().join("no_monitoring.toml");
let rendered = render_config_template(bad);
assert!(deserialize_config(&rendered).is_err())
}

#[test]
fn both_monitoring() {
let bad = net_config().join("both_monitoring.toml");
let rendered = render_config_template(bad);
assert!(deserialize_config(&rendered).is_err())
}

#[test]
fn no_interfaces() {
let bad = net_config().join("no_interfaces.toml");
let rendered = render_config_template(bad);
assert!(deserialize_config(&rendered).is_err())
}

#[test]
fn disabled_miimon() {
let bad = net_config().join("disabled_miimon.toml");
let rendered = render_config_template(bad);
assert!(deserialize_config(&rendered).is_err())
}

#[test]
fn disabled_arpmon() {
let bad = net_config().join("disabled_arpmon.toml");
let rendered = render_config_template(bad);
assert!(deserialize_config(&rendered).is_err())
}

#[test]
fn too_many_min_links() {
let bad = net_config().join("too_many_min_links.toml");
let rendered = render_config_template(bad);
assert!(deserialize_config(&rendered).is_err())
}

#[test]
fn arpmon_no_targets() {
let bad = net_config().join("arpmon_no_targets.toml");
let rendered = render_config_template(bad);
assert!(deserialize_config(&rendered).is_err())
}

#[test]
fn vlan_using_bond_interface() {
let bad = net_config().join("vlan_using_bond.toml");
let rendered = render_config_template(bad);
assert!(deserialize_config(&rendered).is_err())
}
}
};
}
pub(crate) use bonding_tests;
6 changes: 6 additions & 0 deletions sources/api/netdog/src/net_config/test_macros/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,19 @@
#[cfg(test)]
pub(super) mod basic;
#[cfg(test)]
pub(super) mod bonding;
#[cfg(test)]
pub(super) mod dhcp;
#[cfg(test)]
pub(super) mod static_address;
#[cfg(test)]
pub(super) mod vlan;

pub(super) use basic::basic_tests;
pub(super) use bonding::bonding_tests;
pub(super) use dhcp::dhcp_tests;
pub(super) use static_address::static_address_tests;
pub(super) use vlan::vlan_tests;

/// gen_boilerplate!() is a convenience macro meant to be used inside of test macros to generate
/// some generally useful boilerplate code. It creates a `VERSION` constant in case the test
Expand Down
46 changes: 46 additions & 0 deletions sources/api/netdog/src/net_config/test_macros/vlan.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
macro_rules! vlan_tests {
($version:expr) => {
mod vlan {
use $crate::net_config::deserialize_config;
use $crate::net_config::test_macros::gen_boilerplate;

gen_boilerplate!($version, "vlan");

#[test]
fn ok_config() {
let ok = net_config().join("net_config.toml");
let rendered = render_config_template(ok);
assert!(deserialize_config(&rendered).is_ok())
}

#[test]
fn no_id() {
let bad = net_config().join("no_id.toml");
let rendered = render_config_template(bad);
assert!(deserialize_config(&rendered).is_err())
}

#[test]
fn out_of_bounds_id() {
let bad = net_config().join("oob_id.toml");
let rendered = render_config_template(bad);
assert!(deserialize_config(&rendered).is_err())
}

#[test]
fn missing_kind() {
let bad = net_config().join("missing_kind.toml");
let rendered = render_config_template(bad);
assert!(deserialize_config(&rendered).is_err())
}

#[test]
fn no_device() {
let bad = net_config().join("no_device.toml");
let rendered = render_config_template(bad);
assert!(deserialize_config(&rendered).is_err())
}
}
};
}
pub(crate) use vlan_tests;
Loading

0 comments on commit 9e43a1a

Please sign in to comment.