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

Allow specifying mac on macvlan link creation #45

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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ env_logger = "0.10.0"
ipnetwork = "0.18.0"
tokio = { version = "1.0.1", features = ["macros", "rt", "rt-multi-thread"] }
async-std = { version = "1.9.0", features = ["attributes"]}
macaddr = "1.0"
30 changes: 24 additions & 6 deletions examples/create_macvlan.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,55 @@
// SPDX-License-Identifier: MIT

use futures::stream::TryStreamExt;
use macaddr::MacAddr;
use rtnetlink::{new_connection, Error, Handle};
use std::env;
use std::{env, str::FromStr};

use netlink_packet_route::link::LinkAttribute;

#[tokio::main]
async fn main() -> Result<(), String> {
let args: Vec<String> = env::args().collect();
if args.len() != 2 {
if args.len() != 2 && args.len() != 3 {
usage();
return Ok(());
}
let link_name = &args[1];
let mac: Option<Vec<u8>> = if args.len() == 3 {
let mac_address_arg = (&args[2]).to_string();
let mac_address = MacAddr::from_str(mac_address_arg.as_str())
.map_err(|e| format!("{e}"))?;
Some(mac_address.as_bytes().into())
} else {
None
};

let (connection, handle, _) = new_connection().unwrap();
tokio::spawn(connection);

create_macvlan(handle, link_name.to_string())
create_macvlan(handle, link_name.to_string(), mac)
.await
.map_err(|e| format!("{e}"))
}

async fn create_macvlan(
handle: Handle,
link_name: String,
mac_address: Option<Vec<u8>>,
) -> Result<(), Error> {
let mut links = handle.link().get().match_name(link_name.clone()).execute();
if let Some(link) = links.try_next().await? {
let request = handle.link().add().macvlan(
let mut request = handle.link().add().macvlan(
"test_macvlan".into(),
link.header.index,
4u32, // bridge mode
);
if let Some(mac) = mac_address {
request
.message_mut()
.attributes
.push(LinkAttribute::Address(mac));
}
request.execute().await?
} else {
println!("no link {link_name} found");
Expand All @@ -42,7 +60,7 @@ async fn create_macvlan(
fn usage() {
eprintln!(
"usage:
cargo run --example create_macvlan -- <link name>
cargo run --example create_macvlan -- <link name> [mac address]

Note that you need to run this program as root. Instead of running cargo as root,
build the example normally:
Expand All @@ -51,6 +69,6 @@ build the example normally:

Then find the binary in the target directory:

cd target/debug/examples ; sudo ./create_macvlan <link_name>"
cd target/debug/examples ; sudo ./create_macvlan <link_name> [mac address]"
);
}
11 changes: 8 additions & 3 deletions src/link/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -790,9 +790,14 @@ impl LinkAddRequest {
self.append_nla(LinkAttribute::LinkInfo(link_info_nlas))
}

fn name(mut self, name: String) -> Self {
self.message.attributes.push(LinkAttribute::IfName(name));
self
pub fn name(self, name: String) -> Self {
self.append_nla(LinkAttribute::IfName(name))
}

/// Define the hardware address of the link when creating it (equivalent to
/// `ip link add NAME address ADDRESS`)
pub fn address(self, address: Vec<u8>) -> Self {
self.append_nla(LinkAttribute::Address(address))
}

fn append_nla(mut self, nla: LinkAttribute) -> Self {
Expand Down
85 changes: 79 additions & 6 deletions src/link/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use futures::stream::TryStreamExt;
use netlink_packet_route::link::{
InfoKind, LinkAttribute, LinkInfo, LinkMessage,
InfoData, InfoKind, InfoMacVlan, LinkAttribute, LinkInfo, LinkMessage,
};
use tokio::runtime::Runtime;

Expand All @@ -16,14 +16,66 @@ fn create_get_delete_wg() {
let handle = rt.block_on(_create_wg());
assert!(handle.is_ok());
let mut handle = handle.unwrap();
let msg = rt.block_on(_get_wg(&mut handle));
let msg = rt.block_on(_get_iface(&mut handle, IFACE_NAME.to_owned()));
assert!(msg.is_ok());
let msg = msg.unwrap();
assert!(has_nla(
&msg,
&LinkAttribute::LinkInfo(vec![LinkInfo::Kind(InfoKind::Wireguard)])
));
rt.block_on(_del_wg(&mut handle, msg.header.index)).unwrap();
rt.block_on(_del_iface(&mut handle, msg.header.index))
.unwrap();
}

#[test]
fn create_get_delete_macvlan() {
const MACVLAN_IFACE_NAME: &str = "mvlan1";
const LOWER_DEVICE_IDX: u32 = 2;
const MACVLAN_MODE: u32 = 4; // bridge
let mac_address = &vec![2u8, 0, 0, 0, 0, 1];

let rt = Runtime::new().unwrap();
let handle = rt.block_on(_create_macvlan(
&MACVLAN_IFACE_NAME.to_owned(),
LOWER_DEVICE_IDX, /* assuming there's always a network interface in
* the system ... */
MACVLAN_MODE,
mac_address.to_vec(),
));
assert!(handle.is_ok());

let mut handle = handle.unwrap();
let msg =
rt.block_on(_get_iface(&mut handle, MACVLAN_IFACE_NAME.to_owned()));
assert!(msg.is_ok());
assert!(has_nla(
&msg.as_ref().unwrap(),
&LinkAttribute::LinkInfo(vec![
LinkInfo::Kind(InfoKind::MacVlan),
LinkInfo::Data(InfoData::MacVlan(vec![
InfoMacVlan::Mode(MACVLAN_MODE),
InfoMacVlan::Flags(0), // defaulted by the kernel
InfoMacVlan::MacAddrCount(0), // defaulted by the kernel
InfoMacVlan::BcQueueLen(1000), // defaulted by the kernel
InfoMacVlan::BcQueueLenUsed(1000) // defaulted by the kernel
]))
])
));
assert!(has_nla(
&msg.as_ref().unwrap(),
&LinkAttribute::IfName(MACVLAN_IFACE_NAME.to_string())
));
assert!(has_nla(
&msg.as_ref().unwrap(),
&LinkAttribute::Link(LOWER_DEVICE_IDX)
));
assert!(has_nla(
&msg.as_ref().unwrap(),
&LinkAttribute::Address(mac_address.to_vec())
));

rt.block_on(_del_iface(&mut handle, msg.unwrap().header.index))
.unwrap();
}

fn has_nla(msg: &LinkMessage, nla: &LinkAttribute) -> bool {
Expand All @@ -46,12 +98,33 @@ async fn _create_wg() -> Result<LinkHandle, Error> {
Ok(link_handle)
}

async fn _get_wg(handle: &mut LinkHandle) -> Result<LinkMessage, Error> {
let mut links = handle.get().match_name(IFACE_NAME.to_owned()).execute();
async fn _get_iface(
handle: &mut LinkHandle,
iface_name: String,
) -> Result<LinkMessage, Error> {
let mut links = handle.get().match_name(iface_name).execute();
let msg = links.try_next().await?;
msg.ok_or(Error::RequestFailed)
}

async fn _del_wg(handle: &mut LinkHandle, index: u32) -> Result<(), Error> {
async fn _del_iface(handle: &mut LinkHandle, index: u32) -> Result<(), Error> {
handle.del(index).execute().await
}

async fn _create_macvlan(
name: &String,
lower_device_index: u32,
mode: u32,
mac: Vec<u8>,
) -> Result<LinkHandle, Error> {
let (conn, handle, _) = new_connection().unwrap();
tokio::spawn(conn);
let link_handle = handle.link();
let req = link_handle
.add()
.macvlan(name.to_string(), lower_device_index, mode)
.name(name.to_owned())
.address(mac);
req.execute().await?;
Ok(link_handle)
}