From 59be42273ff158fa7e80e18c3891c7b20570f97e Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Wed, 12 Oct 2022 17:55:14 +0200 Subject: [PATCH] netlink: add del_{addr,route} calls 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 --- src/network/netlink.rs | 40 +++++++++++++++++--- src/test/netlink.rs | 86 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 5 deletions(-) diff --git a/src/network/netlink.rs b/src/network/netlink.rs index dc53a2cf1..e25dc8556 100644 --- a/src/network/netlink.rs +++ b/src/network/netlink.rs @@ -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}; @@ -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; @@ -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, @@ -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; @@ -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)?; @@ -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> { 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; @@ -305,7 +336,6 @@ impl Socket { loop { let bytes = &self.buffer[offset..]; - let rx_packet: NetlinkMessage = NetlinkMessage::deserialize(bytes) .map_err(|e| { NetavarkError::Message(format!( diff --git a/src/test/netlink.rs b/src/test/netlink.rs index 9f9a65a6a..a75ee076c 100644 --- a/src/test/netlink.rs +++ b/src/test/netlink.rs @@ -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"); + } }