[netstack3] Support deprecating SLAAC address
SLAAC addresses with a zero preferred lifetime should be considered
deprecated, even if they were just generated.
Test: fx test netstack3_test
Bug: 52349
Change-Id: Iaee50ef54fba22c4b6376a760ca670664f114ef1
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/394064
Reviewed-by: Ghanan Gowripalan <ghanan@google.com>
Testability-Review: Ghanan Gowripalan <ghanan@google.com>
Commit-Queue: Chris Hahn <chahn@google.com>
diff --git a/src/connectivity/network/netstack3/core/src/device/ndp.rs b/src/connectivity/network/netstack3/core/src/device/ndp.rs
index 510f151..0f59fe8 100644
--- a/src/connectivity/network/netstack3/core/src/device/ndp.rs
+++ b/src/connectivity/network/netstack3/core/src/device/ndp.rs
@@ -2936,16 +2936,10 @@
};
let now = ctx.now();
- // TODO(52349): Immediately deprecate the prefix when it has a 0
- // preferred lifetime.
- let preferred_until = now
- .checked_add(
- prefix_info
- .preferred_lifetime()
- .map(|l| l.get())
- .unwrap_or(Duration::from_secs(0)),
- )
- .unwrap();
+ let preferred_until = prefix_info
+ .preferred_lifetime()
+ .map(|l| now.checked_add(l.get()).unwrap());
+
let valid_for = prefix_info
.valid_lifetime()
.map(|l| l.get())
@@ -2985,16 +2979,25 @@
//
// Must not have reached this point if the address was not already
// assigned to a device.
- assert!(ctx
- .schedule_timer_instant(
- preferred_until,
+ if let Some(preferred_until_duration) = preferred_until {
+ if entry.state().is_deprecated() {
+ ctx.unique_address_determined(device_id, addr.get());
+ }
+ ctx.schedule_timer_instant(
+ preferred_until_duration,
NdpTimerId::new_deprecate_slaac_address(
device_id,
addr.get(),
)
.into(),
- )
- .is_some());
+ );
+ } else if !entry.state().is_deprecated() {
+ ctx.deprecate_slaac_addr(device_id, &addr.get());
+ ctx.cancel_timer(NdpTimerId::new_deprecate_slaac_address(
+ device_id,
+ addr.get(),
+ ));
+ }
// As per RFC 4862 section 5.5.3.e, the specific action to perform
// for the valid lifetime of the address depends on the Valid
@@ -3118,21 +3121,6 @@
} else {
trace!("receive_ndp_packet_inner: Successfully configured new IPv6 address {:?} on device {:?} via SLAAC", address, device_id);
- // Set the preferred lifetime for this address.
- //
- // Must not have reached this point if the address was already
- // assigned to a device.
- assert!(ctx
- .schedule_timer_instant(
- preferred_until,
- NdpTimerId::new_deprecate_slaac_address(
- device_id,
- address.addr().get()
- )
- .into(),
- )
- .is_none());
-
// Set the valid lifetime for this address.
//
// Must not have reached this point if the address was already
@@ -3147,6 +3135,31 @@
.into(),
)
.is_none());
+
+ let timer_id = NdpTimerId::new_deprecate_slaac_address(
+ device_id,
+ address.addr().get(),
+ );
+
+ // Set the preferred lifetime for this address.
+ //
+ // Must not have reached this point if the address was already
+ // assigned to a device.
+ match preferred_until {
+ Some(preferred_until_duration) => assert!(ctx
+ .schedule_timer_instant(
+ preferred_until_duration,
+ timer_id.into()
+ )
+ .is_none()),
+ None => {
+ ctx.deprecate_slaac_addr(
+ device_id,
+ &address.addr().get(),
+ );
+ assert!(ctx.cancel_timer(timer_id.into()).is_none());
+ }
+ };
}
}
}
@@ -7589,8 +7602,10 @@
//
// Should be marked as deprecated.
//
-
- run_for(&mut ctx, Duration::from_secs(preferred_lifetime.into()));
+ assert_eq!(
+ run_for(&mut ctx, Duration::from_secs(preferred_lifetime.into())),
+ vec!(NdpTimerId::new_deprecate_slaac_address(device.id().into(), expected_addr).into())
+ );
let entry =
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.last()
@@ -7604,7 +7619,496 @@
// Should be deleted.
//
- run_for(&mut ctx, Duration::from_secs((valid_lifetime - preferred_lifetime).into()));
+ assert_eq!(
+ run_for(&mut ctx, Duration::from_secs((valid_lifetime - preferred_lifetime).into())),
+ vec!(
+ NdpTimerId::new_prefix_invalidation(device.id().into(), addr_subnet).into(),
+ NdpTimerId::new_invalidate_slaac_address(device.id().into(), expected_addr).into()
+ )
+ );
+ assert_eq!(
+ NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
+ .count(),
+ 0
+ );
+
+ // No more timers.
+ assert!(trigger_next_timer(&mut ctx).is_none());
+ }
+
+ #[test]
+ fn test_host_stateless_address_autoconfiguration_new_ra_with_preferred_lifetime_zero() {
+ let config = Ipv6::DUMMY_CONFIG;
+ let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
+ let device =
+ ctx.state_mut().add_ethernet_device(config.local_mac, Ipv6::MINIMUM_LINK_MTU.into());
+ crate::device::initialize_device(&mut ctx, device);
+
+ let src_mac = config.remote_mac;
+ let src_ip = src_mac.to_ipv6_link_local().addr().get();
+ let prefix = Ipv6Addr::new([1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0]);
+ let prefix_length = 64;
+ let addr_subnet = AddrSubnet::new(prefix, prefix_length).unwrap();
+ let mut expected_addr = [1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0];
+ expected_addr[8..].copy_from_slice(&config.local_mac.to_eui64()[..]);
+ let expected_addr = Ipv6Addr::new(expected_addr);
+
+ // Enable DAD for future IPs.
+ let mut ndp_configs = NdpConfigurations::default();
+ ndp_configs.set_max_router_solicitations(None);
+ crate::device::set_ndp_configurations(&mut ctx, device, ndp_configs);
+
+ //
+ // Receive a new RA with new prefix (autonomous).
+ //
+ // Should get a new IP.
+ //
+
+ let valid_lifetime = 10000;
+
+ let mut icmpv6_packet_buf = slaac_packet_buf(
+ src_ip,
+ config.local_ip.get(),
+ prefix,
+ prefix_length,
+ true,
+ true,
+ valid_lifetime,
+ 0,
+ );
+ let icmpv6_packet = icmpv6_packet_buf
+ .parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip, config.local_ip))
+ .unwrap();
+ ctx.receive_ndp_packet(device, src_ip, config.local_ip, icmpv6_packet.unwrap_ndp());
+ let ndp_state =
+ StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
+ &mut ctx,
+ device.id().into(),
+ );
+ assert!(ndp_state.has_prefix(&addr_subnet));
+
+ // Should NOT have gotten a new ip.
+ assert_eq!(
+ NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
+ .count(),
+ 0
+ );
+
+ // Make sure deprecate and invalidation timers are set.
+ let now = ctx.now();
+ assert_eq!(
+ ctx.dispatcher()
+ .timer_events()
+ .filter(|x| (*x.1
+ == NdpTimerId::new_deprecate_slaac_address(device.id().into(), expected_addr)
+ .into()))
+ .count(),
+ 0
+ );
+ assert_eq!(
+ ctx.dispatcher()
+ .timer_events()
+ .filter(|x| (*x.0
+ == now.checked_add(Duration::from_secs(valid_lifetime.into())).unwrap())
+ && (*x.1
+ == NdpTimerId::new_invalidate_slaac_address(
+ device.id().into(),
+ expected_addr
+ )
+ .into()))
+ .count(),
+ 0
+ );
+ assert_eq!(
+ ctx.dispatcher()
+ .timer_events()
+ .filter(|x| (*x.1
+ == NdpTimerId::new_dad_ns_transmission(device.id().into(), expected_addr)
+ .into()))
+ .count(),
+ 0
+ );
+
+ assert_eq!(run_for(&mut ctx, Duration::from_secs(1)).len(), 0);
+
+ assert_eq!(
+ NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
+ .count(),
+ 0
+ );
+ }
+
+ #[test]
+ fn test_host_stateless_address_autoconfiguration_updated_ra_with_preferred_lifetime_zero() {
+ let config = Ipv6::DUMMY_CONFIG;
+ let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
+ let device =
+ ctx.state_mut().add_ethernet_device(config.local_mac, Ipv6::MINIMUM_LINK_MTU.into());
+ crate::device::initialize_device(&mut ctx, device);
+
+ let src_mac = config.remote_mac;
+ let src_ip = src_mac.to_ipv6_link_local().addr().get();
+ let prefix = Ipv6Addr::new([1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0]);
+ let prefix_length = 64;
+ let addr_subnet = AddrSubnet::new(prefix, prefix_length).unwrap();
+ let mut expected_addr = [1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0];
+ expected_addr[8..].copy_from_slice(&config.local_mac.to_eui64()[..]);
+ let expected_addr = Ipv6Addr::new(expected_addr);
+ let expected_addr_sub = AddrSubnet::new(expected_addr, prefix_length).unwrap();
+
+ // Enable DAD for future IPs.
+ let mut ndp_configs = NdpConfigurations::default();
+ ndp_configs.set_max_router_solicitations(None);
+ crate::device::set_ndp_configurations(&mut ctx, device, ndp_configs);
+
+ //
+ // Receive a new RA with new prefix (autonomous).
+ //
+ // Should get a new IP.
+ //
+
+ let valid_lifetime = 10000;
+ let preferred_lifetime = 9000;
+
+ let mut icmpv6_packet_buf = slaac_packet_buf(
+ src_ip,
+ config.local_ip.get(),
+ prefix,
+ prefix_length,
+ true,
+ true,
+ valid_lifetime,
+ preferred_lifetime,
+ );
+ let icmpv6_packet = icmpv6_packet_buf
+ .parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip, config.local_ip))
+ .unwrap();
+ ctx.receive_ndp_packet(device, src_ip, config.local_ip, icmpv6_packet.unwrap_ndp());
+ let ndp_state =
+ StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
+ &mut ctx,
+ device.id().into(),
+ );
+ assert!(ndp_state.has_prefix(&addr_subnet));
+
+ // Should have gotten a new ip.
+ assert_eq!(
+ NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
+ .count(),
+ 1
+ );
+ let entry =
+ NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
+ .last()
+ .unwrap();
+ assert_eq!(*entry.addr_sub(), expected_addr_sub);
+ assert_eq!(entry.state(), AddressState::Tentative);
+ assert_eq!(entry.configuration_type(), AddressConfigurationType::Slaac);
+
+ // Make sure deprecate and invalidation timers are set.
+ let now = ctx.now();
+ assert_eq!(
+ ctx.dispatcher()
+ .timer_events()
+ .filter(|x| (*x.0
+ == now.checked_add(Duration::from_secs(preferred_lifetime.into())).unwrap())
+ && (*x.1
+ == NdpTimerId::new_deprecate_slaac_address(
+ device.id().into(),
+ expected_addr
+ )
+ .into()))
+ .count(),
+ 1
+ );
+ assert_eq!(
+ ctx.dispatcher()
+ .timer_events()
+ .filter(|x| (*x.0
+ == now.checked_add(Duration::from_secs(valid_lifetime.into())).unwrap())
+ && (*x.1
+ == NdpTimerId::new_invalidate_slaac_address(
+ device.id().into(),
+ expected_addr
+ )
+ .into()))
+ .count(),
+ 1
+ );
+ assert_eq!(
+ ctx.dispatcher()
+ .timer_events()
+ .filter(|x| (*x.1
+ == NdpTimerId::new_dad_ns_transmission(device.id().into(), expected_addr)
+ .into()))
+ .count(),
+ 1
+ );
+
+ assert_eq!(
+ run_for(&mut ctx, Duration::from_secs(1)),
+ vec!(NdpTimerId::new_dad_ns_transmission(device.id().into(), expected_addr).into())
+ );
+
+ let entry =
+ NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
+ .last()
+ .unwrap();
+ assert_eq!(*entry.addr_sub(), expected_addr_sub);
+ assert_eq!(entry.state(), AddressState::Assigned);
+ assert_eq!(entry.configuration_type(), AddressConfigurationType::Slaac);
+
+ //
+ // Receive the same RA, now with preferred_lifetime = 0
+ //
+ // Should not get a new IP, but keep the one generated before.
+ //
+ let mut icmpv6_packet_buf = slaac_packet_buf(
+ src_ip,
+ config.local_ip.get(),
+ prefix,
+ prefix_length,
+ true,
+ true,
+ valid_lifetime,
+ 0,
+ );
+ let icmpv6_packet = icmpv6_packet_buf
+ .parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip, config.local_ip))
+ .unwrap();
+ ctx.receive_ndp_packet(device, src_ip, config.local_ip, icmpv6_packet.unwrap_ndp());
+ let ndp_state =
+ StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
+ &mut ctx,
+ device.id().into(),
+ );
+ assert!(ndp_state.has_prefix(&addr_subnet));
+
+ // Should not have changed.
+ assert_eq!(
+ NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
+ .count(),
+ 1
+ );
+ let entry =
+ NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
+ .last()
+ .unwrap();
+ assert_eq!(*entry.addr_sub(), expected_addr_sub);
+ assert_eq!(entry.state(), AddressState::Deprecated);
+ assert_eq!(entry.configuration_type(), AddressConfigurationType::Slaac);
+
+ // Timers should have been reset.
+ let now = ctx.now();
+ assert_eq!(
+ ctx.dispatcher()
+ .timer_events()
+ .filter(|x| (*x.1
+ == NdpTimerId::new_deprecate_slaac_address(device.id().into(), expected_addr)
+ .into()))
+ .count(),
+ 0
+ );
+ assert_eq!(
+ ctx.dispatcher()
+ .timer_events()
+ .filter(|x| (*x.0
+ == now.checked_add(Duration::from_secs(valid_lifetime.into())).unwrap())
+ && (*x.1
+ == NdpTimerId::new_invalidate_slaac_address(
+ device.id().into(),
+ expected_addr
+ )
+ .into()))
+ .count(),
+ 1
+ );
+
+ //
+ // Receive the same RA (again), still with preferred_lifetime = 0
+ //
+ // Should not get a new IP, but keep the one generated before.
+ //
+ let mut icmpv6_packet_buf = slaac_packet_buf(
+ src_ip,
+ config.local_ip.get(),
+ prefix,
+ prefix_length,
+ true,
+ true,
+ valid_lifetime,
+ 0,
+ );
+ let icmpv6_packet = icmpv6_packet_buf
+ .parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip, config.local_ip))
+ .unwrap();
+ ctx.receive_ndp_packet(device, src_ip, config.local_ip, icmpv6_packet.unwrap_ndp());
+ let ndp_state =
+ StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
+ &mut ctx,
+ device.id().into(),
+ );
+ assert!(ndp_state.has_prefix(&addr_subnet));
+
+ // Should not have changed.
+ assert_eq!(
+ NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
+ .count(),
+ 1
+ );
+ let entry =
+ NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
+ .last()
+ .unwrap();
+ assert_eq!(*entry.addr_sub(), expected_addr_sub);
+ assert_eq!(entry.state(), AddressState::Deprecated);
+ assert_eq!(entry.configuration_type(), AddressConfigurationType::Slaac);
+
+ // Timers should have been reset.
+ let now = ctx.now();
+ assert_eq!(
+ ctx.dispatcher()
+ .timer_events()
+ .filter(|x| (*x.1
+ == NdpTimerId::new_deprecate_slaac_address(device.id().into(), expected_addr)
+ .into()))
+ .count(),
+ 0
+ );
+ assert_eq!(
+ ctx.dispatcher()
+ .timer_events()
+ .filter(|x| (*x.0
+ == now.checked_add(Duration::from_secs(valid_lifetime.into())).unwrap())
+ && (*x.1
+ == NdpTimerId::new_invalidate_slaac_address(
+ device.id().into(),
+ expected_addr
+ )
+ .into()))
+ .count(),
+ 1
+ );
+
+ //
+ // Receive the same RA, now with preferred_lifetime > 0
+ //
+ // Should not get a new IP, but keep the one generated before.
+ //
+ let mut icmpv6_packet_buf = slaac_packet_buf(
+ src_ip,
+ config.local_ip.get(),
+ prefix,
+ prefix_length,
+ true,
+ true,
+ valid_lifetime,
+ preferred_lifetime,
+ );
+ let icmpv6_packet = icmpv6_packet_buf
+ .parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip, config.local_ip))
+ .unwrap();
+ ctx.receive_ndp_packet(device, src_ip, config.local_ip, icmpv6_packet.unwrap_ndp());
+ let ndp_state =
+ StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
+ &mut ctx,
+ device.id().into(),
+ );
+ assert!(ndp_state.has_prefix(&addr_subnet));
+
+ // Should not have changed.
+ assert_eq!(
+ NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
+ .count(),
+ 1
+ );
+ let entry =
+ NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
+ .last()
+ .unwrap();
+ assert_eq!(*entry.addr_sub(), expected_addr_sub);
+ assert_eq!(entry.state(), AddressState::Assigned);
+ assert_eq!(entry.configuration_type(), AddressConfigurationType::Slaac);
+
+ // Make sure deprecate and invalidation timers are set.
+ let now = ctx.now();
+ assert_eq!(
+ ctx.dispatcher()
+ .timer_events()
+ .filter(|x| (*x.0
+ == now.checked_add(Duration::from_secs(preferred_lifetime.into())).unwrap())
+ && (*x.1
+ == NdpTimerId::new_deprecate_slaac_address(
+ device.id().into(),
+ expected_addr
+ )
+ .into()))
+ .count(),
+ 1
+ );
+ assert_eq!(
+ ctx.dispatcher()
+ .timer_events()
+ .filter(|x| (*x.0
+ == now.checked_add(Duration::from_secs(valid_lifetime.into())).unwrap())
+ && (*x.1
+ == NdpTimerId::new_invalidate_slaac_address(
+ device.id().into(),
+ expected_addr
+ )
+ .into()))
+ .count(),
+ 1
+ );
+ assert_eq!(
+ ctx.dispatcher()
+ .timer_events()
+ .filter(|x| (*x.1
+ == NdpTimerId::new_dad_ns_transmission(device.id().into(), expected_addr)
+ .into()))
+ .count(),
+ 0
+ );
+
+ assert_eq!(run_for(&mut ctx, Duration::from_secs(1)), vec!());
+
+ let entry =
+ NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
+ .last()
+ .unwrap();
+ assert_eq!(*entry.addr_sub(), expected_addr_sub);
+ assert_eq!(entry.state(), AddressState::Assigned);
+ assert_eq!(entry.configuration_type(), AddressConfigurationType::Slaac);
+
+ //
+ // Preferred lifetime expiration.
+ //
+ // Should be marked as deprecated.
+ //
+ assert_eq!(
+ run_for(&mut ctx, Duration::from_secs(preferred_lifetime.into())),
+ vec!(NdpTimerId::new_deprecate_slaac_address(device.id().into(), expected_addr).into())
+ );
+
+ let entry =
+ NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
+ .last()
+ .unwrap();
+ assert_eq!(entry.state(), AddressState::Deprecated);
+ assert_eq!(entry.configuration_type(), AddressConfigurationType::Slaac);
+
+ //
+ // Valid lifetime expiration.
+ //
+ // Should be deleted.
+ //
+ assert_eq!(
+ run_for(&mut ctx, Duration::from_secs((valid_lifetime - preferred_lifetime).into())),
+ vec!(
+ NdpTimerId::new_prefix_invalidation(device.id().into(), addr_subnet).into(),
+ NdpTimerId::new_invalidate_slaac_address(device.id().into(), expected_addr).into()
+ )
+ );
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),