use nlink::netlink::{
Connection, Macsec, Route,
genl::macsec::{MacsecDevice, MacsecSaBuilder},
link::{DummyLink, MacsecLink},
namespace,
};
const LOCAL_KEY: [u8; 16] = [0x11; 16];
const PEER_KEY: [u8; 16] = [0x22; 16];
const PEER_SCI: u64 = 0x0011_2233_4455_0001;
#[tokio::main]
async fn main() -> nlink::Result<()> {
let args: Vec<String> = std::env::args().collect();
match args.get(1).map(|s| s.as_str()) {
Some("show") => run_show().await?,
Some("--apply") => run_apply().await?,
_ => print_overview(),
}
Ok(())
}
fn print_overview() {
println!("=== MACsec (IEEE 802.1AE) ===\n");
println!("Layer-2 encryption for point-to-point links. The kernel");
println!("object hierarchy is:\n");
println!(" macsec0 (TX SC ──> TX SA 0..3)");
println!(" (RX SC per peer ──> RX SA 0..3)\n");
println!("--- What --apply does ---\n");
println!(
" 1. Create a temporary namespace.
2. Add dummy0 (nlink), bring up.
3. Add macsec0 link=dummy0 via MacsecLink rtnetlink builder.
4. Open Connection::<Macsec>, print device SCI + cipher.
5. add_tx_sa(AN=0, local key, pn=1, active=true).
6. add_rx_sc(peer_sci).
7. add_rx_sa(peer_sci, AN=0, peer key, pn=1, active=true).
8. get_device() to dump the TX + RX SC/SA state.
9. del_rx_sa, del_rx_sc, del_tx_sa.
10. Delete namespace (takes macsec0 + dummy0 with it).
"
);
println!("--- Code ---\n");
println!(
r#" use nlink::netlink::{{Connection, Macsec, namespace}};
use nlink::netlink::genl::macsec::MacsecSaBuilder;
let conn: Connection<Macsec> =
namespace::connection_for_async("lab").await?;
// TX SA (encodes outgoing frames).
conn.add_tx_sa("macsec0",
MacsecSaBuilder::new(0, &local_key) // AN=0, key
.packet_number(1)
.active(true),
).await?;
// RX SC (per peer) + RX SA (decodes that peer's frames).
conn.add_rx_sc("macsec0", peer_sci).await?;
conn.add_rx_sa("macsec0", peer_sci,
MacsecSaBuilder::new(0, &peer_key)
.packet_number(1)
.active(true),
).await?;
// Inspect current state.
let dev = conn.get_device("macsec0").await?;
// Cleanup (caller's responsibility).
conn.del_rx_sa("macsec0", peer_sci, 0).await?;
conn.del_rx_sc("macsec0", peer_sci).await?;
conn.del_tx_sa("macsec0", 0).await?;
"#
);
println!("--- Re-run with `--apply` (as root) ---");
println!(" Runs the lifecycle above in a temporary namespace.");
}
async fn run_show() -> nlink::Result<()> {
let conn = match Connection::<Macsec>::new_async().await {
Ok(conn) => conn,
Err(e) => {
eprintln!("Failed to open MACsec GENL: {e}");
eprintln!("Load the module with: sudo modprobe macsec");
return Ok(());
}
};
println!("MACsec family_id={}\n", conn.family_id());
let route = Connection::<Route>::new()?;
let links = route.get_links().await?;
let macsec_links: Vec<_> = links
.iter()
.filter(|l| l.kind() == Some("macsec"))
.collect();
if macsec_links.is_empty() {
println!("No MACsec interfaces on this host.");
println!();
println!("Build one in a temporary namespace with:");
println!(" sudo cargo run -p nlink --example genl_macsec -- --apply");
return Ok(());
}
for link in &macsec_links {
let name = link.name_or("?");
match conn.get_device(name).await {
Ok(device) => print_device(name, &device),
Err(e) => eprintln!(" Error querying {name}: {e}"),
}
}
Ok(())
}
async fn run_apply() -> nlink::Result<()> {
if unsafe { libc::geteuid() } != 0 {
eprintln!("--apply requires root (CAP_NET_ADMIN). Aborting.");
std::process::exit(1);
}
println!("=== MACsec live demo (temporary namespace) ===");
let ns_name = format!("nlink-macsec-demo-{}", std::process::id());
namespace::create(&ns_name)?;
let result = run_demo(&ns_name).await;
let _ = namespace::delete(&ns_name);
result?;
println!();
println!("Done. Namespace `{ns_name}` removed.");
Ok(())
}
async fn run_demo(ns_name: &str) -> nlink::Result<()> {
let route: Connection<Route> = namespace::connection_for(ns_name)?;
route.add_link(DummyLink::new("dummy0")).await?;
route.set_link_up("dummy0").await?;
println!(" Created dummy0 in namespace `{ns_name}`.");
route
.add_link(MacsecLink::new("macsec0", "dummy0"))
.await
.map_err(|e| {
eprintln!("\n add_link(MacsecLink) failed: {e}");
eprintln!(" Load the module with: sudo modprobe macsec");
e
})?;
route.set_link_up("macsec0").await?;
println!(" Created macsec0 link=dummy0 via MacsecLink and brought it up.");
let macsec: Connection<Macsec> = namespace::connection_for_async(ns_name).await?;
println!(
" Opened MACsec GENL connection (family_id={}).",
macsec.family_id()
);
println!();
println!(" --- Initial state ---");
let before = macsec.get_device("macsec0").await?;
print_device("macsec0", &before);
println!();
println!(" add_tx_sa(AN=0, key=0x11.., pn=1, active=true)");
macsec
.add_tx_sa(
"macsec0",
MacsecSaBuilder::new(0, &LOCAL_KEY)
.packet_number(1)
.active(true),
)
.await?;
println!(" add_rx_sc(peer_sci=0x{PEER_SCI:016x})");
macsec.add_rx_sc("macsec0", PEER_SCI).await?;
println!(" add_rx_sa(peer_sci, AN=0, key=0x22.., pn=1, active=true)");
macsec
.add_rx_sa(
"macsec0",
PEER_SCI,
MacsecSaBuilder::new(0, &PEER_KEY)
.packet_number(1)
.active(true),
)
.await?;
println!();
println!(" --- After SA configuration ---");
let after = macsec.get_device("macsec0").await?;
print_device("macsec0", &after);
println!();
println!(" Cleaning up: del_rx_sa, del_rx_sc, del_tx_sa...");
macsec.del_rx_sa("macsec0", PEER_SCI, 0).await?;
macsec.del_rx_sc("macsec0", PEER_SCI).await?;
macsec.del_tx_sa("macsec0", 0).await?;
Ok(())
}
fn print_device(name: &str, device: &MacsecDevice) {
println!(" interface: {name} (ifindex {})", device.ifindex);
println!(" SCI: 0x{:016x}", device.sci);
println!(" cipher: {:?}", device.cipher);
println!(" encoding_sa: {}", device.encoding_sa);
println!(
" encrypt={} protect={} replay_protect={}",
device.encrypt, device.protect, device.replay_protect
);
if let Some(tx_sc) = &device.tx_sc {
println!(" TX SC (SCI 0x{:016x}):", tx_sc.sci);
if tx_sc.sas.is_empty() {
println!(" (no TX SAs)");
}
for sa in &tx_sc.sas {
println!(" TX SA an={} active={} pn={}", sa.an, sa.active, sa.pn);
}
} else {
println!(" TX SC: (none)");
}
if device.rx_scs.is_empty() {
println!(" RX SCs: (none)");
} else {
for rx in &device.rx_scs {
println!(" RX SC 0x{:016x} active={}:", rx.sci, rx.active);
for sa in &rx.sas {
println!(" RX SA an={} active={} pn={}", sa.an, sa.active, sa.pn);
}
}
}
}