From 648a19963b89379547647668043df7a8f07a860c Mon Sep 17 00:00:00 2001 From: Gris Ge Date: Mon, 16 Dec 2024 20:24:40 +0800 Subject: [PATCH] Add lease release support in ASYNC API Introducing these functions to release the lease: * `DhcpV6ClientAsync::release()` * `DhcpV4ClientAsync::release()` If user want to get new release after lease been released, new instance of DHCP Client should be created. Example code and integration test cases updated. Signed-off-by: Gris Ge --- README.md | 2 +- ...m_dhcpv4_sync.rs => mozim_dhcpv4_async.rs} | 14 ++++--- examples/mozim_dhcpv6_sync.rs | 2 + src/client_async.rs | 14 +++++++ src/dhcpv4/client.rs | 22 ++++++++-- src/dhcpv6/client.rs | 42 +++++++++++++++++++ src/dhcpv6/lease.rs | 5 +++ src/dhcpv6/msg.rs | 6 ++- src/integ_tests/dhcpv4_async.rs | 1 + src/integ_tests/dhcpv6_async.rs | 1 + 10 files changed, 98 insertions(+), 11 deletions(-) rename examples/{mozim_dhcpv4_sync.rs => mozim_dhcpv4_async.rs} (69%) diff --git a/README.md b/README.md index bb7a708..2acc8c9 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,6 @@ TODO: # The `eth1.ep` is DHCP server interface running dnsmasq in `mozim` network # namespace. sudo ./utils/test_env_mozim & -cargo run --example mozim_dhcpv4_sync +cargo run --example mozim_dhcpv4_async cargo run --example mozim_dhcpv6_sync ``` diff --git a/examples/mozim_dhcpv4_sync.rs b/examples/mozim_dhcpv4_async.rs similarity index 69% rename from examples/mozim_dhcpv4_sync.rs rename to examples/mozim_dhcpv4_async.rs index ee24f6d..5b8429a 100644 --- a/examples/mozim_dhcpv4_sync.rs +++ b/examples/mozim_dhcpv4_async.rs @@ -15,13 +15,15 @@ async fn main() -> Result<(), Box> { config.set_timeout(60); let mut cli = DhcpV4ClientAsync::init(config, None).unwrap(); - while let Some(Ok(lease)) = cli.next().await { - // You need to code to apply the IP address in lease to this NIC, so - // follow up renew can work. - println!("Got lease {lease:?}"); + loop { + if let Some(Ok(lease)) = cli.next().await { + // You need to code to apply the IP address in lease to this NIC, so + // follow up renew can work. + println!("Got lease {lease:?}"); + cli.release(&lease)?; + return Ok(()); + } } - - Ok(()) } fn enable_log() { diff --git a/examples/mozim_dhcpv6_sync.rs b/examples/mozim_dhcpv6_sync.rs index 3b146d7..4b3ec62 100644 --- a/examples/mozim_dhcpv6_sync.rs +++ b/examples/mozim_dhcpv6_sync.rs @@ -16,6 +16,8 @@ fn main() -> Result<(), Box> { for event in cli.poll(POLL_WAIT_TIME)? { if let Some(lease) = cli.process(event)? { println!("Got DHCPv6 lease {:?}", lease); + cli.release(&lease)?; + return Ok(()); } } } diff --git a/src/client_async.rs b/src/client_async.rs index 8161d35..64324a1 100644 --- a/src/client_async.rs +++ b/src/client_async.rs @@ -29,6 +29,13 @@ pub struct DhcpV4ClientAsync { share_state: Arc>, } +impl DhcpV4ClientAsync { + /// Release the lease acquired from DHCPv4 server. + pub fn release(&mut self, lease: &DhcpV4Lease) -> Result<(), DhcpError> { + self.client.release(lease) + } +} + impl Stream for DhcpV4ClientAsync { type Item = Result; @@ -241,3 +248,10 @@ impl std::ops::Drop for DhcpV6ClientAsync { } } } + +impl DhcpV6ClientAsync { + /// Release the lease acquired from DHCPv6 server. + pub fn release(&mut self, lease: &DhcpV6Lease) -> Result<(), DhcpError> { + self.client.release(lease) + } +} diff --git a/src/dhcpv4/client.rs b/src/dhcpv4/client.rs index d29579a..d552482 100644 --- a/src/dhcpv4/client.rs +++ b/src/dhcpv4/client.rs @@ -541,6 +541,9 @@ impl DhcpV4Client { } } + /// Release the DHCPv4 lease. + /// To request new lease once released, please create new instance of + /// [DhcpV4Client]. pub fn release(&mut self, lease: &DhcpV4Lease) -> Result<(), DhcpError> { let mut dhcp_msg = DhcpV4Message::new( &self.config, @@ -553,13 +556,26 @@ impl DhcpV4Client { let raw_socket = DhcpRawSocket::new(&self.config)?; raw_socket.send(&dhcp_msg.to_proxy_eth_pkg_unicast()?)?; } else { - let udp_socket = DhcpUdpSocket::new( + // Cannot create UDP socket when interface does not have DHCP IP + // assigned, so we fallback to RAW socket + match DhcpUdpSocket::new( self.config.iface_name.as_str(), &lease.yiaddr, &lease.siaddr, self.config.socket_timeout, - )?; - udp_socket.send(&dhcp_msg.to_dhcp_pkg()?)?; + ) { + Ok(udp_socket) => { + udp_socket.send(&dhcp_msg.to_dhcp_pkg()?)?; + } + Err(e) => { + log::debug!( + "Failed to create UDP socket to release lease {e}, \ + fallback to RAW socket" + ); + let raw_socket = DhcpRawSocket::new(&self.config)?; + raw_socket.send(&dhcp_msg.to_proxy_eth_pkg_unicast()?)?; + } + } } self.clean_up(); Ok(()) diff --git a/src/dhcpv6/client.rs b/src/dhcpv6/client.rs index 60d148a..5cbd064 100644 --- a/src/dhcpv6/client.rs +++ b/src/dhcpv6/client.rs @@ -73,6 +73,14 @@ impl AsRawFd for DhcpV6Client { } impl DhcpV6Client { + fn clean_up(&mut self) { + self.lease = None; + self.retrans_count = 0; + self.phase = DhcpV6Phase::Done; + self.event_pool.remove_all_event(); + self.udp_socket = None; + } + pub fn init( mut config: DhcpV6Config, lease: Option, @@ -167,6 +175,40 @@ impl DhcpV6Client { } } + /// The RFC 8415: + /// Implementations SHOULD retransmit one or more times but MAY choose + /// to terminate the retransmission procedure early. + /// So here we decided not to wait reply from DHCPv6 server. + /// To request new release, you need to create new instance of + /// [DhcpV6Client]. + pub fn release(&mut self, lease: &DhcpV6Lease) -> Result<(), DhcpError> { + if self.udp_socket.is_none() { + let socket = DhcpUdpSocket::new_v6( + self.config.iface_index, + &self.config.src_ip, + self.config.socket_timeout, + )?; + self.udp_socket = Some(socket); + } + let socket = self.udp_socket.as_ref().unwrap(); + + let mut dhcp_msg = DhcpV6Message::new( + &self.config, + DhcpV6MessageType::RELEASE, + self.xid, + ); + dhcp_msg.load_lease(lease.clone())?; + let dst = if lease.srv_ip.is_unspecified() { + &DHCPV6_REPLAY_AND_SRVS + } else { + &lease.srv_ip + }; + socket.send_to_v6(dst, &dhcp_msg.to_dhcp_pkg()?)?; + + self.clean_up(); + Ok(()) + } + fn process_solicit(&mut self) -> Result<(), DhcpError> { self.phase = DhcpV6Phase::PreSolicit; self.lease = None; diff --git a/src/dhcpv6/lease.rs b/src/dhcpv6/lease.rs index 66949fc..0c3f6c2 100644 --- a/src/dhcpv6/lease.rs +++ b/src/dhcpv6/lease.rs @@ -28,6 +28,7 @@ pub struct DhcpV6Lease { pub cli_duid: Vec, pub srv_duid: Vec, pub dhcp_opts: Vec, + pub srv_ip: Ipv6Addr, } impl Default for DhcpV6Lease { @@ -45,6 +46,7 @@ impl Default for DhcpV6Lease { cli_duid: Vec::new(), srv_duid: Vec::new(), dhcp_opts: Vec::new(), + srv_ip: Ipv6Addr::UNSPECIFIED, } } } @@ -79,6 +81,9 @@ impl std::convert::TryFrom<&v6::Message> for DhcpV6Lease { ret.t2 = v.t2; parse_dhcp_opt_iaadr(&v.opts, &mut ret); } + DhcpOption::ServerUnicast(srv_ip) => { + ret.srv_ip = *srv_ip; + } DhcpOption::StatusCode(v) => { if v.status != v6::Status::Success { return Err(DhcpError::new( diff --git a/src/dhcpv6/msg.rs b/src/dhcpv6/msg.rs index d5fc1c1..576e443 100644 --- a/src/dhcpv6/msg.rs +++ b/src/dhcpv6/msg.rs @@ -29,6 +29,8 @@ impl DhcpV6MessageType { pub(crate) const REPLY: Self = DhcpV6MessageType(v6::MessageType::Reply); pub(crate) const RENEW: Self = DhcpV6MessageType(v6::MessageType::Renew); pub(crate) const REBIND: Self = DhcpV6MessageType(v6::MessageType::Rebind); + pub(crate) const RELEASE: Self = + DhcpV6MessageType(v6::MessageType::Release); } impl Default for DhcpV6MessageType { @@ -170,7 +172,9 @@ impl DhcpV6Message { match self.msg_type { DhcpV6MessageType::SOLICIT | DhcpV6MessageType::REBIND => (), - DhcpV6MessageType::REQUEST | DhcpV6MessageType::RENEW => { + DhcpV6MessageType::REQUEST + | DhcpV6MessageType::RENEW + | DhcpV6MessageType::RELEASE => { if let Some(lease) = self.lease.as_ref() { dhcp_msg .opts_mut() diff --git a/src/integ_tests/dhcpv4_async.rs b/src/integ_tests/dhcpv4_async.rs index 3fe6b8a..f57fa79 100644 --- a/src/integ_tests/dhcpv4_async.rs +++ b/src/integ_tests/dhcpv4_async.rs @@ -52,6 +52,7 @@ fn test_dhcpv4_async() { // call to use_host_name_as_client_id(), then the server should // return FOO1_STATIC_IP_HOSTNAME_AS_CLIENT_ID. assert_eq!(lease.yiaddr, FOO1_STATIC_IP_HOSTNAME_AS_CLIENT_ID,); + cli.release(&lease).unwrap(); } }) } diff --git a/src/integ_tests/dhcpv6_async.rs b/src/integ_tests/dhcpv6_async.rs index f8fa205..ee454c3 100644 --- a/src/integ_tests/dhcpv6_async.rs +++ b/src/integ_tests/dhcpv6_async.rs @@ -28,6 +28,7 @@ fn test_dhcpv6_async() { // call to use_host_name_as_client_id(), then the server should // return FOO1_STATIC_IP_HOSTNAME_AS_CLIENT_ID. assert_eq!(lease.addr, FOO1_STATIC_IPV6); + cli.release(&lease).unwrap(); } }) }