From f4ff603b8e9cbddba913f48114d38adc7c695bbc Mon Sep 17 00:00:00 2001 From: longjin Date: Fri, 4 Sep 2026 07:45:57 +0000 Subject: [PATCH] feat(iface): support runtime link configuration Add bounded runtime IP MTU updates so cached interface capabilities, TCP MSS calculations, and native fragmentation observe administrative MTU changes without rebuilding the interface. Preserve the device capability ceiling and reject unsafe values atomically. Add a runtime neighbor-discovery mode for DragonOS IFF_NOARP integration. Dynamic ARP and NDISC learning is disabled while Linux-compatible IPv6 solicitation replies, explicit permanent neighbors, direct device-address fallback, readiness reporting, and fragment invalidation remain coherent across mode transitions. Cover MTU boundaries, dynamic cache invalidation, ARP/NDISC behavior, permanent-neighbor priority, and pending-fragment transitions. Signed-off-by: longjin --- src/iface/interface/ipv4.rs | 4 + src/iface/interface/ipv6.rs | 16 ++- src/iface/interface/mod.rs | 105 ++++++++++++++++-- src/iface/interface/tests/ipv4.rs | 174 ++++++++++++++++++++++++++++++ src/iface/interface/tests/ipv6.rs | 94 ++++++++++++++++ src/iface/interface/tests/mod.rs | 63 +++++++++++ src/iface/mod.rs | 4 +- 7 files changed, 449 insertions(+), 11 deletions(-) diff --git a/src/iface/interface/ipv4.rs b/src/iface/interface/ipv4.rs index da1aee9d9..0ffd5c0c7 100644 --- a/src/iface/interface/ipv4.rs +++ b/src/iface/interface/ipv4.rs @@ -260,6 +260,10 @@ impl InterfaceInner { timestamp: Instant, eth_frame: &EthernetFrame<&'frame [u8]>, ) -> Option> { + if !self.neighbor_discovery_enabled { + return None; + } + let arp_packet = check!(ArpPacket::new_checked(eth_frame.payload())); let arp_repr = check!(ArpRepr::parse(&arp_packet)); diff --git a/src/iface/interface/ipv6.rs b/src/iface/interface/ipv6.rs index 61b738d0d..a50b325f3 100644 --- a/src/iface/interface/ipv6.rs +++ b/src/iface/interface/ipv6.rs @@ -455,6 +455,10 @@ impl InterfaceInner { target_addr, flags, } => { + if !self.neighbor_discovery_enabled { + return None; + } + let ip_addr = ip_repr.src_addr.into(); if let Some(lladdr) = lladdr { let lladdr = check!(lladdr.parse(self.caps.medium)); @@ -474,13 +478,19 @@ impl InterfaceInner { lladdr, .. } => { + if !target_addr.x_is_unicast() { + return None; + } + if let Some(lladdr) = lladdr { let lladdr = check!(lladdr.parse(self.caps.medium)); - if !lladdr.is_unicast() || !target_addr.x_is_unicast() { + if !lladdr.is_unicast() { return None; } - self.neighbor_cache - .fill(ip_repr.src_addr.into(), lladdr, self.now); + if self.neighbor_discovery_enabled { + self.neighbor_cache + .fill(ip_repr.src_addr.into(), lladdr, self.now); + } } if self.has_solicited_node(ip_repr.dst_addr) && self.has_ip_addr(target_addr) { diff --git a/src/iface/interface/mod.rs b/src/iface/interface/mod.rs index d8228e492..d06a8b89e 100644 --- a/src/iface/interface/mod.rs +++ b/src/iface/interface/mod.rs @@ -120,6 +120,23 @@ pub enum Ipv4PacketDispatchError { NeighborPending { retry_at: Instant }, } +/// Error returned when an IP MTU cannot be used by the interface. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum IpMtuError { + /// The IP MTU is below the minimum supported by the stack. + TooSmall, + /// The requested MTU exceeds the device capability captured at creation. + TooLarge, + /// Adding the link-layer header to the IP MTU overflowed `usize`. + FrameSizeOverflow, +} + +// IPv4 requires hosts to accept datagrams of at least 68 bytes. This also keeps +// every currently supported transport/header calculation non-negative (the +// largest one is DHCPv4's 60-byte IPv4 header plus its 8-byte UDP header). +const MIN_IP_MTU: usize = 68; + /// A network interface. /// /// The network interface logically owns a number of other data structures; to avoid @@ -140,11 +157,14 @@ pub struct Interface { /// exclusively). However, it is still possible to call methods on its `inner` field. pub struct InterfaceInner { pub caps: DeviceCapabilities, + max_frame_mtu: usize, pub now: Instant, rand: Rand, #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] neighbor_cache: NeighborCache, + #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] + neighbor_discovery_enabled: bool, hardware_addr: HardwareAddress, #[cfg(feature = "medium-ieee802154")] sequence_no: u8, @@ -209,6 +229,7 @@ impl Interface { /// the medium of the device. pub fn new(config: Config, device: &mut (impl Device + ?Sized), now: Instant) -> Self { let caps = device.capabilities(); + let max_frame_mtu = caps.max_transmission_unit; assert_eq!( config.hardware_addr.medium(), caps.medium, @@ -263,6 +284,7 @@ impl Interface { inner: InterfaceInner { now, caps, + max_frame_mtu, hardware_addr: config.hardware_addr, ip_addrs: Vec::new(), any_ip: false, @@ -270,6 +292,8 @@ impl Interface { routes: Routes::new(), #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] neighbor_cache: NeighborCache::new(), + #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] + neighbor_discovery_enabled: true, #[cfg(feature = "multicast")] multicast: multicast::State::new(), #[cfg(feature = "medium-ieee802154")] @@ -336,6 +360,63 @@ impl Interface { self.inner.hardware_addr = addr; } + /// Set the IP-layer maximum transmission unit used by this interface. + /// + /// The cached device capabilities are updated immediately, so socket MSS + /// calculation and packet fragmentation observe the new value without + /// reconstructing the interface. A pending native IPv4 fragmentation + /// sequence re-evaluates this value for every continuation fragment. + /// Route-selected egress overrides intentionally retain their own MTU. + pub fn set_ip_mtu(&mut self, ip_mtu: usize) -> Result<(), IpMtuError> { + if ip_mtu < MIN_IP_MTU { + return Err(IpMtuError::TooSmall); + } + + let frame_mtu = match self.inner.caps.medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => ip_mtu + .checked_add(EthernetFrame::<&[u8]>::header_len()) + .ok_or(IpMtuError::FrameSizeOverflow)?, + #[cfg(feature = "medium-ip")] + Medium::Ip => ip_mtu, + #[cfg(feature = "medium-ieee802154")] + Medium::Ieee802154 => ip_mtu, + }; + if frame_mtu > self.inner.max_frame_mtu { + return Err(IpMtuError::TooLarge); + } + self.inner.caps.max_transmission_unit = frame_mtu; + Ok(()) + } + + /// Return whether dynamic link-layer neighbor discovery is enabled. + #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] + pub fn neighbor_discovery_enabled(&self) -> bool { + self.inner.neighbor_discovery_enabled + } + + /// Enable or disable dynamic ARP/NDISC neighbor discovery. + /// + /// Disabling discovery drops dynamically learned mappings and rate-limit + /// state. Unknown unicast neighbors then use the interface hardware address + /// directly, matching the Linux `IFF_NOARP` neighbor initialization model, + /// without transmitting discovery packets. Explicit hardware addresses + /// supplied by an integration remain authoritative. + /// Changing modes drops any pending fragmentation sequence because it may + /// have cached a link-layer destination selected under the previous mode. + #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] + pub fn set_neighbor_discovery_enabled(&mut self, enabled: bool) { + if self.inner.neighbor_discovery_enabled == enabled { + return; + } + self.inner.neighbor_discovery_enabled = enabled; + if !enabled { + self.inner.neighbor_cache.flush(); + } + #[cfg(feature = "_proto-fragmentation")] + self.fragmenter.reset(); + } + /// Get the IP addresses of the interface. pub fn ip_addrs(&self) -> &[IpCidr] { self.inner.ip_addrs.as_ref() @@ -516,10 +597,12 @@ impl Interface { return false; } self.inner.now = timestamp; - self.inner - .neighbor_cache - .lookup(&protocol_addr, timestamp) - .found() + !self.inner.neighbor_discovery_enabled + || self + .inner + .neighbor_cache + .lookup(&protocol_addr, timestamp) + .found() } /// Invalidate one dynamically learned neighbor and its discovery @@ -1131,9 +1214,15 @@ impl InterfaceInner { match self.route(addr, self.now) { Some(_routed_addr) => match self.caps.medium { #[cfg(feature = "medium-ethernet")] - Medium::Ethernet => self.neighbor_cache.lookup(&_routed_addr, self.now).found(), + Medium::Ethernet => { + !self.neighbor_discovery_enabled + || self.neighbor_cache.lookup(&_routed_addr, self.now).found() + } #[cfg(feature = "medium-ieee802154")] - Medium::Ieee802154 => self.neighbor_cache.lookup(&_routed_addr, self.now).found(), + Medium::Ieee802154 => { + !self.neighbor_discovery_enabled + || self.neighbor_cache.lookup(&_routed_addr, self.now).found() + } #[cfg(feature = "medium-ip")] Medium::Ip => true, }, @@ -1226,6 +1315,10 @@ impl InterfaceInner { return Ok((hardware_addr, tx_token)); } + if !self.neighbor_discovery_enabled { + return Ok((self.hardware_addr, tx_token)); + } + match self.neighbor_cache.lookup(next_hop, self.now) { NeighborAnswer::Found(hardware_addr) => return Ok((hardware_addr, tx_token)), NeighborAnswer::RateLimited => return Err(DispatchError::NeighborPending), diff --git a/src/iface/interface/tests/ipv4.rs b/src/iface/interface/tests/ipv4.rs index 718ab360c..8193cb365 100644 --- a/src/iface/interface/tests/ipv4.rs +++ b/src/iface/interface/tests/ipv4.rs @@ -37,6 +37,7 @@ impl TxToken for CapturingTxToken { #[cfg(feature = "medium-ethernet")] fn explicit_ipv4_dispatch_uses_supplied_next_hop_neighbor() { let (mut iface, _, _) = setup(Medium::Ethernet); + iface.set_neighbor_discovery_enabled(false); let frames = std::rc::Rc::new(core::cell::RefCell::new(Vec::new())); let destination_mac = EthernetAddress::from_bytes(&[0x02, 0, 0, 0, 0, 9]); let packet = serialized_ipv4_packet(Ipv4Address::new(198, 51, 100, 7)); @@ -113,6 +114,89 @@ fn explicit_ipv4_dispatch_emits_rate_limited_arp_for_missing_neighbor() { assert!(iface.is_neighbor_resolved(Instant::from_millis(20), IpAddress::Ipv4(next_hop))); } +#[test] +#[cfg(feature = "medium-ethernet")] +fn disabled_neighbor_discovery_flushes_cache_and_uses_direct_hardware_address() { + let (mut iface, _, _) = setup(Medium::Ethernet); + let frames = std::rc::Rc::new(core::cell::RefCell::new(Vec::new())); + let next_hop = Ipv4Address::new(192, 168, 1, 99); + let learned_mac = EthernetAddress::from_bytes(&[0x02, 0, 0, 0, 0, 99]); + let local_mac = iface.hardware_addr().ethernet_or_panic(); + let packet = serialized_ipv4_packet(Ipv4Address::new(198, 51, 100, 7)); + iface.inner.neighbor_cache.fill( + IpAddress::Ipv4(next_hop), + HardwareAddress::Ethernet(learned_mac), + Instant::from_millis(1), + ); + + iface.set_neighbor_discovery_enabled(false); + assert!(!iface.neighbor_discovery_enabled()); + assert!(iface.inner.has_neighbor(&IpAddress::Ipv4(next_hop))); + assert!(iface.is_neighbor_resolved(Instant::from_millis(10), IpAddress::Ipv4(next_hop))); + assert_eq!( + iface.dispatch_ipv4_packet( + Instant::from_millis(10), + CapturingTxToken(frames.clone()), + next_hop, + None, + &packet, + ), + Ok(()) + ); + + let frames = frames.borrow(); + assert_eq!(frames.len(), 1); + let frame = EthernetFrame::new_checked(&frames[0]).unwrap(); + assert_eq!(frame.ethertype(), EthernetProtocol::Ipv4); + assert_eq!(frame.dst_addr(), local_mac); + drop(frames); + + iface.set_neighbor_discovery_enabled(true); + assert_eq!( + iface.inner.lookup_hardware_addr( + MockTxToken, + &IpAddress::Ipv4(next_hop), + &mut iface.fragmenter, + ), + Err(DispatchError::NeighborPending) + ); +} + +#[test] +#[cfg(all(feature = "medium-ethernet", feature = "proto-ipv4-fragmentation"))] +fn disabling_neighbor_discovery_drops_pending_fragment_with_cached_hardware_address() { + let (mut iface, _, _) = setup(Medium::Ethernet); + iface.fragmenter.packet_len = 100; + iface.fragmenter.sent_bytes = 40; + iface.fragmenter.ipv4.dst_hardware_addr = EthernetAddress::from_bytes(&[0x02, 0, 0, 0, 0, 99]); + + iface.set_neighbor_discovery_enabled(false); + + assert!(iface.fragmenter.is_empty()); + assert_eq!( + iface.fragmenter.ipv4.dst_hardware_addr, + EthernetAddress::default() + ); +} + +#[test] +#[cfg(all(feature = "medium-ethernet", feature = "proto-ipv4-fragmentation"))] +fn enabling_neighbor_discovery_drops_pending_direct_fragment() { + let (mut iface, _, _) = setup(Medium::Ethernet); + iface.set_neighbor_discovery_enabled(false); + iface.fragmenter.packet_len = 100; + iface.fragmenter.sent_bytes = 40; + iface.fragmenter.ipv4.dst_hardware_addr = iface.hardware_addr().ethernet_or_panic(); + + iface.set_neighbor_discovery_enabled(true); + + assert!(iface.fragmenter.is_empty()); + assert_eq!( + iface.fragmenter.ipv4.dst_hardware_addr, + EthernetAddress::default() + ); +} + #[test] #[cfg(feature = "medium-ip")] fn explicit_ipv4_dispatch_preserves_packet_on_ip_medium() { @@ -685,6 +769,28 @@ fn test_handle_valid_arp_request(#[case] medium: Medium) { let mut packet = ArpPacket::new_unchecked(frame.payload_mut()); repr.emit(&mut packet); + iface.set_neighbor_discovery_enabled(false); + assert_eq!( + iface.inner.process_ethernet( + &mut sockets, + PacketMeta::default(), + frame.into_inner(), + &mut iface.fragments + ), + None + ); + iface.set_neighbor_discovery_enabled(true); + assert_eq!( + iface.inner.lookup_hardware_addr( + MockTxToken, + &IpAddress::Ipv4(remote_ip_addr), + &mut iface.fragmenter, + ), + Err(DispatchError::NeighborPending) + ); + + let frame = EthernetFrame::new_unchecked(&mut eth_bytes); + // Ensure an ARP Request for us triggers an ARP Reply assert_eq!( iface.inner.process_ethernet( @@ -713,6 +819,74 @@ fn test_handle_valid_arp_request(#[case] medium: Medium) { ); } +#[test] +#[cfg(all(feature = "proto-ipv4-fragmentation", feature = "medium-ip"))] +fn pending_native_fragments_observe_runtime_mtu_change() { + use core::cell::RefCell; + use std::rc::Rc; + + #[derive(Clone)] + struct BoundedTxToken { + limit: usize, + lengths: Rc>>, + } + + impl TxToken for BoundedTxToken { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + assert!(len <= self.limit); + self.lengths.borrow_mut().push(len); + let mut buffer = vec![0; len]; + f(&mut buffer) + } + } + + let (mut iface, _, _) = setup(Medium::Ip); + iface.set_ip_mtu(1200).unwrap(); + let lengths = Rc::new(RefCell::new(Vec::new())); + let payload = vec![0xa5; 1800]; + let packet = Packet::new_ipv4( + Ipv4Repr { + src_addr: Ipv4Address::new(192, 0, 2, 1), + dst_addr: Ipv4Address::new(198, 51, 100, 1), + next_header: IpProtocol::Udp, + payload_len: payload.len(), + hop_limit: 64, + }, + IpPayload::Raw(&payload), + ); + + iface + .inner + .dispatch_ip( + BoundedTxToken { + limit: 1200, + lengths: lengths.clone(), + }, + PacketMeta::default(), + packet, + &mut iface.fragmenter, + ) + .unwrap(); + assert!(!iface.fragmenter.finished()); + + iface.set_ip_mtu(576).unwrap(); + iface.inner.dispatch_ipv4_frag( + BoundedTxToken { + limit: 576, + lengths: lengths.clone(), + }, + &mut iface.fragmenter, + ); + + let lengths = lengths.borrow(); + assert_eq!(lengths.len(), 2); + assert!(lengths[0] <= 1200); + assert!(lengths[1] <= 576); +} + #[rstest] #[case(Medium::Ethernet)] #[cfg(feature = "medium-ethernet")] diff --git a/src/iface/interface/tests/ipv6.rs b/src/iface/interface/tests/ipv6.rs index eab12508e..04423b08a 100644 --- a/src/iface/interface/tests/ipv6.rs +++ b/src/iface/interface/tests/ipv6.rs @@ -636,6 +636,25 @@ fn ndisc_neighbor_advertisement_ethernet(#[case] medium: Medium) { let (mut iface, mut sockets, _device) = setup(medium); + iface.set_neighbor_discovery_enabled(false); + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + None + ); + iface.set_neighbor_discovery_enabled(true); + assert_eq!( + iface.inner.neighbor_cache.lookup( + &IpAddress::Ipv6(Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002)), + iface.inner.now, + ), + NeighborAnswer::NotFound, + ); + assert_eq!( iface.inner.process_ipv6( &mut sockets, @@ -804,6 +823,51 @@ fn test_handle_valid_ndisc_request(#[case] medium: Medium) { &ChecksumCapabilities::default(), ); + iface.set_neighbor_discovery_enabled(false); + assert_eq!( + iface.inner.lookup_hardware_addr( + MockTxToken, + &IpAddress::Ipv6(remote_ip_addr), + &mut iface.fragmenter, + ), + Ok((HardwareAddress::Ethernet(local_hw_addr), MockTxToken)) + ); + let icmpv6_expected = Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert { + flags: NdiscNeighborFlags::SOLICITED, + target_addr: local_ip_addr, + lladdr: Some(local_hw_addr.into()), + }); + let ipv6_expected = Ipv6Repr { + src_addr: local_ip_addr, + dst_addr: remote_ip_addr, + next_header: IpProtocol::Icmpv6, + hop_limit: 0xff, + payload_len: icmpv6_expected.buffer_len(), + }; + assert_eq!( + iface.inner.process_ethernet( + &mut sockets, + PacketMeta::default(), + frame.into_inner(), + &mut iface.fragments + ), + Some(EthernetPacket::Ip(Packet::new_ipv6( + ipv6_expected, + IpPayload::Icmpv6(icmpv6_expected) + ))) + ); + iface.set_neighbor_discovery_enabled(true); + assert_eq!( + iface.inner.lookup_hardware_addr( + MockTxToken, + &IpAddress::Ipv6(remote_ip_addr), + &mut iface.fragmenter, + ), + Err(DispatchError::NeighborPending) + ); + + let frame = EthernetFrame::new_unchecked(&mut eth_bytes); + let icmpv6_expected = Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert { flags: NdiscNeighborFlags::SOLICITED, target_addr: local_ip_addr, @@ -843,6 +907,36 @@ fn test_handle_valid_ndisc_request(#[case] medium: Medium) { ); } +#[rstest] +#[case::malformed(RawHardwareAddress::from_bytes(&[0x02]))] +#[case::non_unicast(RawHardwareAddress::from_bytes(&[0xff; 6]))] +#[cfg(feature = "medium-ethernet")] +fn disabled_neighbor_discovery_rejects_invalid_ns_source_lladdr( + #[case] source_lladdr: RawHardwareAddress, +) { + let (mut iface, _, _) = setup(Medium::Ethernet); + let local_ip_addr = Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 1); + let remote_ip_addr = Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2); + iface.set_neighbor_discovery_enabled(false); + + assert_eq!( + iface.inner.process_ndisc( + Ipv6Repr { + src_addr: remote_ip_addr, + dst_addr: local_ip_addr.solicited_node(), + next_header: IpProtocol::Icmpv6, + hop_limit: 0xff, + payload_len: 0, + }, + NdiscRepr::NeighborSolicit { + target_addr: local_ip_addr, + lladdr: Some(source_lladdr), + }, + ), + None + ); +} + #[rstest] #[case(Medium::Ip)] #[cfg(feature = "medium-ip")] diff --git a/src/iface/interface/tests/mod.rs b/src/iface/interface/tests/mod.rs index a74ba9e04..961473d40 100644 --- a/src/iface/interface/tests/mod.rs +++ b/src/iface/interface/tests/mod.rs @@ -59,6 +59,69 @@ fn test_new_panic() { Interface::new(config, &mut device, Instant::ZERO); } +#[rstest] +#[case::ip(Medium::Ip, 1200)] +#[cfg(feature = "medium-ip")] +#[case::ethernet(Medium::Ethernet, 1214)] +#[cfg(feature = "medium-ethernet")] +fn runtime_ip_mtu_updates_cached_capabilities( + #[case] medium: Medium, + #[case] expected_frame_mtu: usize, +) { + let (mut iface, _, _) = setup(medium); + + iface.set_ip_mtu(1200).unwrap(); + + assert_eq!(iface.inner.ip_mtu(), 1200); + assert_eq!(iface.inner.caps.max_transmission_unit, expected_frame_mtu); +} + +#[test] +#[cfg(feature = "medium-ethernet")] +fn runtime_ip_mtu_rejects_frame_size_overflow_without_mutation() { + let (mut iface, _, _) = setup(Medium::Ethernet); + let before = iface.inner.caps.max_transmission_unit; + + assert_eq!( + iface.set_ip_mtu(usize::MAX), + Err(IpMtuError::FrameSizeOverflow) + ); + assert_eq!(iface.inner.caps.max_transmission_unit, before); +} + +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case::ethernet(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn runtime_ip_mtu_rejects_unsafe_small_values_without_mutation(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let before = iface.inner.caps.max_transmission_unit; + + assert_eq!(iface.set_ip_mtu(67), Err(IpMtuError::TooSmall)); + assert_eq!(iface.inner.caps.max_transmission_unit, before); + assert_eq!(iface.set_ip_mtu(68), Ok(())); + assert_eq!(iface.inner.ip_mtu(), 68); +} + +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case::ethernet(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn runtime_ip_mtu_rejects_values_above_device_capability_without_mutation(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let frame_mtu = iface.inner.caps.max_transmission_unit; + let max_ip_mtu = iface.inner.ip_mtu(); + + assert_eq!(iface.set_ip_mtu(max_ip_mtu + 1), Err(IpMtuError::TooLarge)); + assert_eq!(iface.inner.caps.max_transmission_unit, frame_mtu); +} + #[cfg(feature = "socket-udp")] #[rstest] #[case::ip(Medium::Ip)] diff --git a/src/iface/mod.rs b/src/iface/mod.rs index 1d1aa85c5..f3f100c22 100644 --- a/src/iface/mod.rs +++ b/src/iface/mod.rs @@ -19,8 +19,8 @@ pub mod packet; #[cfg(feature = "multicast")] pub use self::interface::multicast::MulticastError; pub use self::interface::{ - Config, Interface, InterfaceInner as Context, Ipv4PacketDispatchError, PollIngressSingleResult, - PollResult, + Config, Interface, InterfaceInner as Context, IpMtuError, Ipv4PacketDispatchError, + PollIngressSingleResult, PollResult, }; pub use self::route::{Route, RouteTableFull, Routes};