diff --git a/src/iface/interface/ipv6.rs b/src/iface/interface/ipv6.rs index a72492bb2..71114726b 100644 --- a/src/iface/interface/ipv6.rs +++ b/src/iface/interface/ipv6.rs @@ -342,7 +342,7 @@ impl InterfaceInner { pub(super) fn process_icmpv6<'frame>( &mut self, - _sockets: &mut SocketSet, + sockets: &mut SocketSet, ip_repr: Ipv6Repr, ip_payload: &'frame [u8], ) -> Option> { @@ -360,7 +360,7 @@ impl InterfaceInner { #[cfg(feature = "socket-icmp")] { use crate::socket::icmp::Socket as IcmpSocket; - for icmp_socket in _sockets + for icmp_socket in sockets .items_mut() .filter_map(|i| IcmpSocket::downcast_mut(&mut i.socket)) { @@ -393,9 +393,9 @@ impl InterfaceInner { #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] Icmpv6Repr::Ndisc(repr) if ip_repr.hop_limit == 0xff => match self.caps.medium { #[cfg(feature = "medium-ethernet")] - Medium::Ethernet => self.process_ndisc(ip_repr, repr), + Medium::Ethernet => self.process_ndisc(sockets, ip_repr, repr), #[cfg(feature = "medium-ieee802154")] - Medium::Ieee802154 => self.process_ndisc(ip_repr, repr), + Medium::Ieee802154 => self.process_ndisc(sockets, ip_repr, repr), #[cfg(feature = "medium-ip")] Medium::Ip => None, }, @@ -413,9 +413,20 @@ impl InterfaceInner { #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] pub(super) fn process_ndisc<'frame>( &mut self, + sockets: &mut SocketSet, ip_repr: Ipv6Repr, repr: NdiscRepr<'frame>, ) -> Option> { + // todo add feature slaac + { + use crate::socket::slaac::Socket as SlaacSocket; + if let Some(slaac_socket) = sockets + .items_mut() + .find_map(|i| SlaacSocket::downcast_mut(&mut i.socket)) + { + slaac_socket.process(self, &ip_repr, &repr); + } + } match repr { NdiscRepr::NeighborAdvert { lladdr, diff --git a/src/iface/interface/mod.rs b/src/iface/interface/mod.rs index 697e60ce1..8018bef1e 100644 --- a/src/iface/interface/mod.rs +++ b/src/iface/interface/mod.rs @@ -70,6 +70,8 @@ macro_rules! check { }; } use check; +use crate::iface::packet::IpPayload::Icmpv6; +use crate::wire::ip::Repr; /// A network interface. /// @@ -669,6 +671,13 @@ impl Interface { Packet::new(ip, IpPayload::Udp(udp, dns)), ) }), + Socket::Slaac(socket) => socket.dispatch(&mut self.inner, |inner, (ipv6_repr, ndisc)| { + respond( + inner, + PacketMeta::default(), + Packet::new_ipv6(ipv6_repr, Icmpv6(Icmpv6Repr::Ndisc(ndisc))), + ) + }), }; match result { diff --git a/src/socket/mod.rs b/src/socket/mod.rs index 7d48b4234..7793df7ef 100644 --- a/src/socket/mod.rs +++ b/src/socket/mod.rs @@ -29,6 +29,7 @@ pub mod udp; #[cfg(feature = "async")] mod waker; +pub mod slaac; #[cfg(feature = "async")] pub(crate) use self::waker::WakerRegistration; @@ -69,6 +70,8 @@ pub enum Socket<'a> { Dhcpv4(dhcpv4::Socket<'a>), #[cfg(feature = "socket-dns")] Dns(dns::Socket<'a>), + // todo add feature + Slaac(slaac::Socket), } impl<'a> Socket<'a> { @@ -86,6 +89,8 @@ impl<'a> Socket<'a> { Socket::Dhcpv4(s) => s.poll_at(cx), #[cfg(feature = "socket-dns")] Socket::Dns(s) => s.poll_at(cx), + // todo + Socket::Slaac(s) => s.poll_at(cx), } } } @@ -139,3 +144,5 @@ from_socket!(tcp::Socket<'a>, Tcp); from_socket!(dhcpv4::Socket<'a>, Dhcpv4); #[cfg(feature = "socket-dns")] from_socket!(dns::Socket<'a>, Dns); +// todo add feature +from_socket!(slaac::Socket, Slaac); diff --git a/src/socket/slaac.rs b/src/socket/slaac.rs new file mode 100644 index 000000000..ee88f7769 --- /dev/null +++ b/src/socket/slaac.rs @@ -0,0 +1,174 @@ +use core::task::Waker; +use crate::iface::Context; +use crate::socket::{PollAt, WakerRegistration}; +use crate::socket::slaac::SlaacState::Renewing; +use crate::time::{Duration, Instant}; +use crate::wire::{DhcpPacket, DhcpRepr, IpProtocol, Ipv6Address, Ipv6Cidr, Ipv6Repr, NdiscRepr, UdpRepr}; + +#[derive(Debug)] +struct DiscoveringState { + /// When to send next request + retry_at: Instant, +} +struct RequestState { + /// When to send next request + retry_at: Instant, + /// How many retries have been done + router: Ipv6Address, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Config { + /// IP address + pub cdir: Ipv6Cidr, + /// Router address, also known as default gateway + pub router: Option, + /// DNS servers + pub dns_servers: heapless::Vec, +} + +#[derive(Debug)] +struct RenewState { + // active network configuration + config: Config, + + /// Renew timer. When reached, we will start attempting + /// to configure a new IPv6 address + /// + /// Must be less or equal than `rebind_at`. + renew_at: Instant, + + /// Expiration timer. When reached, this IPv6 address is no longer valid, so it must be + /// thrown away and the ethernet interface deconfigured. + expires_at: Instant, +} + +#[derive(Debug)] +enum SlaacState { + /// waiting for router advertisement + Discovering(DiscoveringState), + Renewing(RenewState), +} + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Event { + /// Configuration has been lost (for example, the lease has expired) + Deconfigured, + /// Configuration has been newly acquired, or modified. + Configured(Config), +} + + +#[derive(Debug)] +pub struct Socket { + state: SlaacState, + + /// Waker registration + #[cfg(feature = "async")] + waker: WakerRegistration, + /// Set to true on config/state change, cleared back to false by the `config` function. + config_changed: bool, +} + +impl Socket { + pub fn new() -> Self { + Self { + state: SlaacState::Discovering(DiscoveringState { + retry_at: Instant::from_secs(0), + }), + #[cfg(feature = "async")] + waker: WakerRegistration::new(), + config_changed: true, + } + } +} + +impl Socket { + pub(crate) fn process( + &mut self, + cx: &mut Context, + ip_repr: &Ipv6Repr, + ndisc: &NdiscRepr, + ) { + match (&mut self.state, ndisc) { + (SlaacState::Discovering(state), NdiscRepr::RouterAdvert {router_lifetime, prefix_info, .. }) => { + if let Some(prefix) = prefix_info { + self.config_changed(); + self.state = Renewing(RenewState { + config: Config { + cdir: Ipv6Cidr::new(prefix.prefix, prefix.prefix_len), + router: None, + dns_servers: Default::default(), + }, + renew_at: cx.now() + *router_lifetime / 2, + expires_at: cx.now() + *router_lifetime, + }); + } else { + // does not contain prefix?! + } + } + (SlaacState::Renewing(_), _) => {} + _ => {} + } + } + pub(crate) fn dispatch(&mut self, cx: &mut Context, emit: F) -> Result<(), E> + where + F: FnOnce(&mut Context, (Ipv6Repr, NdiscRepr)) -> Result<(), E>, + { + match &mut self.state { + SlaacState::Discovering(state) => { + state.retry_at = cx.now() + Duration::from_secs(5); + } + SlaacState::Renewing(state) => { + state.expires_at = cx.now() + Duration::from_secs(5); + } + } + Ok(()) + } +} + +impl Socket { + pub(crate) fn poll_at(&self, _cx: &mut Context) -> PollAt { + let t = match &self.state { + SlaacState::Discovering(discover) => discover.retry_at, + SlaacState::Renewing(renew) => renew.renew_at, + }; + PollAt::Time(t) + } + + /// 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) -> Option { + if !self.config_changed { + return None + } + if let SlaacState::Renewing(state) = &self.state { + self.config_changed = false; + Some(Event::Configured(Config { + cdir: state.config.cdir, + router: state.config.router, + dns_servers: state.config.dns_servers.clone() + })) + } else { + self.config_changed = false; + Some(Event::Deconfigured) + } + } + + /// This function _must_ be called when the configuration provided to the + /// interface, changes. It will update the `config_changed` field + /// so that a subsequent call to `poll` will yield an event, and wake a possible waker. + pub(crate) fn config_changed(&mut self) { + self.config_changed = true; + #[cfg(feature = "async")] + self.waker.wake(); + } + + #[cfg(feature = "async")] + pub fn register_waker(&mut self, waker: &Waker) { + self.waker.register(waker) + } +} \ No newline at end of file