Skip to content

feat: extend support to ipv6 for default MACAddres #534

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
123 changes: 107 additions & 16 deletions manufacturing-client/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use anyhow::{bail, Context, Result};
use clap::{Args, Parser, Subcommand};
use regex::Regex;
use std::io::{BufRead, BufReader};
use std::path::Path;
use std::{convert::TryFrom, fs};
use std::{convert::TryInto, env, str::FromStr};
Expand Down Expand Up @@ -324,11 +325,23 @@ async fn main() -> Result<()> {
url = args.manufacturing_server_url;

mfg_string_type = args.mfg_string_type;
// ensure that we are given the iface if MACAddress is selected
if mfg_string_type == MfgStringType::MACAddress && args.iface.is_none() {
bail!("When using MACAddress as the MfgStringType --iface is required");
if mfg_string_type == MfgStringType::MACAddress {
// user provided iface
if args.iface.is_some() {
iface = args.iface;
} else {
// If user has not selected any specific iface then default iface will be used
match get_default_network_iface() {
Ok(result) => {
iface = result;
log::info!("Default network interface found: {:#?}", iface);
}
Err(error) => {
bail!("Error retrieving default network interface: {}", error);
}
}
}
}
iface = args.iface;

keyref = KeyReference::str_key(args.key_ref)
.await
Expand All @@ -351,15 +364,28 @@ async fn main() -> Result<()> {
} else {
bail!("No DIUN root key verification methods set");
}
iface = args.iface;

log::debug!("Performing DIUN");
client = ServiceClient::new(ProtocolVersion::Version1_1, &url);
(keyref, mfg_string_type) = perform_diun(&mut client, diun_pub_key_verification)
.await
.context("Error performing DIUN")?;
if mfg_string_type == MfgStringType::MACAddress && iface.is_none() {
bail!("Server has requested mac_address as mfg_string_type and there is no iface set");
if mfg_string_type == MfgStringType::MACAddress {
// user provided iface
if args.iface.is_some() {
iface = args.iface;
} else {
// If user has not selected any specific iface then default iface will be used
match get_default_network_iface() {
Ok(result) => {
iface = result;
log::info!("Default network interface found: {:#?}", iface);
}
Err(error) => {
bail!("Error retrieving default network interface: {}", error);
}
}
}
}
}
}
Expand Down Expand Up @@ -388,9 +414,18 @@ async fn main() -> Result<()> {
format!("Unsupported MFG string type {env_mfg_string_type} requested")
})?;
if mfg_string_type == MfgStringType::MACAddress {
iface = Some(env::var("DI_MFG_STRING_TYPE_MAC_IFACE").context(
"Please provide an iface for the MAC address with DI_MFG_STRING_TYPE_MAC_IFACE",
)?);
iface = match env::var("DI_MFG_STRING_TYPE_MAC_IFACE") {
Ok(iface) => Some(iface),
Err(_) => match get_default_network_iface() {
Ok(result) => result,
Err(error) => {
bail!("Error determining default network interface: {}", error);
}
},
};
if iface.is_none() {
bail!("Error determining default network interface");
}
}
keyref = KeyReference::env_key()
.await
Expand All @@ -400,17 +435,24 @@ async fn main() -> Result<()> {
// since the mfg_string_type will be determined in the manufacturing server
// and it might request MACAddress as the mfg_string_type. What it cannot do
// is select the iface for the client, so we must set it ahead of time.
// Thereby, we are just getting this value if provided, hiding errors.
if env::var("DI_MFG_STRING_TYPE_MAC_IFACE").is_ok() {
iface = Some(env::var("DI_MFG_STRING_TYPE_MAC_IFACE").unwrap());
// This can be by setting DI_MFG_STRING_TYPE_MAC_IFACE env variable to required interface
// or else default active network interface will be assigned.
if let Ok(iface_var) = env::var("DI_MFG_STRING_TYPE_MAC_IFACE") {
iface = Some(iface_var);
}
(keyref, mfg_string_type) = perform_diun(&mut client, diun_pub_key_verification)
.await
.context("Error performing DIUN")?;
// once diun has been performed we can error ahead of time if the
// server has requested MACaddress but we haven't set an iface.
if mfg_string_type == MfgStringType::MACAddress && iface.is_none() {
bail!("Server has requested mac_address as mfg_string_type and there is no iface (DI_MFG_STRING_TYPE_MAC_IFACE) set");
match get_default_network_iface() {
Ok(result) => {
iface = result;
log::info!("Default network interface found: {:#?}", iface);
}
Err(error) => {
bail!("Error retrieving default network interface: {}", error);
}
}
}
}
}
Expand Down Expand Up @@ -937,3 +979,52 @@ impl KeyReference {
}
}
}

const IPV6_DEFAULT: &str = "00000000000000000000000000000000"; //DevSkim: ignore DS173237
const IPV4_DEFAULT: &str = "00000000";

fn get_default_network_iface() -> Result<Option<String>, std::io::Error> {
// Check IPv4 addresses from /proc/net/route
if let Ok(file) = std::fs::File::open("/proc/net/route") {
let reader = BufReader::new(file);

for line in reader.lines().skip(1) {
let line = line?;
let fields: Vec<_> = line.split_whitespace().collect();
if fields.is_empty() {
continue;
}
if fields[1] == IPV4_DEFAULT && fields[0] != "lo" {
let iface = fields[0].to_string();
log::info!("Default network interface is ipv4 based {}", iface);
return Ok(Some(iface));
}
}
}

// Check IPv6 addresses from /proc/net/ipv6_route
// FYI: For IPv4 addresses, DNS maintains so-called “A” records, for “Address”.
// The IPv6 equivalent is the “AAAA” record, for “Address four times longer”.
// so you can see fields being compared to a string which is fourtimes longer
// for ipv4 than for ipv6.
// For ipv6_route file and columns meaning ,refer https://tldp.org/HOWTO/Linux+IPv6-HOWTO/ch11s04.html

if let Ok(file) = std::fs::File::open("/proc/net/ipv6_route") {
let reader = BufReader::new(file);

for line in reader.lines().skip(1) {
let line = line?;
let fields: Vec<_> = line.split_whitespace().collect();
if fields.is_empty() {
continue;
}
if fields[1] == IPV6_DEFAULT && fields[9] != "lo" {
let iface = fields[0].to_string();
log::info!("Default network interface is ipv6 based {}", iface);
return Ok(Some(iface));
}
Comment on lines +1015 to +1025
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic is not going to work for parsing /proc/net/ipv6_route, because the layout of the fields is quite different from /proc/net/route

Skipping the first line of the output is likely going to drop one of the IPv6 loopback addresses; on two of my Fedora systems the line looks like:

00000000000000000000000000000001 80 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000001 00000000 00000001 lo

fields[0] in this table is going to be "IPv6 destination network displayed in 32 hexadecimal chars without colons as separator", so it is not the right field to use for the interface name.

It's also possible for an interface to have a single IPv6 address that is "link local" and will never be routed, so it is unlikely to be usable for broader communication. I would think we would need to check for this kind of address before we decide to use the interface name.

Copy link
Contributor Author

@sarmahaj sarmahaj Jul 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree!
I think instead of extracting the network interface at low level, it will be better to use some networking related crate/s which provides info on the active ipv6 network interface etc . I would like to convert this PR to draft, go back and check for a proper crate so that even if something changes with the kernel files (e.g. columns or way of representation this will fail!) whereas with crate it will be(probably) handled in case of any changes .
CC: @nullr0ute @7flying

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my only concern is that the crate (if any) should ideally be already packaged in Fedora, as well as its dependencies

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a standard network crate that provides all this information for both IPv4 and IPv6 that is widely used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

last time when I had looked for such crates, there were many options but

}
}

Err(std::io::Error::last_os_error())
}