From ab47db24e03a310162468f2cd6f0da1b3c34a14a Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 2 Apr 2021 00:14:30 +0200 Subject: [PATCH 01/12] udp: do not include payload in UdpRepr This makes UdpRepr work like IpRepr, where it only emits the header, and the user must emit the payload. This makes it easier to emit UDP packets with payloads that come from protocol-specific reprs, like DHCP and in the future DNS. --- src/dhcp/clientv4.rs | 20 +++----- src/iface/interface.rs | 111 +++++++++++++++++++++++++---------------- src/socket/icmp.rs | 29 ++++++++--- src/socket/udp.rs | 46 ++++++++--------- src/wire/ip.rs | 2 +- src/wire/udp.rs | 32 ++++++------ 6 files changed, 135 insertions(+), 105 deletions(-) diff --git a/src/dhcp/clientv4.rs b/src/dhcp/clientv4.rs index a454870..1679413 100644 --- a/src/dhcp/clientv4.rs +++ b/src/dhcp/clientv4.rs @@ -1,9 +1,9 @@ -use crate::{Result, Error}; +use crate::{Error, Result}; use crate::wire::{IpVersion, IpProtocol, IpEndpoint, IpAddress, Ipv4Cidr, Ipv4Address, Ipv4Packet, Ipv4Repr, UdpPacket, UdpRepr, DhcpPacket, DhcpRepr, DhcpMessageType}; -use crate::wire::dhcpv4::field as dhcpv4_field; +use crate::wire::dhcpv4::{field as dhcpv4_field, Packet as Dhcpv4Packet}; use crate::socket::{SocketSet, SocketHandle, RawSocket, RawSocketBuffer}; use crate::phy::{Device, ChecksumCapabilities}; use crate::iface::Interface; @@ -361,18 +361,10 @@ impl Client { } fn send_packet Device<'d>>(iface: &mut Interface, raw_socket: &mut RawSocket, endpoint: &IpEndpoint, dhcp_repr: &DhcpRepr, checksum_caps: &ChecksumCapabilities) -> Result<()> { - let mut dhcp_payload_buf = [0; 320]; - assert!(dhcp_repr.buffer_len() <= dhcp_payload_buf.len()); - let dhcp_payload = &mut dhcp_payload_buf[0..dhcp_repr.buffer_len()]; - { - let mut dhcp_packet = DhcpPacket::new_checked(&mut dhcp_payload[..])?; - dhcp_repr.emit(&mut dhcp_packet)?; - } let udp_repr = UdpRepr { src_port: UDP_CLIENT_PORT, dst_port: endpoint.port, - payload: dhcp_payload, }; let src_addr = iface.ipv4_addr().unwrap(); @@ -384,12 +376,12 @@ fn send_packet Device<'d>>(iface: &mut Interface, raw_ src_addr, dst_addr, protocol: IpProtocol::Udp, - payload_len: udp_repr.buffer_len(), + payload_len: udp_repr.header_len() + dhcp_repr.buffer_len(), hop_limit: 64, }; let mut packet = raw_socket.send( - ipv4_repr.buffer_len() + udp_repr.buffer_len() + ipv4_repr.buffer_len() + udp_repr.header_len() + dhcp_repr.buffer_len() )?; { let mut ipv4_packet = Ipv4Packet::new_unchecked(&mut packet); @@ -401,6 +393,8 @@ fn send_packet Device<'d>>(iface: &mut Interface, raw_ ); udp_repr.emit(&mut udp_packet, &src_addr.into(), &dst_addr.into(), + dhcp_repr.buffer_len(), + |buf| dhcp_repr.emit(&mut Dhcpv4Packet::new_unchecked(buf)).unwrap(), checksum_caps); } Ok(()) @@ -423,6 +417,6 @@ fn parse_udp<'a>(data: &'a [u8], checksum_caps: &ChecksumCapabilities) -> Result addr: ipv4_repr.dst_addr.into(), port: udp_repr.dst_port, }; - let data = udp_repr.payload; + let data = udp_packet.payload(); Ok((src, dst, data)) } diff --git a/src/iface/interface.rs b/src/iface/interface.rs index 4a1961e..d569067 100644 --- a/src/iface/interface.rs +++ b/src/iface/interface.rs @@ -263,7 +263,7 @@ pub(crate) enum IpPacket<'a> { #[cfg(feature = "socket-raw")] Raw((IpRepr, &'a [u8])), #[cfg(feature = "socket-udp")] - Udp((IpRepr, UdpRepr<'a>)), + Udp((IpRepr, UdpRepr, &'a [u8])), #[cfg(feature = "socket-tcp")] Tcp((IpRepr, TcpRepr<'a>)) } @@ -280,7 +280,7 @@ impl<'a> IpPacket<'a> { #[cfg(feature = "socket-raw")] IpPacket::Raw((ip_repr, _)) => ip_repr.clone(), #[cfg(feature = "socket-udp")] - IpPacket::Udp((ip_repr, _)) => ip_repr.clone(), + IpPacket::Udp((ip_repr, _, _)) => ip_repr.clone(), #[cfg(feature = "socket-tcp")] IpPacket::Tcp((ip_repr, _)) => ip_repr.clone(), } @@ -289,7 +289,7 @@ impl<'a> IpPacket<'a> { pub(crate) fn emit_payload(&self, _ip_repr: IpRepr, payload: &mut [u8], caps: &DeviceCapabilities) { match self { #[cfg(feature = "proto-ipv4")] - IpPacket::Icmpv4((_, icmpv4_repr)) => + IpPacket::Icmpv4((_, icmpv4_repr)) => icmpv4_repr.emit(&mut Icmpv4Packet::new_unchecked(payload), &caps.checksum), #[cfg(feature = "proto-igmp")] IpPacket::Igmp((_, igmp_repr)) => @@ -302,9 +302,11 @@ impl<'a> IpPacket<'a> { IpPacket::Raw((_, raw_packet)) => payload.copy_from_slice(raw_packet), #[cfg(feature = "socket-udp")] - IpPacket::Udp((_, udp_repr)) => + IpPacket::Udp((_, udp_repr, inner_payload)) => udp_repr.emit(&mut UdpPacket::new_unchecked(payload), - &_ip_repr.src_addr(), &_ip_repr.dst_addr(), &caps.checksum), + &_ip_repr.src_addr(), &_ip_repr.dst_addr(), + inner_payload.len(), |buf| buf.copy_from_slice(inner_payload), + &caps.checksum), #[cfg(feature = "socket-tcp")] IpPacket::Tcp((_, mut tcp_repr)) => { // This is a terrible hack to make TCP performance more acceptable on systems @@ -880,7 +882,7 @@ impl<'a> InterfaceInner<'a> { self.neighbor_cache.as_mut().unwrap().fill(ip_addr, eth_frame.src_addr(), timestamp); } } - + self.process_ipv6(sockets, timestamp, &ipv6_packet).map(|o| o.map(EthernetPacket::Ip)) } // Drop all other traffic. @@ -1439,11 +1441,12 @@ impl<'a> InterfaceInner<'a> { let udp_packet = UdpPacket::new_checked(ip_payload)?; let checksum_caps = self.device_capabilities.checksum.clone(); let udp_repr = UdpRepr::parse(&udp_packet, &src_addr, &dst_addr, &checksum_caps)?; + let udp_payload = udp_packet.payload(); for mut udp_socket in sockets.iter_mut().filter_map(UdpSocket::downcast) { if !udp_socket.accepts(&ip_repr, &udp_repr) { continue } - match udp_socket.process(&ip_repr, &udp_repr) { + match udp_socket.process(&ip_repr, &udp_repr, udp_payload) { // The packet is valid and handled by socket. Ok(()) => return Ok(None), // The packet is malformed, or the socket buffer is full. @@ -1787,6 +1790,13 @@ mod test { use crate::socket::SocketSet; use crate::phy::{Loopback, ChecksumCapabilities}; + #[allow(unused)] + fn fill_slice(s: &mut [u8], val: u8) { + for x in s.iter_mut() { + *x = val + } + } + fn create_loopback<'a>() -> (Interface<'a, Loopback>, SocketSet<'a>) { #[cfg(feature = "medium-ethernet")] return create_loopback_ethernet(); @@ -2035,20 +2045,21 @@ mod test { let udp_repr = UdpRepr { src_port: 67, dst_port: 68, - payload: &UDP_PAYLOAD }; let ip_repr = IpRepr::Ipv4(Ipv4Repr { src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]), dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]), protocol: IpProtocol::Udp, - payload_len: udp_repr.buffer_len(), + payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(), hop_limit: 64 }); // Emit the representations to a packet udp_repr.emit(&mut packet_unicast, &ip_repr.src_addr(), - &ip_repr.dst_addr(), &ChecksumCapabilities::default()); + &ip_repr.dst_addr(), + UDP_PAYLOAD.len(), |buf| buf.copy_from_slice( &UDP_PAYLOAD), + &ChecksumCapabilities::default()); let data = packet_unicast.into_inner(); @@ -2060,7 +2071,7 @@ mod test { src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]), dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]), protocol: IpProtocol::Udp, - payload_len: udp_repr.buffer_len(), + payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(), hop_limit: 64 }, data: &data @@ -2085,13 +2096,14 @@ mod test { src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]), dst_addr: Ipv4Address::BROADCAST, protocol: IpProtocol::Udp, - payload_len: udp_repr.buffer_len(), + payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(), hop_limit: 64 }); // Emit the representations to a packet udp_repr.emit(&mut packet_broadcast, &ip_repr.src_addr(), &IpAddress::Ipv4(Ipv4Address::BROADCAST), + UDP_PAYLOAD.len(), |buf| buf.copy_from_slice( &UDP_PAYLOAD), &ChecksumCapabilities::default()); // Ensure that the port unreachable error does not trigger an @@ -2129,7 +2141,6 @@ mod test { let udp_repr = UdpRepr { src_port: 67, dst_port: 68, - payload: &UDP_PAYLOAD }; #[cfg(feature = "proto-ipv6")] @@ -2137,7 +2148,7 @@ mod test { src_addr: src_ip, dst_addr: Ipv6Address::LINK_LOCAL_ALL_NODES, next_header: IpProtocol::Udp, - payload_len: udp_repr.buffer_len(), + payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(), hop_limit: 0x40 }); #[cfg(all(not(feature = "proto-ipv6"), feature = "proto-ipv4"))] @@ -2145,7 +2156,7 @@ mod test { src_addr: src_ip, dst_addr: Ipv4Address::BROADCAST, protocol: IpProtocol::Udp, - payload_len: udp_repr.buffer_len(), + payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(), hop_limit: 0x40 }); @@ -2158,6 +2169,7 @@ mod test { } udp_repr.emit(&mut packet, &ip_repr.src_addr(), &ip_repr.dst_addr(), + UDP_PAYLOAD.len(), |buf| buf.copy_from_slice( &UDP_PAYLOAD), &ChecksumCapabilities::default()); // Packet should be handled by bound UDP socket @@ -2260,18 +2272,19 @@ mod test { let udp_repr = UdpRepr { src_port: 67, dst_port: 68, - payload: &[0x2a; MAX_PAYLOAD_LEN] }; - let mut bytes = vec![0xff; udp_repr.buffer_len()]; + let mut bytes = vec![0xff; udp_repr.header_len() + MAX_PAYLOAD_LEN]; let mut packet = UdpPacket::new_unchecked(&mut bytes[..]); - udp_repr.emit(&mut packet, &src_addr.into(), &dst_addr.into(), &ChecksumCapabilities::default()); + udp_repr.emit(&mut packet, &src_addr.into(), &dst_addr.into(), + MAX_PAYLOAD_LEN, |buf| fill_slice(buf, 0x2a), + &ChecksumCapabilities::default()); #[cfg(all(feature = "proto-ipv4", not(feature = "proto-ipv6")))] let ip_repr = Ipv4Repr { src_addr: src_addr, dst_addr: dst_addr, protocol: IpProtocol::Udp, hop_limit: 64, - payload_len: udp_repr.buffer_len() + payload_len: udp_repr.header_len() + MAX_PAYLOAD_LEN }; #[cfg(feature = "proto-ipv6")] let ip_repr = Ipv6Repr { @@ -2279,7 +2292,7 @@ mod test { dst_addr: dst_addr, next_header: IpProtocol::Udp, hop_limit: 64, - payload_len: udp_repr.buffer_len() + payload_len: udp_repr.header_len() + MAX_PAYLOAD_LEN }; let payload = packet.into_inner(); @@ -2619,7 +2632,7 @@ mod test { recv_all(&mut iface, timestamp) .iter() .filter_map(|frame| { - + let ipv4_packet = match caps.medium { #[cfg(feature = "medium-ethernet")] Medium::Ethernet => { @@ -2666,11 +2679,11 @@ mod test { // General query let timestamp = Instant::now(); const GENERAL_QUERY_BYTES: &[u8] = &[ - 0x46, 0xc0, 0x00, 0x24, 0xed, 0xb4, 0x00, 0x00, - 0x01, 0x02, 0x47, 0x43, 0xac, 0x16, 0x63, 0x04, - 0xe0, 0x00, 0x00, 0x01, 0x94, 0x04, 0x00, 0x00, - 0x11, 0x64, 0xec, 0x8f, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x46, 0xc0, 0x00, 0x24, 0xed, 0xb4, 0x00, 0x00, + 0x01, 0x02, 0x47, 0x43, 0xac, 0x16, 0x63, 0x04, + 0xe0, 0x00, 0x00, 0x01, 0x94, 0x04, 0x00, 0x00, + 0x11, 0x64, 0xec, 0x8f, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ]; { @@ -2722,25 +2735,28 @@ mod test { let src_addr = Ipv4Address([127, 0, 0, 2]); let dst_addr = Ipv4Address([127, 0, 0, 1]); + const PAYLOAD_LEN: usize = 10; + let udp_repr = UdpRepr { src_port: 67, dst_port: 68, - payload: &[0x2a; 10] }; - let mut bytes = vec![0xff; udp_repr.buffer_len()]; + let mut bytes = vec![0xff; udp_repr.header_len() + PAYLOAD_LEN]; let mut packet = UdpPacket::new_unchecked(&mut bytes[..]); - udp_repr.emit(&mut packet, &src_addr.into(), &dst_addr.into(), &ChecksumCapabilities::default()); + udp_repr.emit(&mut packet, &src_addr.into(), &dst_addr.into(), + PAYLOAD_LEN, |buf| fill_slice(buf, 0x2a), + &ChecksumCapabilities::default()); let ipv4_repr = Ipv4Repr { src_addr: src_addr, dst_addr: dst_addr, protocol: IpProtocol::Udp, hop_limit: 64, - payload_len: udp_repr.buffer_len() + payload_len: udp_repr.header_len() + PAYLOAD_LEN }; // Emit to frame let mut bytes = vec![0u8; - ipv4_repr.buffer_len() + udp_repr.buffer_len() + ipv4_repr.buffer_len() + udp_repr.header_len() + PAYLOAD_LEN ]; let frame = { ipv4_repr.emit( @@ -2751,6 +2767,7 @@ mod test { &mut bytes[ipv4_repr.buffer_len()..]), &src_addr.into(), &dst_addr.into(), + PAYLOAD_LEN, |buf| fill_slice(buf, 0x2a), &ChecksumCapabilities::default()); Ipv4Packet::new_unchecked(&bytes) }; @@ -2776,25 +2793,28 @@ mod test { let src_addr = Ipv4Address([127, 0, 0, 2]); let dst_addr = Ipv4Address([127, 0, 0, 1]); + const PAYLOAD_LEN: usize = 49; // 49 > 48, hence packet will be truncated + let udp_repr = UdpRepr { src_port: 67, dst_port: 68, - payload: &[0x2a; 49] // 49 > 48, hence packet will be truncated }; - let mut bytes = vec![0xff; udp_repr.buffer_len()]; + let mut bytes = vec![0xff; udp_repr.header_len() + PAYLOAD_LEN]; let mut packet = UdpPacket::new_unchecked(&mut bytes[..]); - udp_repr.emit(&mut packet, &src_addr.into(), &dst_addr.into(), &ChecksumCapabilities::default()); + udp_repr.emit(&mut packet, &src_addr.into(), &dst_addr.into(), + PAYLOAD_LEN, |buf| fill_slice(buf, 0x2a), + &ChecksumCapabilities::default()); let ipv4_repr = Ipv4Repr { src_addr: src_addr, dst_addr: dst_addr, protocol: IpProtocol::Udp, hop_limit: 64, - payload_len: udp_repr.buffer_len() + payload_len: udp_repr.header_len() + PAYLOAD_LEN }; // Emit to frame let mut bytes = vec![0u8; - ipv4_repr.buffer_len() + udp_repr.buffer_len() + ipv4_repr.buffer_len() + udp_repr.header_len() + PAYLOAD_LEN ]; let frame = { ipv4_repr.emit( @@ -2802,9 +2822,10 @@ mod test { &ChecksumCapabilities::default()); udp_repr.emit( &mut UdpPacket::new_unchecked( - &mut bytes[ipv4_repr.buffer_len()..]), + &mut bytes[ipv4_repr.buffer_len()..]), &src_addr.into(), &dst_addr.into(), + PAYLOAD_LEN, |buf| fill_slice(buf, 0x2a), &ChecksumCapabilities::default()); Ipv4Packet::new_unchecked(&bytes) }; @@ -2812,7 +2833,7 @@ mod test { let frame = iface.inner.process_ipv4(&mut socket_set, Instant::from_millis(0), &frame); // because the packet could not be handled we should send an Icmp message - assert!(match frame { + assert!(match frame { Ok(Some(IpPacket::Icmpv4(_))) => true, _ => false, }); @@ -2853,22 +2874,23 @@ mod test { let udp_repr = UdpRepr { src_port: 67, dst_port: 68, - payload: &UDP_PAYLOAD }; - let mut bytes = vec![0xff; udp_repr.buffer_len()]; + let mut bytes = vec![0xff; udp_repr.header_len() + UDP_PAYLOAD.len()]; let mut packet = UdpPacket::new_unchecked(&mut bytes[..]); - udp_repr.emit(&mut packet, &src_addr.into(), &dst_addr.into(), &ChecksumCapabilities::default()); + udp_repr.emit(&mut packet, &src_addr.into(), &dst_addr.into(), + UDP_PAYLOAD.len(), |buf| buf.copy_from_slice( &UDP_PAYLOAD), + &ChecksumCapabilities::default()); let ipv4_repr = Ipv4Repr { src_addr: src_addr, dst_addr: dst_addr, protocol: IpProtocol::Udp, hop_limit: 64, - payload_len: udp_repr.buffer_len() + payload_len: udp_repr.header_len() + UDP_PAYLOAD.len() }; // Emit to frame let mut bytes = vec![0u8; - ipv4_repr.buffer_len() + udp_repr.buffer_len() + ipv4_repr.buffer_len() + udp_repr.header_len() + UDP_PAYLOAD.len() ]; let frame = { ipv4_repr.emit( @@ -2876,9 +2898,10 @@ mod test { &ChecksumCapabilities::default()); udp_repr.emit( &mut UdpPacket::new_unchecked( - &mut bytes[ipv4_repr.buffer_len()..]), + &mut bytes[ipv4_repr.buffer_len()..]), &src_addr.into(), &dst_addr.into(), + UDP_PAYLOAD.len(), |buf| buf.copy_from_slice( &UDP_PAYLOAD), &ChecksumCapabilities::default()); Ipv4Packet::new_unchecked(&bytes) }; diff --git a/src/socket/icmp.rs b/src/socket/icmp.rs index e5779bd..9208ca7 100644 --- a/src/socket/icmp.rs +++ b/src/socket/icmp.rs @@ -94,12 +94,12 @@ impl<'a> IcmpSocket<'a> { /// /// The waker is woken on state changes that might affect the return value /// of `recv` method calls, such as receiving data, or the socket closing. - /// + /// /// Notes: /// /// - Only one waker can be registered at a time. If another waker was previously registered, /// it is overwritten and will no longer be woken. - /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes. + /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes. /// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `recv` has /// necessarily changed. #[cfg(feature = "async")] @@ -112,12 +112,12 @@ impl<'a> IcmpSocket<'a> { /// The waker is woken on state changes that might affect the return value /// of `send` method calls, such as space becoming available in the transmit /// buffer, or the socket closing. - /// + /// /// Notes: /// /// - Only one waker can be registered at a time. If another waker was previously registered, /// it is overwritten and will no longer be woken. - /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes. + /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes. /// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `send` has /// necessarily changed. #[cfg(feature = "async")] @@ -440,7 +440,7 @@ impl<'a> IcmpSocket<'a> { _ => Err(Error::Unaddressable) } })?; - + #[cfg(feature = "async")] self.tx_waker.wake(); @@ -482,8 +482,9 @@ mod tests_common { pub static UDP_REPR: UdpRepr = UdpRepr { src_port: 53, dst_port: 9090, - payload: &[0xff; 10] }; + + pub static UDP_PAYLOAD: &[u8] = &[0xff; 10]; } #[cfg(all(test, feature = "proto-ipv4"))] @@ -644,7 +645,13 @@ mod test_ipv4 { let mut bytes = [0xff; 18]; let mut packet = UdpPacket::new_unchecked(&mut bytes); - UDP_REPR.emit(&mut packet, &REMOTE_IPV4.into(), &LOCAL_IPV4.into(), &checksum); + UDP_REPR.emit( + &mut packet, + &REMOTE_IPV4.into(), + &LOCAL_IPV4.into(), + UDP_PAYLOAD.len(), + |buf| buf.copy_from_slice(UDP_PAYLOAD), + &checksum); let data = &packet.into_inner()[..]; @@ -843,7 +850,13 @@ mod test_ipv6 { let mut bytes = [0xff; 18]; let mut packet = UdpPacket::new_unchecked(&mut bytes); - UDP_REPR.emit(&mut packet, &REMOTE_IPV6.into(), &LOCAL_IPV6.into(), &checksum); + UDP_REPR.emit( + &mut packet, + &REMOTE_IPV6.into(), + &LOCAL_IPV6.into(), + UDP_PAYLOAD.len(), + |buf| buf.copy_from_slice(UDP_PAYLOAD), + &checksum); let data = &packet.into_inner()[..]; diff --git a/src/socket/udp.rs b/src/socket/udp.rs index 88f5be4..f62d94f 100644 --- a/src/socket/udp.rs +++ b/src/socket/udp.rs @@ -54,12 +54,12 @@ impl<'a> UdpSocket<'a> { /// /// The waker is woken on state changes that might affect the return value /// of `recv` method calls, such as receiving data, or the socket closing. - /// + /// /// Notes: /// /// - Only one waker can be registered at a time. If another waker was previously registered, /// it is overwritten and will no longer be woken. - /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes. + /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes. /// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `recv` has /// necessarily changed. #[cfg(feature = "async")] @@ -72,12 +72,12 @@ impl<'a> UdpSocket<'a> { /// The waker is woken on state changes that might affect the return value /// of `send` method calls, such as space becoming available in the transmit /// buffer, or the socket closing. - /// + /// /// Notes: /// /// - Only one waker can be registered at a time. If another waker was previously registered, /// it is overwritten and will no longer be woken. - /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes. + /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes. /// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `send` has /// necessarily changed. #[cfg(feature = "async")] @@ -277,13 +277,13 @@ impl<'a> UdpSocket<'a> { true } - pub(crate) fn process(&mut self, ip_repr: &IpRepr, repr: &UdpRepr) -> Result<()> { + pub(crate) fn process(&mut self, ip_repr: &IpRepr, repr: &UdpRepr, payload: &[u8]) -> Result<()> { debug_assert!(self.accepts(ip_repr, repr)); - let size = repr.payload.len(); + let size = payload.len(); let endpoint = IpEndpoint { addr: ip_repr.src_addr(), port: repr.src_port }; - self.rx_buffer.enqueue(size, endpoint)?.copy_from_slice(repr.payload); + self.rx_buffer.enqueue(size, endpoint)?.copy_from_slice(payload); net_trace!("{}:{}:{}: receiving {} octets", self.meta.handle, self.endpoint, @@ -296,7 +296,7 @@ impl<'a> UdpSocket<'a> { } pub(crate) fn dispatch(&mut self, emit: F) -> Result<()> - where F: FnOnce((IpRepr, UdpRepr)) -> Result<()> { + where F: FnOnce((IpRepr, UdpRepr, &[u8])) -> Result<()> { let handle = self.handle(); let endpoint = self.endpoint; let hop_limit = self.hop_limit.unwrap_or(64); @@ -309,16 +309,15 @@ impl<'a> UdpSocket<'a> { let repr = UdpRepr { src_port: endpoint.port, dst_port: remote_endpoint.port, - payload: payload_buf, }; let ip_repr = IpRepr::Unspecified { src_addr: endpoint.addr, dst_addr: remote_endpoint.addr, protocol: IpProtocol::Udp, - payload_len: repr.buffer_len(), + payload_len: repr.header_len() + payload_buf.len(), hop_limit: hop_limit, }; - emit((ip_repr, repr)) + emit((ip_repr, repr, payload_buf)) })?; #[cfg(feature = "async")] @@ -379,15 +378,15 @@ mod test { const LOCAL_UDP_REPR: UdpRepr = UdpRepr { src_port: LOCAL_PORT, dst_port: REMOTE_PORT, - payload: b"abcdef" }; const REMOTE_UDP_REPR: UdpRepr = UdpRepr { src_port: REMOTE_PORT, dst_port: LOCAL_PORT, - payload: b"abcdef" }; + const PAYLOAD: &[u8] = b"abcdef"; + fn remote_ip_repr() -> IpRepr { match (MOCK_IP_ADDR_2, MOCK_IP_ADDR_1) { #[cfg(feature = "proto-ipv4")] @@ -457,16 +456,18 @@ mod test { assert_eq!(socket.send_slice(b"123456", REMOTE_END), Err(Error::Exhausted)); assert!(!socket.can_send()); - assert_eq!(socket.dispatch(|(ip_repr, udp_repr)| { + assert_eq!(socket.dispatch(|(ip_repr, udp_repr, payload)| { assert_eq!(ip_repr, LOCAL_IP_REPR); assert_eq!(udp_repr, LOCAL_UDP_REPR); + assert_eq!(payload, PAYLOAD); Err(Error::Unaddressable) }), Err(Error::Unaddressable)); assert!(!socket.can_send()); - assert_eq!(socket.dispatch(|(ip_repr, udp_repr)| { + assert_eq!(socket.dispatch(|(ip_repr, udp_repr, payload)| { assert_eq!(ip_repr, LOCAL_IP_REPR); assert_eq!(udp_repr, LOCAL_UDP_REPR); + assert_eq!(payload, PAYLOAD); Ok(()) }), Ok(())); assert!(socket.can_send()); @@ -481,12 +482,12 @@ mod test { assert_eq!(socket.recv(), Err(Error::Exhausted)); assert!(socket.accepts(&remote_ip_repr(), &REMOTE_UDP_REPR)); - assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR), + assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR, PAYLOAD), Ok(())); assert!(socket.can_recv()); assert!(socket.accepts(&remote_ip_repr(), &REMOTE_UDP_REPR)); - assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR), + assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR, PAYLOAD), Err(Error::Exhausted)); assert_eq!(socket.recv(), Ok((&b"abcdef"[..], REMOTE_END))); assert!(!socket.can_recv()); @@ -499,7 +500,7 @@ mod test { assert_eq!(socket.peek(), Err(Error::Exhausted)); - assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR), + assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR, PAYLOAD), Ok(())); assert_eq!(socket.peek(), Ok((&b"abcdef"[..], &REMOTE_END))); assert_eq!(socket.recv(), Ok((&b"abcdef"[..], REMOTE_END))); @@ -512,7 +513,7 @@ mod test { assert_eq!(socket.bind(LOCAL_PORT), Ok(())); assert!(socket.accepts(&remote_ip_repr(), &REMOTE_UDP_REPR)); - assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR), + assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR, PAYLOAD), Ok(())); let mut slice = [0; 4]; @@ -525,7 +526,7 @@ mod test { let mut socket = socket(buffer(1), buffer(0)); assert_eq!(socket.bind(LOCAL_PORT), Ok(())); - assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR), + assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR, PAYLOAD), Ok(())); let mut slice = [0; 4]; @@ -543,7 +544,7 @@ mod test { s.set_hop_limit(Some(0x2a)); assert_eq!(s.send_slice(b"abcdef", REMOTE_END), Ok(())); - assert_eq!(s.dispatch(|(ip_repr, _)| { + assert_eq!(s.dispatch(|(ip_repr, _, _)| { assert_eq!(ip_repr, IpRepr::Unspecified{ src_addr: MOCK_IP_ADDR_1, dst_addr: MOCK_IP_ADDR_2, @@ -619,9 +620,8 @@ mod test { let repr = UdpRepr { src_port: REMOTE_PORT, dst_port: LOCAL_PORT, - payload: &[] }; - assert_eq!(socket.process(&remote_ip_repr(), &repr), Ok(())); + assert_eq!(socket.process(&remote_ip_repr(), &repr, &[]), Ok(())); assert_eq!(socket.recv(), Ok((&[][..], REMOTE_END))); } } diff --git a/src/wire/ip.rs b/src/wire/ip.rs index bdfeee2..67a25e3 100644 --- a/src/wire/ip.rs +++ b/src/wire/ip.rs @@ -887,7 +887,7 @@ pub fn pretty_print_ip_payload>(f: &mut fmt::Formatter, indent: &m &repr.dst_addr(), &checksum_caps) { Err(err) => write!(f, "{}{} ({})", indent, udp_packet, err), Ok(udp_repr) => { - write!(f, "{}{}", indent, udp_repr)?; + write!(f, "{}{} len={}", indent, udp_repr, udp_packet.payload().len())?; let valid = udp_packet.verify_checksum(&repr.src_addr(), &repr.dst_addr()); format_checksum(f, valid) diff --git a/src/wire/udp.rs b/src/wire/udp.rs index f28b149..ef60ed2 100644 --- a/src/wire/udp.rs +++ b/src/wire/udp.rs @@ -201,16 +201,15 @@ impl> AsRef<[u8]> for Packet { /// A high-level representation of an User Datagram Protocol packet. #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Repr<'a> { +pub struct Repr { pub src_port: u16, pub dst_port: u16, - pub payload: &'a [u8] } -impl<'a> Repr<'a> { +impl Repr { /// Parse an User Datagram Protocol packet and return a high-level representation. - pub fn parse(packet: &Packet<&'a T>, src_addr: &IpAddress, dst_addr: &IpAddress, - checksum_caps: &ChecksumCapabilities) -> Result> + pub fn parse(packet: &Packet<&T>, src_addr: &IpAddress, dst_addr: &IpAddress, + checksum_caps: &ChecksumCapabilities) -> Result where T: AsRef<[u8]> + ?Sized { // Destination port cannot be omitted (but source port can be). if packet.dst_port() == 0 { return Err(Error::Malformed) } @@ -230,25 +229,26 @@ impl<'a> Repr<'a> { Ok(Repr { src_port: packet.src_port(), dst_port: packet.dst_port(), - payload: packet.payload() }) } /// Return the length of a packet that will be emitted from this high-level representation. - pub fn buffer_len(&self) -> usize { - field::CHECKSUM.end + self.payload.len() + pub fn header_len(&self) -> usize { + field::CHECKSUM.end } /// Emit a high-level representation into an User Datagram Protocol packet. pub fn emit(&self, packet: &mut Packet<&mut T>, src_addr: &IpAddress, dst_addr: &IpAddress, + payload_len: usize, + emit_payload: impl FnOnce(&mut [u8]), checksum_caps: &ChecksumCapabilities) where T: AsRef<[u8]> + AsMut<[u8]> { packet.set_src_port(self.src_port); packet.set_dst_port(self.dst_port); - packet.set_len((field::CHECKSUM.end + self.payload.len()) as u16); - packet.payload_mut().copy_from_slice(self.payload); + packet.set_len((field::CHECKSUM.end + payload_len) as u16); + emit_payload(packet.payload_mut()); if checksum_caps.udp.tx() { packet.fill_checksum(src_addr, dst_addr) @@ -268,10 +268,9 @@ impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Packet<&'a T> { } } -impl<'a> fmt::Display for Repr<'a> { +impl fmt::Display for Repr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "UDP src={} dst={} len={}", - self.src_port, self.dst_port, self.payload.len()) + write!(f, "UDP src={} dst={}", self.src_port, self.dst_port) } } @@ -361,11 +360,10 @@ mod test { } #[cfg(feature = "proto-ipv4")] - fn packet_repr() -> Repr<'static> { + fn packet_repr() -> Repr { Repr { src_port: 48896, dst_port: 53, - payload: &PAYLOAD_BYTES } } @@ -382,9 +380,11 @@ mod test { #[cfg(feature = "proto-ipv4")] fn test_emit() { let repr = packet_repr(); - let mut bytes = vec![0xa5; repr.buffer_len()]; + let mut bytes = vec![0xa5; repr.header_len() + PAYLOAD_BYTES.len()]; let mut packet = Packet::new_unchecked(&mut bytes); repr.emit(&mut packet, &SRC_ADDR.into(), &DST_ADDR.into(), + PAYLOAD_BYTES.len(), + |payload| payload.copy_from_slice(&PAYLOAD_BYTES), &ChecksumCapabilities::default()); assert_eq!(&packet.into_inner()[..], &PACKET_BYTES[..]); } From ef58fc67a42e8dd0fff6cafb06d56cbcaf2a5ec1 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 7 Apr 2021 00:47:41 +0200 Subject: [PATCH 02/12] route: add remove_default_ipvX_route --- src/iface/route.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/iface/route.rs b/src/iface/route.rs index 65e255b..e37c6a1 100644 --- a/src/iface/route.rs +++ b/src/iface/route.rs @@ -106,6 +106,24 @@ impl<'a> Routes<'a> { } } + /// Remove the default ipv4 gateway + /// + /// On success, returns the previous default route, if any. + #[cfg(feature = "proto-ipv4")] + pub fn remove_default_ipv4_route(&mut self) -> Option { + let cidr = IpCidr::new(IpAddress::v4(0, 0, 0, 0), 0); + self.storage.remove(&cidr) + } + + /// Remove the default ipv6 gateway + /// + /// On success, returns the previous default route, if any. + #[cfg(feature = "proto-ipv6")] + pub fn remove_default_ipv6_route(&mut self) -> Option { + let cidr = IpCidr::new(IpAddress::v6(0, 0, 0, 0, 0, 0, 0, 0), 0); + self.storage.remove(&cidr) + } + pub(crate) fn lookup(&self, addr: &IpAddress, timestamp: Instant) -> Option { assert!(addr.is_unicast()); From 0d53163c556223c751cc1334361dd80115de848a Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 7 Apr 2021 01:31:53 +0200 Subject: [PATCH 03/12] dhcp: convert to socket --- Cargo.toml | 5 +- examples/dhcp_client.rs | 103 +++++----- src/dhcp/clientv4.rs | 422 ---------------------------------------- src/dhcp/mod.rs | 5 - src/iface/interface.rs | 62 +++++- src/lib.rs | 2 - src/socket/dhcpv4.rs | 422 ++++++++++++++++++++++++++++++++++++++++ src/socket/mod.rs | 11 ++ src/socket/ref_.rs | 18 +- src/socket/set.rs | 3 + src/wire/dhcpv4.rs | 3 + src/wire/mod.rs | 7 +- src/wire/udp.rs | 2 + 13 files changed, 555 insertions(+), 510 deletions(-) delete mode 100644 src/dhcp/clientv4.rs delete mode 100644 src/dhcp/mod.rs create mode 100644 src/socket/dhcpv4.rs diff --git a/Cargo.toml b/Cargo.toml index ae47165..c0d2166 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,13 +39,14 @@ verbose = [] "phy-tuntap_interface" = ["std", "libc", "medium-ethernet"] "proto-ipv4" = [] "proto-igmp" = ["proto-ipv4"] -"proto-dhcpv4" = ["proto-ipv4", "socket-raw", "medium-ethernet"] +"proto-dhcpv4" = ["proto-ipv4"] "proto-ipv6" = [] "socket" = [] "socket-raw" = ["socket"] "socket-udp" = ["socket"] "socket-tcp" = ["socket"] "socket-icmp" = ["socket"] +"socket-dhcpv4" = ["socket", "medium-ethernet", "proto-dhcpv4"] "async" = [] defmt-trace = [] @@ -59,7 +60,7 @@ default = [ "medium-ethernet", "medium-ip", "phy-raw_socket", "phy-tuntap_interface", "proto-ipv4", "proto-igmp", "proto-dhcpv4", "proto-ipv6", - "socket-raw", "socket-icmp", "socket-udp", "socket-tcp", + "socket-raw", "socket-icmp", "socket-udp", "socket-tcp", "socket-dhcpv4", "async" ] diff --git a/examples/dhcp_client.rs b/examples/dhcp_client.rs index 81d685f..06b3080 100644 --- a/examples/dhcp_client.rs +++ b/examples/dhcp_client.rs @@ -3,12 +3,13 @@ mod utils; use std::collections::BTreeMap; use std::os::unix::io::AsRawFd; +use log::*; + use smoltcp::phy::{Device, Medium, wait as phy_wait}; use smoltcp::wire::{EthernetAddress, Ipv4Address, IpCidr, Ipv4Cidr}; -use smoltcp::iface::{NeighborCache, InterfaceBuilder, Routes}; -use smoltcp::socket::{SocketSet, RawSocketBuffer, RawPacketMetadata}; +use smoltcp::iface::{NeighborCache, InterfaceBuilder, Interface, Routes}; +use smoltcp::socket::{SocketSet, Dhcpv4Socket, Dhcpv4Event}; use smoltcp::time::Instant; -use smoltcp::dhcp::Dhcpv4Client; fn main() { #[cfg(feature = "log")] @@ -41,65 +42,53 @@ fn main() { let mut iface = builder.finalize(); let mut sockets = SocketSet::new(vec![]); - let dhcp_rx_buffer = RawSocketBuffer::new( - [RawPacketMetadata::EMPTY; 1], - vec![0; 900] - ); - let dhcp_tx_buffer = RawSocketBuffer::new( - [RawPacketMetadata::EMPTY; 1], - vec![0; 600] - ); - let mut dhcp = Dhcpv4Client::new(&mut sockets, dhcp_rx_buffer, dhcp_tx_buffer, Instant::now()); - let mut prev_cidr = Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0); + let dhcp_handle = sockets.add(Dhcpv4Socket::new()); + loop { let timestamp = Instant::now(); - iface.poll(&mut sockets, timestamp) - .map(|_| ()) - .unwrap_or_else(|e| println!("Poll: {:?}", e)); - let config = dhcp.poll(&mut iface, &mut sockets, timestamp) - .unwrap_or_else(|e| { - println!("DHCP: {:?}", e); - None - }); - config.map(|config| { - println!("DHCP config: {:?}", config); - if let Some(cidr) = config.address { - if cidr != prev_cidr { - iface.update_ip_addrs(|addrs| { - addrs.iter_mut().next() - .map(|addr| { - *addr = IpCidr::Ipv4(cidr); - }); - }); - prev_cidr = cidr; - println!("Assigned a new IPv4 address: {}", cidr); + if let Err(e) = iface.poll(&mut sockets, timestamp) { + debug!("poll error: {}", e); + } + + match sockets.get::(dhcp_handle).poll() { + Dhcpv4Event::NoChange => {} + Dhcpv4Event::Configured(config) => { + debug!("DHCP config acquired!"); + + debug!("IP address: {}", config.address); + set_ipv4_addr(&mut iface, config.address); + + if let Some(router) = config.router { + debug!("Default gateway: {}", router); + iface.routes_mut().add_default_ipv4_route(router).unwrap(); + } else { + debug!("Default gateway: None"); + iface.routes_mut().remove_default_ipv4_route(); + } + + for (i, s) in config.dns_servers.iter().enumerate() { + if let Some(s) = s { + debug!("DNS server {}: {}", i, s); + } } } - - config.router.map(|router| iface.routes_mut() - .add_default_ipv4_route(router) - .unwrap() - ); - iface.routes_mut() - .update(|routes_map| { - routes_map.get(&IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 0)) - .map(|default_route| { - println!("Default gateway: {}", default_route.via_router); - }); - }); - - if config.dns_servers.iter().any(|s| s.is_some()) { - println!("DNS servers:"); - for dns_server in config.dns_servers.iter().filter_map(|s| *s) { - println!("- {}", dns_server); - } + Dhcpv4Event::Deconfigured => { + debug!("DHCP lost config!"); + set_ipv4_addr(&mut iface, Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0)); + iface.routes_mut().remove_default_ipv4_route(); } - }); + } - let mut timeout = dhcp.next_poll(timestamp); - iface.poll_delay(&sockets, timestamp) - .map(|sockets_timeout| timeout = sockets_timeout); - phy_wait(fd, Some(timeout)) - .unwrap_or_else(|e| println!("Wait: {:?}", e)); + phy_wait(fd, iface.poll_delay(&sockets, timestamp)).expect("wait error"); } } + +fn set_ipv4_addr(iface: &mut Interface<'_, DeviceT>, cidr: Ipv4Cidr) + where DeviceT: for<'d> Device<'d> +{ + iface.update_ip_addrs(|addrs| { + let dest = addrs.iter_mut().next().unwrap(); + *dest = IpCidr::Ipv4(cidr); + }); +} + diff --git a/src/dhcp/clientv4.rs b/src/dhcp/clientv4.rs deleted file mode 100644 index 1679413..0000000 --- a/src/dhcp/clientv4.rs +++ /dev/null @@ -1,422 +0,0 @@ -use crate::{Error, Result}; -use crate::wire::{IpVersion, IpProtocol, IpEndpoint, IpAddress, - Ipv4Cidr, Ipv4Address, Ipv4Packet, Ipv4Repr, - UdpPacket, UdpRepr, - DhcpPacket, DhcpRepr, DhcpMessageType}; -use crate::wire::dhcpv4::{field as dhcpv4_field, Packet as Dhcpv4Packet}; -use crate::socket::{SocketSet, SocketHandle, RawSocket, RawSocketBuffer}; -use crate::phy::{Device, ChecksumCapabilities}; -use crate::iface::Interface; -use crate::time::{Instant, Duration}; -use super::{UDP_SERVER_PORT, UDP_CLIENT_PORT}; - -const DISCOVER_TIMEOUT: u64 = 10; -const REQUEST_TIMEOUT: u64 = 1; -const REQUEST_RETRIES: u16 = 15; -const DEFAULT_RENEW_INTERVAL: u32 = 60; -const PARAMETER_REQUEST_LIST: &[u8] = &[ - dhcpv4_field::OPT_SUBNET_MASK, - dhcpv4_field::OPT_ROUTER, - dhcpv4_field::OPT_DOMAIN_NAME_SERVER, -]; - -/// IPv4 configuration data returned by `client.poll()` -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Config { - pub address: Option, - pub router: Option, - pub dns_servers: [Option; 3], -} - -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -struct RequestState { - retry: u16, - endpoint_ip: Ipv4Address, - server_identifier: Ipv4Address, - requested_ip: Ipv4Address, -} - -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -struct RenewState { - endpoint_ip: Ipv4Address, - server_identifier: Ipv4Address, -} - -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -enum ClientState { - /// Discovering the DHCP server - Discovering, - /// Requesting an address - Requesting(RequestState), - /// Having an address, refresh it periodically - Renew(RenewState), -} - -pub struct Client { - state: ClientState, - raw_handle: SocketHandle, - /// When to send next request - next_egress: Instant, - /// When any existing DHCP address will expire. - lease_expiration: Option, - transaction_id: u32, -} - -/// DHCP client with a RawSocket. -/// -/// To provide memory for the dynamic IP address, configure your -/// `Interface` with one of `ip_addrs` and the `ipv4_gateway` being -/// `Ipv4Address::UNSPECIFIED`. You must also assign this `0.0.0.0/0` -/// while the client's state is `Discovering`. Hence, the `poll()` -/// method returns a corresponding `Config` struct in this case. -/// -/// You must call `dhcp_client.poll()` after `iface.poll()` to send -/// and receive DHCP packets. -impl Client { - /// # Usage - /// ```rust - /// use smoltcp::socket::{SocketSet, RawSocketBuffer, RawPacketMetadata}; - /// use smoltcp::dhcp::Dhcpv4Client; - /// use smoltcp::time::Instant; - /// - /// let mut sockets = SocketSet::new(vec![]); - /// let dhcp_rx_buffer = RawSocketBuffer::new( - /// [RawPacketMetadata::EMPTY; 1], - /// vec![0; 600] - /// ); - /// let dhcp_tx_buffer = RawSocketBuffer::new( - /// [RawPacketMetadata::EMPTY; 1], - /// vec![0; 600] - /// ); - /// let mut dhcp = Dhcpv4Client::new( - /// &mut sockets, - /// dhcp_rx_buffer, dhcp_tx_buffer, - /// Instant::now() - /// ); - /// ``` - pub fn new<'a>(sockets: &mut SocketSet<'a>, rx_buffer: RawSocketBuffer<'a>, tx_buffer: RawSocketBuffer<'a>, now: Instant) -> Self - { - let raw_socket = RawSocket::new(IpVersion::Ipv4, IpProtocol::Udp, rx_buffer, tx_buffer); - let raw_handle = sockets.add(raw_socket); - - Client { - state: ClientState::Discovering, - raw_handle, - next_egress: now, - transaction_id: 1, - lease_expiration: None, - } - } - - /// When to send next packet - /// - /// Useful for suspending execution after polling. - pub fn next_poll(&self, now: Instant) -> Duration { - self.next_egress - now - } - - /// Process incoming packets on the contained RawSocket, and send - /// DHCP requests when timeouts are ready. - /// - /// Applying the obtained network configuration is left to the - /// user. - /// - /// A Config can be returned from any valid DHCP reply. The client - /// performs no bookkeeping on configuration or their changes. - pub fn poll(&mut self, - iface: &mut Interface, sockets: &mut SocketSet, - now: Instant - ) -> Result> - where - DeviceT: for<'d> Device<'d>, - { - let checksum_caps = iface.device().capabilities().checksum; - let mut raw_socket = sockets.get::(self.raw_handle); - - // Process incoming - let config = { - match raw_socket.recv() - .and_then(|packet| parse_udp(packet, &checksum_caps)) { - Ok((IpEndpoint { - addr: IpAddress::Ipv4(src_ip), - port: UDP_SERVER_PORT, - }, IpEndpoint { - addr: _, - port: UDP_CLIENT_PORT, - }, payload)) => - self.ingress(iface, now, payload, &src_ip), - Ok(_) => - return Err(Error::Unrecognized), - Err(Error::Exhausted) => - None, - Err(e) => - return Err(e), - } - }; - - if config.is_some() { - // Return a new config immediately so that addresses can - // be configured that are required by egress(). - Ok(config) - } else { - // Send requests - if raw_socket.can_send() && now >= self.next_egress { - self.egress(iface, &mut *raw_socket, &checksum_caps, now) - } else { - Ok(None) - } - } - } - - fn ingress(&mut self, - iface: &mut Interface, now: Instant, - data: &[u8], src_ip: &Ipv4Address - ) -> Option - where - DeviceT: for<'d> Device<'d>, - { - let dhcp_packet = match DhcpPacket::new_checked(data) { - Ok(dhcp_packet) => dhcp_packet, - Err(e) => { - net_debug!("DHCP invalid pkt from {}: {:?}", src_ip, e); - return None; - } - }; - let dhcp_repr = match DhcpRepr::parse(&dhcp_packet) { - Ok(dhcp_repr) => dhcp_repr, - Err(e) => { - net_debug!("DHCP error parsing pkt from {}: {:?}", src_ip, e); - return None; - } - }; - let mac = iface.ethernet_addr(); - if dhcp_repr.client_hardware_address != mac { return None } - if dhcp_repr.transaction_id != self.transaction_id { return None } - let server_identifier = match dhcp_repr.server_identifier { - Some(server_identifier) => server_identifier, - None => return None, - }; - net_debug!("DHCP recv {:?} from {} ({})", dhcp_repr.message_type, src_ip, server_identifier); - - // once we receive the ack, we can pass the config to the user - let config = if dhcp_repr.message_type == DhcpMessageType::Ack { - let lease_duration = dhcp_repr.lease_duration.unwrap_or(DEFAULT_RENEW_INTERVAL * 2); - self.lease_expiration = Some(now + Duration::from_secs(lease_duration.into())); - - // RFC 2131 indicates clients should renew a lease halfway through its expiration. - self.next_egress = now + Duration::from_secs((lease_duration / 2).into()); - - let address = dhcp_repr.subnet_mask - .and_then(|mask| IpAddress::Ipv4(mask).to_prefix_len()) - .map(|prefix_len| Ipv4Cidr::new(dhcp_repr.your_ip, prefix_len)); - let router = dhcp_repr.router; - let dns_servers = dhcp_repr.dns_servers - .unwrap_or([None; 3]); - Some(Config { address, router, dns_servers }) - } else { - None - }; - - match self.state { - ClientState::Discovering - if dhcp_repr.message_type == DhcpMessageType::Offer => - { - self.next_egress = now; - let r_state = RequestState { - retry: 0, - endpoint_ip: *src_ip, - server_identifier, - requested_ip: dhcp_repr.your_ip // use the offered ip - }; - Some(ClientState::Requesting(r_state)) - } - ClientState::Requesting(ref r_state) - if dhcp_repr.message_type == DhcpMessageType::Ack && - server_identifier == r_state.server_identifier => - { - let p_state = RenewState { - endpoint_ip: *src_ip, - server_identifier, - }; - Some(ClientState::Renew(p_state)) - } - _ => None - }.map(|new_state| self.state = new_state); - - config - } - - fn egress Device<'d>>(&mut self, iface: &mut Interface, raw_socket: &mut RawSocket, checksum_caps: &ChecksumCapabilities, now: Instant) -> Result> { - // Reset after maximum amount of retries - let retries_exceeded = match self.state { - ClientState::Requesting(ref mut r_state) if r_state.retry >= REQUEST_RETRIES => { - net_debug!("DHCP request retries exceeded, restarting discovery"); - true - } - _ => false - }; - - let lease_expired = self.lease_expiration.map_or(false, |expiration| now >= expiration); - - if lease_expired || retries_exceeded { - self.reset(now); - // Return a config now so that user code assigns the - // 0.0.0.0/0 address, which will be used sending a DHCP - // discovery packet in the next call to egress(). - return Ok(Some(Config { - address: Some(Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0)), - router: None, - dns_servers: [None; 3], - })); - } - - // Prepare sending next packet - self.transaction_id += 1; - let mac = iface.ethernet_addr(); - - let mut dhcp_repr = DhcpRepr { - message_type: DhcpMessageType::Discover, - transaction_id: self.transaction_id, - client_hardware_address: mac, - client_ip: Ipv4Address::UNSPECIFIED, - your_ip: Ipv4Address::UNSPECIFIED, - server_ip: Ipv4Address::UNSPECIFIED, - router: None, - subnet_mask: None, - relay_agent_ip: Ipv4Address::UNSPECIFIED, - broadcast: true, - requested_ip: None, - client_identifier: Some(mac), - server_identifier: None, - parameter_request_list: Some(PARAMETER_REQUEST_LIST), - max_size: Some(raw_socket.payload_recv_capacity() as u16), - lease_duration: None, - dns_servers: None, - }; - let mut send_packet = |iface, endpoint, dhcp_repr| { - send_packet(iface, raw_socket, &endpoint, &dhcp_repr, checksum_caps) - .map(|()| None) - }; - - - match self.state { - ClientState::Discovering => { - self.next_egress = now + Duration::from_secs(DISCOVER_TIMEOUT); - let endpoint = IpEndpoint { - addr: Ipv4Address::BROADCAST.into(), - port: UDP_SERVER_PORT, - }; - net_trace!("DHCP send discover to {}: {:?}", endpoint, dhcp_repr); - send_packet(iface, endpoint, dhcp_repr) - } - ClientState::Requesting(ref mut r_state) => { - r_state.retry += 1; - self.next_egress = now + Duration::from_secs(REQUEST_TIMEOUT); - - let endpoint = IpEndpoint { - addr: Ipv4Address::BROADCAST.into(), - port: UDP_SERVER_PORT, - }; - dhcp_repr.message_type = DhcpMessageType::Request; - dhcp_repr.broadcast = false; - dhcp_repr.requested_ip = Some(r_state.requested_ip); - dhcp_repr.server_identifier = Some(r_state.server_identifier); - net_trace!("DHCP send request to {} = {:?}", endpoint, dhcp_repr); - send_packet(iface, endpoint, dhcp_repr) - } - ClientState::Renew(ref mut p_state) => { - self.next_egress = now + Duration::from_secs(DEFAULT_RENEW_INTERVAL.into()); - - let endpoint = IpEndpoint { - addr: p_state.endpoint_ip.into(), - port: UDP_SERVER_PORT, - }; - let client_ip = iface.ipv4_addr().unwrap_or(Ipv4Address::UNSPECIFIED); - dhcp_repr.message_type = DhcpMessageType::Request; - dhcp_repr.client_ip = client_ip; - dhcp_repr.broadcast = false; - net_trace!("DHCP send renew to {}: {:?}", endpoint, dhcp_repr); - send_packet(iface, endpoint, dhcp_repr) - } - } - } - - /// Reset state and restart discovery phase. - /// - /// Use this to speed up acquisition of an address in a new - /// network if a link was down and it is now back up. - /// - /// You *must* configure a `0.0.0.0` address on your interface - /// before the next call to `poll()`! - pub fn reset(&mut self, now: Instant) { - net_trace!("DHCP reset"); - self.state = ClientState::Discovering; - self.next_egress = now; - self.lease_expiration = None; - } -} - -fn send_packet Device<'d>>(iface: &mut Interface, raw_socket: &mut RawSocket, endpoint: &IpEndpoint, dhcp_repr: &DhcpRepr, checksum_caps: &ChecksumCapabilities) -> Result<()> { - - let udp_repr = UdpRepr { - src_port: UDP_CLIENT_PORT, - dst_port: endpoint.port, - }; - - let src_addr = iface.ipv4_addr().unwrap(); - let dst_addr = match endpoint.addr { - IpAddress::Ipv4(addr) => addr, - _ => return Err(Error::Illegal), - }; - let ipv4_repr = Ipv4Repr { - src_addr, - dst_addr, - protocol: IpProtocol::Udp, - payload_len: udp_repr.header_len() + dhcp_repr.buffer_len(), - hop_limit: 64, - }; - - let mut packet = raw_socket.send( - ipv4_repr.buffer_len() + udp_repr.header_len() + dhcp_repr.buffer_len() - )?; - { - let mut ipv4_packet = Ipv4Packet::new_unchecked(&mut packet); - ipv4_repr.emit(&mut ipv4_packet, &checksum_caps); - } - { - let mut udp_packet = UdpPacket::new_unchecked( - &mut packet[ipv4_repr.buffer_len()..] - ); - udp_repr.emit(&mut udp_packet, - &src_addr.into(), &dst_addr.into(), - dhcp_repr.buffer_len(), - |buf| dhcp_repr.emit(&mut Dhcpv4Packet::new_unchecked(buf)).unwrap(), - checksum_caps); - } - Ok(()) -} - -fn parse_udp<'a>(data: &'a [u8], checksum_caps: &ChecksumCapabilities) -> Result<(IpEndpoint, IpEndpoint, &'a [u8])> { - let ipv4_packet = Ipv4Packet::new_checked(data)?; - let ipv4_repr = Ipv4Repr::parse(&ipv4_packet, &checksum_caps)?; - let udp_packet = UdpPacket::new_checked(ipv4_packet.payload())?; - let udp_repr = UdpRepr::parse( - &udp_packet, - &ipv4_repr.src_addr.into(), &ipv4_repr.dst_addr.into(), - checksum_caps - )?; - let src = IpEndpoint { - addr: ipv4_repr.src_addr.into(), - port: udp_repr.src_port, - }; - let dst = IpEndpoint { - addr: ipv4_repr.dst_addr.into(), - port: udp_repr.dst_port, - }; - let data = udp_packet.payload(); - Ok((src, dst, data)) -} diff --git a/src/dhcp/mod.rs b/src/dhcp/mod.rs deleted file mode 100644 index 2c1b47a..0000000 --- a/src/dhcp/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub const UDP_SERVER_PORT: u16 = 67; -pub const UDP_CLIENT_PORT: u16 = 68; - -mod clientv4; -pub use self::clientv4::{Client as Dhcpv4Client, Config as Dhcpv4Config}; diff --git a/src/iface/interface.rs b/src/iface/interface.rs index d569067..e1acc58 100644 --- a/src/iface/interface.rs +++ b/src/iface/interface.rs @@ -265,7 +265,9 @@ pub(crate) enum IpPacket<'a> { #[cfg(feature = "socket-udp")] Udp((IpRepr, UdpRepr, &'a [u8])), #[cfg(feature = "socket-tcp")] - Tcp((IpRepr, TcpRepr<'a>)) + Tcp((IpRepr, TcpRepr<'a>)), + #[cfg(feature = "socket-dhcpv4")] + Dhcpv4((Ipv4Repr, UdpRepr, DhcpRepr<'a>)), } impl<'a> IpPacket<'a> { @@ -283,6 +285,8 @@ impl<'a> IpPacket<'a> { IpPacket::Udp((ip_repr, _, _)) => ip_repr.clone(), #[cfg(feature = "socket-tcp")] IpPacket::Tcp((ip_repr, _)) => ip_repr.clone(), + #[cfg(feature = "socket-dhcpv4")] + IpPacket::Dhcpv4((ipv4_repr, _, _)) => IpRepr::Ipv4(*ipv4_repr), } } @@ -331,6 +335,13 @@ impl<'a> IpPacket<'a> { &_ip_repr.src_addr(), &_ip_repr.dst_addr(), &caps.checksum); } + #[cfg(feature = "socket-dhcpv4")] + IpPacket::Dhcpv4((_, udp_repr, dhcp_repr)) => + udp_repr.emit(&mut UdpPacket::new_unchecked(payload), + &_ip_repr.src_addr(), &_ip_repr.dst_addr(), + dhcp_repr.buffer_len(), + |buf| dhcp_repr.emit(&mut DhcpPacket::new_unchecked(buf)).unwrap(), + &caps.checksum), } } } @@ -662,6 +673,14 @@ impl<'a, DeviceT> Interface<'a, DeviceT> }) } + let _ip_mtu = match _caps.medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => _caps.max_transmission_unit - EthernetFrame::<&[u8]>::header_len(), + #[cfg(feature = "medium-ip")] + Medium::Ip => _caps.max_transmission_unit, + }; + + let socket_result = match *socket { #[cfg(feature = "socket-raw")] @@ -687,15 +706,14 @@ impl<'a, DeviceT> Interface<'a, DeviceT> respond!(IpPacket::Udp(response))), #[cfg(feature = "socket-tcp")] Socket::Tcp(ref mut socket) => { - let ip_mtu = match _caps.medium { - #[cfg(feature = "medium-ethernet")] - Medium::Ethernet => _caps.max_transmission_unit - EthernetFrame::<&[u8]>::header_len(), - #[cfg(feature = "medium-ip")] - Medium::Ip => _caps.max_transmission_unit, - }; - socket.dispatch(timestamp, ip_mtu, |response| + socket.dispatch(timestamp, _ip_mtu, |response| respond!(IpPacket::Tcp(response))) } + #[cfg(feature = "socket-dhcpv4")] + Socket::Dhcpv4(ref mut socket) => + // todo don't unwrap + socket.dispatch(timestamp, inner.ethernet_addr.unwrap(), _ip_mtu, |response| + respond!(IpPacket::Dhcpv4(response))), }; match (device_result, socket_result) { @@ -1063,6 +1081,34 @@ impl<'a> InterfaceInner<'a> { #[cfg(not(feature = "socket-raw"))] let handled_by_raw_socket = false; + + #[cfg(feature = "socket-dhcpv4")] + { + if ipv4_repr.protocol == IpProtocol::Udp && self.ethernet_addr.is_some() { + // First check for source and dest ports, then do `UdpRepr::parse` if they match. + // This way we avoid validating the UDP checksum twice for all non-DHCP UDP packets (one here, one in `process_udp`) + let udp_packet = UdpPacket::new_checked(ip_payload)?; + if udp_packet.src_port() == DHCP_SERVER_PORT && udp_packet.dst_port() == DHCP_CLIENT_PORT { + if let Some(mut dhcp_socket) = sockets.iter_mut().filter_map(Dhcpv4Socket::downcast).next() { + let (src_addr, dst_addr) = (ip_repr.src_addr(), ip_repr.dst_addr()); + let checksum_caps = self.device_capabilities.checksum.clone(); + let udp_repr = UdpRepr::parse(&udp_packet, &src_addr, &dst_addr, &checksum_caps)?; + let udp_payload = udp_packet.payload(); + + // NOTE(unwrap): we checked for is_some above. + let ethernet_addr = self.ethernet_addr.unwrap(); + + match dhcp_socket.process(timestamp, ethernet_addr, &ipv4_repr, &udp_repr, udp_payload) { + // The packet is valid and handled by socket. + Ok(()) => return Ok(None), + // The packet is malformed, or the socket buffer is full. + Err(e) => return Err(e) + } + } + } + } + } + if !self.has_ip_addr(ipv4_repr.dst_addr) && !self.has_multicast_group(ipv4_repr.dst_addr) && !self.is_broadcast_v4(ipv4_repr.dst_addr) { diff --git a/src/lib.rs b/src/lib.rs index 0fda13b..028edf8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,8 +121,6 @@ pub mod iface; #[cfg(feature = "socket")] pub mod socket; pub mod time; -#[cfg(feature = "proto-dhcpv4")] -pub mod dhcp; /// The error type for the networking stack. #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/src/socket/dhcpv4.rs b/src/socket/dhcpv4.rs new file mode 100644 index 0000000..8137ff6 --- /dev/null +++ b/src/socket/dhcpv4.rs @@ -0,0 +1,422 @@ +use crate::{Error, Result}; +use crate::wire::{EthernetAddress, IpProtocol, IpAddress, + Ipv4Cidr, Ipv4Address, Ipv4Repr, + UdpRepr, UDP_HEADER_LEN, + DhcpPacket, DhcpRepr, DhcpMessageType, DHCP_CLIENT_PORT, DHCP_SERVER_PORT}; +use crate::wire::dhcpv4::{field as dhcpv4_field}; +use crate::socket::SocketMeta; +use crate::time::{Instant, Duration}; + +use super::{PollAt, Socket}; + +const DISCOVER_TIMEOUT: Duration = Duration::from_secs(10); + +const REQUEST_TIMEOUT: Duration = Duration::from_secs(1); +const REQUEST_RETRIES: u16 = 15; + +const MIN_RENEW_TIMEOUT: Duration = Duration::from_secs(60); + +const DEFAULT_LEASE_DURATION: u32 = 120; + +const PARAMETER_REQUEST_LIST: &[u8] = &[ + dhcpv4_field::OPT_SUBNET_MASK, + dhcpv4_field::OPT_ROUTER, + dhcpv4_field::OPT_DOMAIN_NAME_SERVER, +]; + +/// IPv4 configuration data provided by the DHCP server. +#[derive(Debug, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Config { + /// IP address + pub address: Ipv4Cidr, + /// Router address, also known as default gateway. Does not necessarily + /// match the DHCP server's address. + pub router: Option, + /// DNS servers + pub dns_servers: [Option; 3], +} + +/// Information on how to reach a DHCP server. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct ServerInfo { + /// IP address to use as destination in outgoing packets + address: Ipv4Address, + /// Server identifier to use in outgoing packets. Usually equal to server_address, + /// but may differ in some situations (eg DHCP relays) + identifier: Ipv4Address, +} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct DiscoverState { + /// When to send next request + retry_at: Instant, +} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct RequestState { + /// When to send next request + retry_at: Instant, + /// How many retries have been done + retry: u16, + /// Server we're trying to request from + server: ServerInfo, + /// IP address that we're trying to request. + requested_ip: Ipv4Address, +} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct RenewState { + /// Server that gave us the lease + server: ServerInfo, + /// Active networkc config + config: Config, + + /// Renew timer. When reached, we will start attempting + /// to renew this lease with the DHCP server. + /// Must be less or equal than `expires_at`. + renew_at: Instant, + /// Expiration timer. When reached, this lease is no longer valid, so it must be + /// thrown away and the ethernet interface deconfigured. + expires_at: Instant, +} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum ClientState { + /// Discovering the DHCP server + Discovering(DiscoverState), + /// Requesting an address + Requesting(RequestState), + /// Having an address, refresh it periodically. + Renewing(RenewState), +} + +/// Return value for the `Dhcpv4Socket::poll` function +pub enum Event<'a> { + /// No change has occured to the configuration. + NoChange, + /// Configuration has been lost (for example, the lease has expired) + Deconfigured, + /// Configuration has been newly acquired, or modified. + Configured(&'a Config), +} + +#[derive(Debug)] +pub struct Dhcpv4Socket { + pub(crate) meta: SocketMeta, + /// State of the DHCP client. + state: ClientState, + /// Set to true on config/state change, cleared back to false by the `config` function. + config_changed: bool, + /// xid of the last sent message. + transaction_id: u32, +} + +/// DHCP client socket. +/// +/// The socket acquires an IP address configuration through DHCP autonomously. +/// You must query the configuration with `.poll()` after every call to `Interface::poll()`, +/// and apply the configuration to the `Interface`. +impl Dhcpv4Socket { + /// Create a DHCPv4 socket + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Dhcpv4Socket { + meta: SocketMeta::default(), + state: ClientState::Discovering(DiscoverState{ + retry_at: Instant::from_millis(0), + }), + config_changed: true, + transaction_id: 1, + } + } + + pub(crate) fn poll_at(&self) -> PollAt { + let t = match &self.state { + ClientState::Discovering(state) => state.retry_at, + ClientState::Requesting(state) => state.retry_at, + ClientState::Renewing(state) => state.renew_at.min(state.expires_at), + }; + PollAt::Time(t) + } + + pub(crate) fn process(&mut self, now: Instant, ethernet_addr: EthernetAddress, ip_repr: &Ipv4Repr, repr: &UdpRepr, payload: &[u8]) -> Result<()> { + let src_ip = ip_repr.src_addr; + + if repr.src_port != DHCP_SERVER_PORT || repr.dst_port != DHCP_CLIENT_PORT { + return Ok(()) + } + + let dhcp_packet = match DhcpPacket::new_checked(payload) { + Ok(dhcp_packet) => dhcp_packet, + Err(e) => { + net_debug!("DHCP invalid pkt from {}: {:?}", src_ip, e); + return Ok(()); + } + }; + let dhcp_repr = match DhcpRepr::parse(&dhcp_packet) { + Ok(dhcp_repr) => dhcp_repr, + Err(e) => { + net_debug!("DHCP error parsing pkt from {}: {:?}", src_ip, e); + return Ok(()); + } + }; + if dhcp_repr.client_hardware_address != ethernet_addr { return Ok(()) } + if dhcp_repr.transaction_id != self.transaction_id { return Ok(()) } + let server_identifier = match dhcp_repr.server_identifier { + Some(server_identifier) => server_identifier, + None => { + net_debug!("DHCP ignoring {:?} because missing server_identifier", dhcp_repr.message_type); + return Ok(()); + } + }; + + net_debug!("DHCP recv {:?} from {} ({})", dhcp_repr.message_type, src_ip, server_identifier); + + match (&mut self.state, dhcp_repr.message_type){ + (ClientState::Discovering(_state), DhcpMessageType::Offer) => { + if !dhcp_repr.your_ip.is_unicast() { + net_debug!("DHCP ignoring OFFER because your_ip is not unicast"); + return Ok(()) + } + + self.state = ClientState::Requesting(RequestState { + retry_at: now, + retry: 0, + server: ServerInfo { + address: src_ip, + identifier: server_identifier, + }, + requested_ip: dhcp_repr.your_ip // use the offered ip + }); + } + (ClientState::Requesting(state), DhcpMessageType::Ack) => { + if let Some((config, renew_at, expires_at)) = Self::parse_ack(now, ip_repr, &dhcp_repr) { + self.config_changed = true; + self.state = ClientState::Renewing(RenewState{ + server: state.server, + config, + renew_at, + expires_at, + }); + } + } + (ClientState::Renewing(state), DhcpMessageType::Ack) => { + if let Some((config, renew_at, expires_at)) = Self::parse_ack(now, ip_repr, &dhcp_repr) { + state.renew_at = renew_at; + state.expires_at = expires_at; + if state.config != config { + self.config_changed = true; + state.config = config; + } + } + } + _ => { + net_debug!("DHCP ignoring {:?}: unexpected in current state", dhcp_repr.message_type); + } + } + + Ok(()) + } + + fn parse_ack(now: Instant, _ip_repr: &Ipv4Repr, dhcp_repr: &DhcpRepr) -> Option<(Config, Instant, Instant)> { + let subnet_mask = match dhcp_repr.subnet_mask { + Some(subnet_mask) => subnet_mask, + None => { + net_debug!("DHCP ignoring ACK because missing subnet_mask"); + return None + } + }; + + let prefix_len = match IpAddress::Ipv4(subnet_mask).to_prefix_len() { + Some(prefix_len) => prefix_len, + None => { + net_debug!("DHCP ignoring ACK because subnet_mask is not a valid mask"); + return None + } + }; + + if !dhcp_repr.your_ip.is_unicast() { + net_debug!("DHCP ignoring ACK because your_ip is not unicast"); + return None + } + + let lease_duration = dhcp_repr.lease_duration.unwrap_or(DEFAULT_LEASE_DURATION); + + let config = Config{ + address: Ipv4Cidr::new(dhcp_repr.your_ip, prefix_len), + router: dhcp_repr.router, + dns_servers: dhcp_repr.dns_servers.unwrap_or([None; 3]), + }; + + // RFC 2131 indicates clients should renew a lease halfway through its expiration. + let renew_at = now + Duration::from_secs((lease_duration / 2).into()); + let expires_at = now + Duration::from_secs(lease_duration.into()); + + Some((config, renew_at, expires_at)) + } + + pub(crate) fn dispatch(&mut self, now: Instant, ethernet_addr: EthernetAddress, ip_mtu: usize, emit: F) -> Result<()> + where F: FnOnce((Ipv4Repr, UdpRepr, DhcpRepr)) -> Result<()> { + + // Worst case biggest IPv4 header length. + // 0x0f * 4 = 60 bytes. + const MAX_IPV4_HEADER_LEN: usize = 60; + + // We don't directly increment transaction_id because sending the packet + // may fail. We only want to update state after succesfully sending. + let next_transaction_id = self.transaction_id + 1; + + let mut dhcp_repr = DhcpRepr { + message_type: DhcpMessageType::Discover, + transaction_id: next_transaction_id, + client_hardware_address: ethernet_addr, + client_ip: Ipv4Address::UNSPECIFIED, + your_ip: Ipv4Address::UNSPECIFIED, + server_ip: Ipv4Address::UNSPECIFIED, + router: None, + subnet_mask: None, + relay_agent_ip: Ipv4Address::UNSPECIFIED, + broadcast: true, + requested_ip: None, + client_identifier: Some(ethernet_addr), + server_identifier: None, + parameter_request_list: Some(PARAMETER_REQUEST_LIST), + max_size: Some((ip_mtu - MAX_IPV4_HEADER_LEN - UDP_HEADER_LEN) as u16), + lease_duration: None, + dns_servers: None, + }; + + let udp_repr = UdpRepr { + src_port: DHCP_CLIENT_PORT, + dst_port: DHCP_SERVER_PORT, + }; + + let mut ipv4_repr = Ipv4Repr { + src_addr: Ipv4Address::UNSPECIFIED, + dst_addr: Ipv4Address::BROADCAST, + protocol: IpProtocol::Udp, + payload_len: 0, // filled right before emit + hop_limit: 64, + }; + + match &mut self.state { + ClientState::Discovering(state) => { + if now < state.retry_at { + return Err(Error::Exhausted) + } + + // send packet + net_debug!("DHCP send DISCOVER to {}: {:?}", ipv4_repr.dst_addr, dhcp_repr); + ipv4_repr.payload_len = udp_repr.header_len() + dhcp_repr.buffer_len(); + emit((ipv4_repr, udp_repr, dhcp_repr))?; + + // Update state AFTER the packet has been successfully sent. + state.retry_at = now + DISCOVER_TIMEOUT; + self.transaction_id = next_transaction_id; + Ok(()) + } + ClientState::Requesting(state) => { + if now < state.retry_at { + return Err(Error::Exhausted) + } + + if state.retry >= REQUEST_RETRIES { + net_debug!("DHCP request retries exceeded, restarting discovery"); + self.reset(); + // return Ok so we get polled again + return Ok(()) + } + + dhcp_repr.message_type = DhcpMessageType::Request; + dhcp_repr.broadcast = false; + dhcp_repr.requested_ip = Some(state.requested_ip); + dhcp_repr.server_identifier = Some(state.server.identifier); + + net_debug!("DHCP send request to {}: {:?}", ipv4_repr.dst_addr, dhcp_repr); + ipv4_repr.payload_len = udp_repr.header_len() + dhcp_repr.buffer_len(); + emit((ipv4_repr, udp_repr, dhcp_repr))?; + + // Exponential backoff + state.retry_at = now + REQUEST_TIMEOUT; + state.retry += 1; + + self.transaction_id = next_transaction_id; + Ok(()) + } + ClientState::Renewing(state) => { + if state.expires_at <= now { + net_debug!("DHCP lease expired"); + self.reset(); + // return Ok so we get polled again + return Ok(()) + } + + if now < state.renew_at { + return Err(Error::Exhausted) + } + + ipv4_repr.src_addr = state.config.address.address(); + ipv4_repr.dst_addr = state.server.address; + dhcp_repr.message_type = DhcpMessageType::Request; + dhcp_repr.client_ip = state.config.address.address(); + dhcp_repr.broadcast = false; + + net_debug!("DHCP send renew to {}: {:?}", ipv4_repr.dst_addr, dhcp_repr); + ipv4_repr.payload_len = udp_repr.header_len() + dhcp_repr.buffer_len(); + emit((ipv4_repr, udp_repr, dhcp_repr))?; + + // In both RENEWING and REBINDING states, if the client receives no + // response to its DHCPREQUEST message, the client SHOULD wait one-half + // of the remaining time until T2 (in RENEWING state) and one-half of + // the remaining lease time (in REBINDING state), down to a minimum of + // 60 seconds, before retransmitting the DHCPREQUEST message. + state.renew_at = now + MIN_RENEW_TIMEOUT.max((state.expires_at - now) / 2); + + self.transaction_id = next_transaction_id; + Ok(()) + } + } + } + + /// Reset state and restart discovery phase. + /// + /// Use this to speed up acquisition of an address in a new + /// network if a link was down and it is now back up. + pub fn reset(&mut self) { + net_trace!("DHCP reset"); + if let ClientState::Renewing(_) = &self.state { + self.config_changed = true; + } + self.state = ClientState::Discovering(DiscoverState{ + retry_at: Instant::from_millis(0), + }); + } + + /// Query the socket for configuration changes. + /// + /// The socket has an internal "configuration changed" flag. If + /// set, this function returns the configuration and resets the flag. + pub fn poll(&mut self) -> Event<'_> { + if !self.config_changed { + Event::NoChange + } else if let ClientState::Renewing(state) = &self.state { + self.config_changed = false; + Event::Configured(&state.config) + } else { + self.config_changed = false; + Event::Deconfigured + } + } +} + +impl<'a> Into> for Dhcpv4Socket { + fn into(self) -> Socket<'a> { + Socket::Dhcpv4(self) + } +} diff --git a/src/socket/mod.rs b/src/socket/mod.rs index 76fe30f..ebd0c42 100644 --- a/src/socket/mod.rs +++ b/src/socket/mod.rs @@ -22,6 +22,8 @@ mod icmp; mod udp; #[cfg(feature = "socket-tcp")] mod tcp; +#[cfg(feature = "socket-dhcpv4")] +mod dhcpv4; mod set; mod ref_; @@ -53,6 +55,9 @@ pub use self::tcp::{SocketBuffer as TcpSocketBuffer, State as TcpState, TcpSocket}; +#[cfg(feature = "socket-dhcpv4")] +pub use self::dhcpv4::{Dhcpv4Socket, Config as Dhcpv4Config, Event as Dhcpv4Event}; + pub use self::set::{Set as SocketSet, Item as SocketSetItem, Handle as SocketHandle}; pub use self::set::{Iter as SocketSetIter, IterMut as SocketSetIterMut}; @@ -91,6 +96,8 @@ pub enum Socket<'a> { Udp(UdpSocket<'a>), #[cfg(feature = "socket-tcp")] Tcp(TcpSocket<'a>), + #[cfg(feature = "socket-dhcpv4")] + Dhcpv4(Dhcpv4Socket), } macro_rules! dispatch_socket { @@ -110,6 +117,8 @@ macro_rules! dispatch_socket { &$( $mut_ )* Socket::Udp(ref $( $mut_ )* $socket) => $code, #[cfg(feature = "socket-tcp")] &$( $mut_ )* Socket::Tcp(ref $( $mut_ )* $socket) => $code, + #[cfg(feature = "socket-dhcpv4")] + &$( $mut_ )* Socket::Dhcpv4(ref $( $mut_ )* $socket) => $code, } }; } @@ -169,3 +178,5 @@ from_socket!(IcmpSocket<'a>, Icmp); from_socket!(UdpSocket<'a>, Udp); #[cfg(feature = "socket-tcp")] from_socket!(TcpSocket<'a>, Tcp); +#[cfg(feature = "socket-dhcpv4")] +from_socket!(Dhcpv4Socket, Dhcpv4); diff --git a/src/socket/ref_.rs b/src/socket/ref_.rs index 9e030ff..3a52a77 100644 --- a/src/socket/ref_.rs +++ b/src/socket/ref_.rs @@ -1,13 +1,5 @@ use core::ops::{Deref, DerefMut}; -#[cfg(feature = "socket-raw")] -use crate::socket::RawSocket; -#[cfg(all(feature = "socket-icmp", any(feature = "proto-ipv4", feature = "proto-ipv6")))] -use crate::socket::IcmpSocket; -#[cfg(feature = "socket-udp")] -use crate::socket::UdpSocket; -#[cfg(feature = "socket-tcp")] -use crate::socket::TcpSocket; /// A trait for tracking a socket usage session. /// @@ -20,13 +12,15 @@ pub trait Session { } #[cfg(feature = "socket-raw")] -impl<'a> Session for RawSocket<'a> {} +impl<'a> Session for crate::socket::RawSocket<'a> {} #[cfg(all(feature = "socket-icmp", any(feature = "proto-ipv4", feature = "proto-ipv6")))] -impl<'a> Session for IcmpSocket<'a> {} +impl<'a> Session for crate::socket::IcmpSocket<'a> {} #[cfg(feature = "socket-udp")] -impl<'a> Session for UdpSocket<'a> {} +impl<'a> Session for crate::socket::UdpSocket<'a> {} #[cfg(feature = "socket-tcp")] -impl<'a> Session for TcpSocket<'a> {} +impl<'a> Session for crate::socket::TcpSocket<'a> {} +#[cfg(feature = "socket-dhcpv4")] +impl Session for crate::socket::Dhcpv4Socket {} /// A smart pointer to a socket. /// diff --git a/src/socket/set.rs b/src/socket/set.rs index 6866656..74af317 100644 --- a/src/socket/set.rs +++ b/src/socket/set.rs @@ -156,6 +156,9 @@ impl<'a> Set<'a> { } else { socket.close() }, + #[cfg(feature = "socket-dhcpv4")] + Socket::Dhcpv4(_) => + may_remove = true, } } if may_remove { diff --git a/src/wire/dhcpv4.rs b/src/wire/dhcpv4.rs index a938943..0e3bfdd 100644 --- a/src/wire/dhcpv4.rs +++ b/src/wire/dhcpv4.rs @@ -6,6 +6,9 @@ use crate::{Error, Result}; use crate::wire::{EthernetAddress, Ipv4Address}; use crate::wire::arp::Hardware; +pub const SERVER_PORT: u16 = 67; +pub const CLIENT_PORT: u16 = 68; + const DHCP_MAGIC_NUMBER: u32 = 0x63825363; enum_with_unknown! { diff --git a/src/wire/mod.rs b/src/wire/mod.rs index 4fee202..ca1f244 100644 --- a/src/wire/mod.rs +++ b/src/wire/mod.rs @@ -211,7 +211,8 @@ pub use self::mld::{AddressRecord as MldAddressRecord, Repr as MldRepr}; pub use self::udp::{Packet as UdpPacket, - Repr as UdpRepr}; + Repr as UdpRepr, + HEADER_LEN as UDP_HEADER_LEN}; pub use self::tcp::{SeqNumber as TcpSeqNumber, Packet as TcpPacket, @@ -222,4 +223,6 @@ pub use self::tcp::{SeqNumber as TcpSeqNumber, #[cfg(feature = "proto-dhcpv4")] pub use self::dhcpv4::{Packet as DhcpPacket, Repr as DhcpRepr, - MessageType as DhcpMessageType}; + MessageType as DhcpMessageType, + CLIENT_PORT as DHCP_CLIENT_PORT, + SERVER_PORT as DHCP_SERVER_PORT}; diff --git a/src/wire/udp.rs b/src/wire/udp.rs index ef60ed2..ad1c1a2 100644 --- a/src/wire/udp.rs +++ b/src/wire/udp.rs @@ -28,6 +28,8 @@ mod field { } } +pub const HEADER_LEN: usize = field::CHECKSUM.end; + #[allow(clippy::len_without_is_empty)] impl> Packet { /// Imbue a raw octet buffer with UDP packet structure. From cb075bfc16034bd12d7b9c6394586080d7d0d5f2 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 7 Apr 2021 01:32:04 +0200 Subject: [PATCH 04/12] dhcp: handle NAK packets --- src/socket/dhcpv4.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/socket/dhcpv4.rs b/src/socket/dhcpv4.rs index 8137ff6..fa26e96 100644 --- a/src/socket/dhcpv4.rs +++ b/src/socket/dhcpv4.rs @@ -206,6 +206,9 @@ impl Dhcpv4Socket { }); } } + (ClientState::Requesting(_), DhcpMessageType::Nak) => { + self.reset(); + } (ClientState::Renewing(state), DhcpMessageType::Ack) => { if let Some((config, renew_at, expires_at)) = Self::parse_ack(now, ip_repr, &dhcp_repr) { state.renew_at = renew_at; @@ -216,6 +219,9 @@ impl Dhcpv4Socket { } } } + (ClientState::Renewing(_), DhcpMessageType::Nak) => { + self.reset(); + } _ => { net_debug!("DHCP ignoring {:?}: unexpected in current state", dhcp_repr.message_type); } From b1f2bcb6786e3ce347f3b91e5288d850d7679df5 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 7 Apr 2021 21:52:26 +0200 Subject: [PATCH 05/12] dhcp: retry REQUEST slower and with exponential backoff. Fixes #464 --- src/socket/dhcpv4.rs | 10 ++++++---- src/time.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/socket/dhcpv4.rs b/src/socket/dhcpv4.rs index fa26e96..84e7ade 100644 --- a/src/socket/dhcpv4.rs +++ b/src/socket/dhcpv4.rs @@ -11,8 +11,10 @@ use super::{PollAt, Socket}; const DISCOVER_TIMEOUT: Duration = Duration::from_secs(10); -const REQUEST_TIMEOUT: Duration = Duration::from_secs(1); -const REQUEST_RETRIES: u16 = 15; +// timeout doubles every 2 tries. +// total time 5 + 5 + 10 + 10 + 20 = 50s +const REQUEST_TIMEOUT: Duration = Duration::from_secs(5); +const REQUEST_RETRIES: u16 = 5; const MIN_RENEW_TIMEOUT: Duration = Duration::from_secs(60); @@ -348,8 +350,8 @@ impl Dhcpv4Socket { ipv4_repr.payload_len = udp_repr.header_len() + dhcp_repr.buffer_len(); emit((ipv4_repr, udp_repr, dhcp_repr))?; - // Exponential backoff - state.retry_at = now + REQUEST_TIMEOUT; + // Exponential backoff: Double every 2 retries. + state.retry_at = now + (REQUEST_TIMEOUT << (state.retry as u32 / 2)); state.retry += 1; self.transaction_id = next_transaction_id; diff --git a/src/time.rs b/src/time.rs index 0dc1c79..724f804 100644 --- a/src/time.rs +++ b/src/time.rs @@ -232,6 +232,34 @@ impl ops::DivAssign for Duration { } } +impl ops::Shl for Duration { + type Output = Duration; + + fn shl(self, rhs: u32) -> Duration { + Duration::from_millis(self.millis << rhs) + } +} + +impl ops::ShlAssign for Duration { + fn shl_assign(&mut self, rhs: u32) { + self.millis <<= rhs; + } +} + +impl ops::Shr for Duration { + type Output = Duration; + + fn shr(self, rhs: u32) -> Duration { + Duration::from_millis(self.millis >> rhs) + } +} + +impl ops::ShrAssign for Duration { + fn shr_assign(&mut self, rhs: u32) { + self.millis >>= rhs; + } +} + impl From<::core::time::Duration> for Duration { fn from(other: ::core::time::Duration) -> Duration { Duration::from_millis( From 53b62cfbad78d15981291e0495408cd311e73fbc Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Thu, 8 Apr 2021 01:33:04 +0200 Subject: [PATCH 06/12] dhcp: remove 0.0.0.0s from the DNS serevr list. tp-link routers pad the DNS server list with 0.0.0.0 to a fixed size :( --- src/socket/dhcpv4.rs | 21 ++++++++++++++++++--- src/wire/dhcpv4.rs | 5 +++-- src/wire/mod.rs | 3 ++- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/socket/dhcpv4.rs b/src/socket/dhcpv4.rs index 84e7ade..ca5bc38 100644 --- a/src/socket/dhcpv4.rs +++ b/src/socket/dhcpv4.rs @@ -2,7 +2,7 @@ use crate::{Error, Result}; use crate::wire::{EthernetAddress, IpProtocol, IpAddress, Ipv4Cidr, Ipv4Address, Ipv4Repr, UdpRepr, UDP_HEADER_LEN, - DhcpPacket, DhcpRepr, DhcpMessageType, DHCP_CLIENT_PORT, DHCP_SERVER_PORT}; + DhcpPacket, DhcpRepr, DhcpMessageType, DHCP_CLIENT_PORT, DHCP_SERVER_PORT, DHCP_MAX_DNS_SERVER_COUNT}; use crate::wire::dhcpv4::{field as dhcpv4_field}; use crate::socket::SocketMeta; use crate::time::{Instant, Duration}; @@ -36,7 +36,7 @@ pub struct Config { /// match the DHCP server's address. pub router: Option, /// DNS servers - pub dns_servers: [Option; 3], + pub dns_servers: [Option; DHCP_MAX_DNS_SERVER_COUNT], } /// Information on how to reach a DHCP server. @@ -256,10 +256,25 @@ impl Dhcpv4Socket { let lease_duration = dhcp_repr.lease_duration.unwrap_or(DEFAULT_LEASE_DURATION); + // Cleanup the DNS servers list, keeping only unicasts/ + // TP-Link TD-W8970 sends 0.0.0.0 as second DNS server if there's only one configured :( + let mut dns_servers = [None; DHCP_MAX_DNS_SERVER_COUNT]; + if let Some(received) = dhcp_repr.dns_servers { + let mut i = 0; + for addr in received.iter() { + if let Some(addr) = addr{ + if addr.is_unicast() { + // This can never be out-of-bounds since both arrays have length DHCP_MAX_DNS_SERVER_COUNT + dns_servers[i] = Some(*addr); + i += 1; + } + } + } + } let config = Config{ address: Ipv4Cidr::new(dhcp_repr.your_ip, prefix_len), router: dhcp_repr.router, - dns_servers: dhcp_repr.dns_servers.unwrap_or([None; 3]), + dns_servers: dns_servers }; // RFC 2131 indicates clients should renew a lease halfway through its expiration. diff --git a/src/wire/dhcpv4.rs b/src/wire/dhcpv4.rs index 0e3bfdd..7ef39d9 100644 --- a/src/wire/dhcpv4.rs +++ b/src/wire/dhcpv4.rs @@ -8,6 +8,7 @@ use crate::wire::arp::Hardware; pub const SERVER_PORT: u16 = 67; pub const CLIENT_PORT: u16 = 68; +pub const MAX_DNS_SERVER_COUNT: usize = 3; const DHCP_MAGIC_NUMBER: u32 = 0x63825363; @@ -686,7 +687,7 @@ pub struct Repr<'a> { /// the client is interested in. pub parameter_request_list: Option<&'a [u8]>, /// DNS servers - pub dns_servers: Option<[Option; 3]>, + pub dns_servers: Option<[Option; MAX_DNS_SERVER_COUNT]>, /// The maximum size dhcp packet the interface can receive pub max_size: Option, /// The DHCP IP lease duration, specified in seconds. @@ -780,7 +781,7 @@ impl<'a> Repr<'a> { parameter_request_list = Some(data); } DhcpOption::Other {kind: field::OPT_DOMAIN_NAME_SERVER, data} => { - let mut servers = [None; 3]; + let mut servers = [None; MAX_DNS_SERVER_COUNT]; for (server, chunk) in servers.iter_mut().zip(data.chunks(4)) { *server = Some(Ipv4Address::from_bytes(chunk)); } diff --git a/src/wire/mod.rs b/src/wire/mod.rs index ca1f244..7fb37b5 100644 --- a/src/wire/mod.rs +++ b/src/wire/mod.rs @@ -225,4 +225,5 @@ pub use self::dhcpv4::{Packet as DhcpPacket, Repr as DhcpRepr, MessageType as DhcpMessageType, CLIENT_PORT as DHCP_CLIENT_PORT, - SERVER_PORT as DHCP_SERVER_PORT}; + SERVER_PORT as DHCP_SERVER_PORT, + MAX_DNS_SERVER_COUNT as DHCP_MAX_DNS_SERVER_COUNT}; From a916888ab883af612cc17a0bd11dcb4a1607b0ff Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 13 Apr 2021 20:23:28 +0200 Subject: [PATCH 07/12] dhcp: add max_lease_duration option --- examples/dhcp_client.rs | 12 ++++++++++-- src/socket/dhcpv4.rs | 27 ++++++++++++++++++++++----- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/examples/dhcp_client.rs b/examples/dhcp_client.rs index 06b3080..78fd562 100644 --- a/examples/dhcp_client.rs +++ b/examples/dhcp_client.rs @@ -5,7 +5,7 @@ use std::collections::BTreeMap; use std::os::unix::io::AsRawFd; use log::*; -use smoltcp::phy::{Device, Medium, wait as phy_wait}; +use smoltcp::{phy::{Device, Medium, wait as phy_wait}, time::Duration}; use smoltcp::wire::{EthernetAddress, Ipv4Address, IpCidr, Ipv4Cidr}; use smoltcp::iface::{NeighborCache, InterfaceBuilder, Interface, Routes}; use smoltcp::socket::{SocketSet, Dhcpv4Socket, Dhcpv4Event}; @@ -42,7 +42,15 @@ fn main() { let mut iface = builder.finalize(); let mut sockets = SocketSet::new(vec![]); - let dhcp_handle = sockets.add(Dhcpv4Socket::new()); + let mut dhcp_socket = Dhcpv4Socket::new(); + + // Set a ridiculously short max lease time to show DHCP renews work properly. + // This will cause the DHCP client to start renewing after 5 seconds, and give up the + // lease after 10 seconds if renew hasn't succeeded. + // IMPORTANT: This should be removed in production. + dhcp_socket.set_max_lease_duration(Some(Duration::from_secs(10))); + + let dhcp_handle = sockets.add(dhcp_socket); loop { let timestamp = Instant::now(); diff --git a/src/socket/dhcpv4.rs b/src/socket/dhcpv4.rs index ca5bc38..f89526d 100644 --- a/src/socket/dhcpv4.rs +++ b/src/socket/dhcpv4.rs @@ -117,6 +117,10 @@ pub struct Dhcpv4Socket { config_changed: bool, /// xid of the last sent message. transaction_id: u32, + + /// Max lease duration. If set, it sets a maximum cap to the server-provided lease duration. + /// Useful to react faster to IP configuration changes and to test whether renews work correctly. + max_lease_duration: Option, } /// DHCP client socket. @@ -135,9 +139,18 @@ impl Dhcpv4Socket { }), config_changed: true, transaction_id: 1, + max_lease_duration: None, } } + pub fn max_lease_duration(&self) -> Option { + self.max_lease_duration + } + + pub fn set_max_lease_duration(&mut self, max_lease_duration: Option) { + self.max_lease_duration = max_lease_duration; + } + pub(crate) fn poll_at(&self) -> PollAt { let t = match &self.state { ClientState::Discovering(state) => state.retry_at, @@ -198,7 +211,7 @@ impl Dhcpv4Socket { }); } (ClientState::Requesting(state), DhcpMessageType::Ack) => { - if let Some((config, renew_at, expires_at)) = Self::parse_ack(now, ip_repr, &dhcp_repr) { + if let Some((config, renew_at, expires_at)) = Self::parse_ack(now, ip_repr, &dhcp_repr, self.max_lease_duration) { self.config_changed = true; self.state = ClientState::Renewing(RenewState{ server: state.server, @@ -212,7 +225,7 @@ impl Dhcpv4Socket { self.reset(); } (ClientState::Renewing(state), DhcpMessageType::Ack) => { - if let Some((config, renew_at, expires_at)) = Self::parse_ack(now, ip_repr, &dhcp_repr) { + if let Some((config, renew_at, expires_at)) = Self::parse_ack(now, ip_repr, &dhcp_repr, self.max_lease_duration) { state.renew_at = renew_at; state.expires_at = expires_at; if state.config != config { @@ -232,7 +245,7 @@ impl Dhcpv4Socket { Ok(()) } - fn parse_ack(now: Instant, _ip_repr: &Ipv4Repr, dhcp_repr: &DhcpRepr) -> Option<(Config, Instant, Instant)> { + fn parse_ack(now: Instant, _ip_repr: &Ipv4Repr, dhcp_repr: &DhcpRepr, max_lease_duration: Option) -> Option<(Config, Instant, Instant)> { let subnet_mask = match dhcp_repr.subnet_mask { Some(subnet_mask) => subnet_mask, None => { @@ -255,6 +268,10 @@ impl Dhcpv4Socket { } let lease_duration = dhcp_repr.lease_duration.unwrap_or(DEFAULT_LEASE_DURATION); + let mut lease_duration = Duration::from_secs(lease_duration as _); + if let Some(max_lease_duration) = max_lease_duration { + lease_duration = lease_duration.min(max_lease_duration); + } // Cleanup the DNS servers list, keeping only unicasts/ // TP-Link TD-W8970 sends 0.0.0.0 as second DNS server if there's only one configured :( @@ -278,8 +295,8 @@ impl Dhcpv4Socket { }; // RFC 2131 indicates clients should renew a lease halfway through its expiration. - let renew_at = now + Duration::from_secs((lease_duration / 2).into()); - let expires_at = now + Duration::from_secs(lease_duration.into()); + let renew_at = now + lease_duration / 2; + let expires_at = now + lease_duration; Some((config, renew_at, expires_at)) } From 284f5bc83448af192728fea06398f39e10e95466 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 28 May 2021 17:47:02 +0200 Subject: [PATCH 08/12] wire/udp: clearer HEADER_LEN usage --- src/wire/udp.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/wire/udp.rs b/src/wire/udp.rs index ad1c1a2..4943349 100644 --- a/src/wire/udp.rs +++ b/src/wire/udp.rs @@ -57,13 +57,13 @@ impl> Packet { /// [set_len]: #method.set_len pub fn check_len(&self) -> Result<()> { let buffer_len = self.buffer.as_ref().len(); - if buffer_len < field::CHECKSUM.end { + if buffer_len < HEADER_LEN { Err(Error::Truncated) } else { let field_len = self.len() as usize; if buffer_len < field_len { Err(Error::Truncated) - } else if field_len < field::CHECKSUM.end { + } else if field_len < HEADER_LEN { Err(Error::Malformed) } else { Ok(()) @@ -234,9 +234,9 @@ impl Repr { }) } - /// Return the length of a packet that will be emitted from this high-level representation. + /// Return the length of the packet header that will be emitted from this high-level representation. pub fn header_len(&self) -> usize { - field::CHECKSUM.end + HEADER_LEN } /// Emit a high-level representation into an User Datagram Protocol packet. @@ -249,7 +249,7 @@ impl Repr { where T: AsRef<[u8]> + AsMut<[u8]> { packet.set_src_port(self.src_port); packet.set_dst_port(self.dst_port); - packet.set_len((field::CHECKSUM.end + payload_len) as u16); + packet.set_len((HEADER_LEN + payload_len) as u16); emit_payload(packet.payload_mut()); if checksum_caps.udp.tx() { From 743f9de0391c5e552de43f2a0e37a2bd41f91af5 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 28 May 2021 18:23:50 +0200 Subject: [PATCH 09/12] dhcp: address review comments. --- examples/dhcp_client.rs | 6 +++--- src/socket/dhcpv4.rs | 23 ++++++++++------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/examples/dhcp_client.rs b/examples/dhcp_client.rs index 78fd562..d274cae 100644 --- a/examples/dhcp_client.rs +++ b/examples/dhcp_client.rs @@ -59,8 +59,8 @@ fn main() { } match sockets.get::(dhcp_handle).poll() { - Dhcpv4Event::NoChange => {} - Dhcpv4Event::Configured(config) => { + None => {} + Some(Dhcpv4Event::Configured(config)) => { debug!("DHCP config acquired!"); debug!("IP address: {}", config.address); @@ -80,7 +80,7 @@ fn main() { } } } - Dhcpv4Event::Deconfigured => { + Some(Dhcpv4Event::Deconfigured) => { debug!("DHCP lost config!"); set_ipv4_addr(&mut iface, Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0)); iface.routes_mut().remove_default_ipv4_route(); diff --git a/src/socket/dhcpv4.rs b/src/socket/dhcpv4.rs index f89526d..1f278e6 100644 --- a/src/socket/dhcpv4.rs +++ b/src/socket/dhcpv4.rs @@ -18,7 +18,7 @@ const REQUEST_RETRIES: u16 = 5; const MIN_RENEW_TIMEOUT: Duration = Duration::from_secs(60); -const DEFAULT_LEASE_DURATION: u32 = 120; +const DEFAULT_LEASE_DURATION: Duration = Duration::from_secs(120); const PARAMETER_REQUEST_LIST: &[u8] = &[ dhcpv4_field::OPT_SUBNET_MASK, @@ -75,7 +75,7 @@ struct RequestState { struct RenewState { /// Server that gave us the lease server: ServerInfo, - /// Active networkc config + /// Active network config config: Config, /// Renew timer. When reached, we will start attempting @@ -100,8 +100,6 @@ enum ClientState { /// Return value for the `Dhcpv4Socket::poll` function pub enum Event<'a> { - /// No change has occured to the configuration. - NoChange, /// Configuration has been lost (for example, the lease has expired) Deconfigured, /// Configuration has been newly acquired, or modified. @@ -211,7 +209,7 @@ impl Dhcpv4Socket { }); } (ClientState::Requesting(state), DhcpMessageType::Ack) => { - if let Some((config, renew_at, expires_at)) = Self::parse_ack(now, ip_repr, &dhcp_repr, self.max_lease_duration) { + if let Some((config, renew_at, expires_at)) = Self::parse_ack(now, &dhcp_repr, self.max_lease_duration) { self.config_changed = true; self.state = ClientState::Renewing(RenewState{ server: state.server, @@ -225,7 +223,7 @@ impl Dhcpv4Socket { self.reset(); } (ClientState::Renewing(state), DhcpMessageType::Ack) => { - if let Some((config, renew_at, expires_at)) = Self::parse_ack(now, ip_repr, &dhcp_repr, self.max_lease_duration) { + if let Some((config, renew_at, expires_at)) = Self::parse_ack(now, &dhcp_repr, self.max_lease_duration) { state.renew_at = renew_at; state.expires_at = expires_at; if state.config != config { @@ -245,7 +243,7 @@ impl Dhcpv4Socket { Ok(()) } - fn parse_ack(now: Instant, _ip_repr: &Ipv4Repr, dhcp_repr: &DhcpRepr, max_lease_duration: Option) -> Option<(Config, Instant, Instant)> { + fn parse_ack(now: Instant, dhcp_repr: &DhcpRepr, max_lease_duration: Option) -> Option<(Config, Instant, Instant)> { let subnet_mask = match dhcp_repr.subnet_mask { Some(subnet_mask) => subnet_mask, None => { @@ -267,8 +265,7 @@ impl Dhcpv4Socket { return None } - let lease_duration = dhcp_repr.lease_duration.unwrap_or(DEFAULT_LEASE_DURATION); - let mut lease_duration = Duration::from_secs(lease_duration as _); + let mut lease_duration = dhcp_repr.lease_duration.map(|d| Duration::from_secs(d as _)).unwrap_or(DEFAULT_LEASE_DURATION); if let Some(max_lease_duration) = max_lease_duration { lease_duration = lease_duration.min(max_lease_duration); } @@ -442,15 +439,15 @@ impl Dhcpv4Socket { /// /// The socket has an internal "configuration changed" flag. If /// set, this function returns the configuration and resets the flag. - pub fn poll(&mut self) -> Event<'_> { + pub fn poll(&mut self) -> Option> { if !self.config_changed { - Event::NoChange + None } else if let ClientState::Renewing(state) = &self.state { self.config_changed = false; - Event::Configured(&state.config) + Some(Event::Configured(&state.config)) } else { self.config_changed = false; - Event::Deconfigured + Some(Event::Deconfigured) } } } From 07c3a402a3527d8e150ab6b9f27740f5dbf79e26 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 28 May 2021 18:26:08 +0200 Subject: [PATCH 10/12] Remove unused macro_use --- src/macros.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/macros.rs b/src/macros.rs index 63c7ca7..f241871 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -12,7 +12,6 @@ macro_rules! net_log { } #[cfg(not(any(feature = "log", feature = "defmt")))] -#[macro_use] macro_rules! net_log { ($level:ident, $($arg:expr),*) => { $( let _ = $arg; )* } } From a81f2fef56421a82b030160df9c548aa74254477 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 28 May 2021 18:43:52 +0200 Subject: [PATCH 11/12] dhcp: convert port check to hard assert. --- src/socket/dhcpv4.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/socket/dhcpv4.rs b/src/socket/dhcpv4.rs index 1f278e6..cc61373 100644 --- a/src/socket/dhcpv4.rs +++ b/src/socket/dhcpv4.rs @@ -161,9 +161,8 @@ impl Dhcpv4Socket { pub(crate) fn process(&mut self, now: Instant, ethernet_addr: EthernetAddress, ip_repr: &Ipv4Repr, repr: &UdpRepr, payload: &[u8]) -> Result<()> { let src_ip = ip_repr.src_addr; - if repr.src_port != DHCP_SERVER_PORT || repr.dst_port != DHCP_CLIENT_PORT { - return Ok(()) - } + // This is enforced in interface.rs. + assert!(repr.src_port == DHCP_SERVER_PORT && repr.dst_port == DHCP_CLIENT_PORT); let dhcp_packet = match DhcpPacket::new_checked(payload) { Ok(dhcp_packet) => dhcp_packet, From 8f1e547b6704b8af83801c67d86944070ffa3d97 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 28 May 2021 18:49:07 +0200 Subject: [PATCH 12/12] Test with defmt-trace --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8de0b17..91eccf2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -73,9 +73,9 @@ jobs: include: # defmt doesn't support 1.40 - rust: stable - features: defmt medium-ip medium-ethernet proto-ipv6 proto-ipv6 proto-igmp proto-dhcpv4 socket-raw socket-udp socket-tcp socket-icmp async + features: defmt defmt-trace medium-ip medium-ethernet proto-ipv6 proto-ipv6 proto-igmp proto-dhcpv4 socket-raw socket-udp socket-tcp socket-icmp async - rust: nightly - features: defmt medium-ip medium-ethernet proto-ipv6 proto-ipv6 proto-igmp proto-dhcpv4 socket-raw socket-udp socket-tcp socket-icmp async + features: defmt defmt-trace medium-ip medium-ethernet proto-ipv6 proto-ipv6 proto-igmp proto-dhcpv4 socket-raw socket-udp socket-tcp socket-icmp async steps: - uses: actions/checkout@v2