Skip to content

Commit

Permalink
netlink: add del_{addr,route} calls
Browse files Browse the repository at this point in the history
It is not required for netavark since we just delete links on teardown
but the dhcp proxy might need to remove addresses and/or routes when
it gets a new lease.

Unfortunately the del_route unit test doe snot seem to work since it
requires "real" working routes which we cannot create in a custom netns.

Signed-off-by: Paul Holzinger <[email protected]>
  • Loading branch information
Luap99 committed Oct 13, 2022
1 parent a2458d6 commit 59be422
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 5 deletions.
40 changes: 35 additions & 5 deletions src/network/netlink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use netlink_packet_route::{
nlas::link::{Info, InfoData, InfoKind, Nla},
AddressMessage, LinkMessage, NetlinkHeader, NetlinkMessage, NetlinkPayload, RouteMessage,
RtnlMessage, AF_INET, AF_INET6, IFF_UP, NLM_F_ACK, NLM_F_CREATE, NLM_F_DUMP, NLM_F_EXCL,
NLM_F_REQUEST, RTN_UNICAST, RTPROT_STATIC, RT_SCOPE_UNIVERSE, RT_TABLE_MAIN,
NLM_F_REQUEST, RTN_UNICAST, RTPROT_STATIC, RTPROT_UNSPEC, RT_SCOPE_UNIVERSE, RT_TABLE_MAIN,
};
use netlink_sys::{protocols::NETLINK_ROUTE, SocketAddr};

Expand Down Expand Up @@ -118,7 +118,7 @@ impl Socket {
Ok(())
}

pub fn add_addr(&mut self, link_id: u32, addr: &ipnet::IpNet) -> NetavarkResult<()> {
fn create_addr_msg(link_id: u32, addr: &ipnet::IpNet) -> AddressMessage {
let mut msg = AddressMessage::default();
msg.header.index = link_id;

Expand All @@ -139,6 +139,11 @@ impl Socket {
msg.header.prefix_len = addr.prefix_len();
msg.nlas
.push(netlink_packet_route::address::Nla::Local(addr_vec));
msg
}

pub fn add_addr(&mut self, link_id: u32, addr: &ipnet::IpNet) -> NetavarkResult<()> {
let msg = Self::create_addr_msg(link_id, addr);
let result = self.make_netlink_request(
RtnlMessage::NewAddress(msg),
NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE,
Expand All @@ -150,7 +155,17 @@ impl Socket {
Ok(())
}

pub fn add_route(&mut self, route: &Route) -> NetavarkResult<()> {
pub fn del_addr(&mut self, link_id: u32, addr: &ipnet::IpNet) -> NetavarkResult<()> {
let msg = Self::create_addr_msg(link_id, addr);
let result = self.make_netlink_request(RtnlMessage::DelAddress(msg), NLM_F_ACK)?;
if !result.is_empty() {
return Err(NetavarkError::msg_str("unexpected netlink result"));
}

Ok(())
}

fn create_route_msg(route: &Route) -> RouteMessage {
let mut msg = RouteMessage::default();

msg.header.table = RT_TABLE_MAIN;
Expand Down Expand Up @@ -182,6 +197,11 @@ impl Socket {
.push(netlink_packet_route::route::Nla::Destination(dest_vec));
msg.nlas
.push(netlink_packet_route::route::Nla::Gateway(gateway_vec));
msg
}

pub fn add_route(&mut self, route: &Route) -> NetavarkResult<()> {
let msg = Self::create_route_msg(route);

let result =
self.make_netlink_request(RtnlMessage::NewRoute(msg), NLM_F_ACK | NLM_F_CREATE)?;
Expand All @@ -192,11 +212,22 @@ impl Socket {
Ok(())
}

pub fn del_route(&mut self, route: &Route) -> NetavarkResult<()> {
let msg = Self::create_route_msg(route);

let result = self.make_netlink_request(RtnlMessage::DelRoute(msg), NLM_F_ACK)?;
if !result.is_empty() {
return Err(NetavarkError::msg_str("unexpected netlink result"));
}

Ok(())
}

pub fn dump_routes(&mut self) -> NetavarkResult<Vec<RouteMessage>> {
let mut msg = RouteMessage::default();

msg.header.table = RT_TABLE_MAIN;
msg.header.protocol = RTPROT_STATIC;
msg.header.protocol = RTPROT_UNSPEC;
msg.header.scope = RT_SCOPE_UNIVERSE;
msg.header.kind = RTN_UNICAST;

Expand Down Expand Up @@ -305,7 +336,6 @@ impl Socket {

loop {
let bytes = &self.buffer[offset..];

let rx_packet: NetlinkMessage<RtnlMessage> = NetlinkMessage::deserialize(bytes)
.map_err(|e| {
NetavarkError::Message(format!(
Expand Down
86 changes: 86 additions & 0 deletions src/test/netlink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,90 @@ mod tests {

assert!(out.contains(net), "addr does not exists");
}

#[test]
fn test_del_addr() {
test_setup!();
let mut sock = Socket::new().expect("Socket::new()");

let out = run_command!("ip", "link", "add", "test1", "type", "dummy");
eprintln!("{}", String::from_utf8(out.stderr).unwrap());
assert!(out.status.success(), "failed to add link via ip");

let net = "10.0.0.2/24";

let out = run_command!("ip", "addr", "add", net, "dev", "test1");
eprintln!("{}", String::from_utf8(out.stderr).unwrap());
assert!(out.status.success(), "failed to add addr via ip");

let link = sock
.get_link(LinkID::Name("test1".into()))
.expect("get_link failed");

sock.del_addr(link.header.index, &net.parse().unwrap())
.expect("del_addr failed");

let out = run_command!("ip", "addr", "show", "test1");
let stdout = String::from_utf8(out.stdout).unwrap();
eprintln!("{}", stdout);
assert!(out.status.success(), "failed to show addr via ip");

assert!(!stdout.contains(net), "addr does exist");
}

/// This test fails because we do not have actual functioning routes in the test netns
/// For some reason the kernel expects us to set different options to make it work but
/// I do not want to expose them just for a test.
/// With these option you could get it to work but it will not work in the actual use case
/// msg.header.protocol = RTPROT_UNSPEC;
/// msg.header.scope = RT_SCOPE_NOWHERE;
/// msg.header.kind = RTN_UNSPEC;
#[test]
#[ignore]
fn test_del_route() {
test_setup!();
let mut sock = Socket::new().expect("Socket::new()");

let out = run_command!("ip", "link", "add", "test1", "type", "dummy");
eprintln!("{}", String::from_utf8(out.stderr).unwrap());
assert!(out.status.success(), "failed to add link via ip");

let net = "10.0.0.2/24";

let out = run_command!("ip", "addr", "add", net, "dev", "test1");
eprintln!("{}", String::from_utf8(out.stderr).unwrap());
assert!(out.status.success(), "failed to add addr via ip");

// route requires that the link is up!
let out = run_command!("ip", "link", "set", "dev", "test1", "up");
eprintln!("{}", String::from_utf8(out.stderr).unwrap());
assert!(out.status.success(), "failed to set test1 up via ip");

let net = "10.0.1.0/24";
let gw = "10.0.0.2";

let out = run_command!("ip", "route", "add", net, "via", gw);
eprintln!("{}", String::from_utf8(out.stderr).unwrap());
assert!(out.status.success(), "failed to add route via ip");

let out = run_command!("ip", "route", "show");
let stdout = String::from_utf8(out.stdout).unwrap();
eprintln!("{}", stdout);
assert!(out.status.success(), "failed to show addr via ip");

assert!(stdout.contains(net), "route should exist");

sock.del_route(&Route::Ipv4 {
dest: net.parse().unwrap(),
gw: gw.parse().unwrap(),
})
.expect("del_route failed");

let out = run_command!("ip", "route", "show");
let stdout = String::from_utf8(out.stdout).unwrap();
eprintln!("{}", stdout);
assert!(out.status.success(), "failed to show addr via ip");

assert!(!stdout.contains(net), "route should not exist");
}
}

0 comments on commit 59be422

Please sign in to comment.