|  | @@ -4,6 +4,8 @@
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  use core::cmp;
 | 
	
		
			
				|  |  |  use managed::{ManagedSlice, ManagedMap};
 | 
	
		
			
				|  |  | +#[cfg(not(feature = "proto-igmp"))]
 | 
	
		
			
				|  |  | +use core::marker::PhantomData;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  use {Error, Result};
 | 
	
		
			
				|  |  |  use phy::{Device, DeviceCapabilities, RxToken, TxToken};
 | 
	
	
		
			
				|  | @@ -14,11 +16,13 @@ use wire::{IpAddress, IpProtocol, IpRepr, IpCidr};
 | 
	
		
			
				|  |  |  #[cfg(feature = "proto-ipv6")]
 | 
	
		
			
				|  |  |  use wire::{Ipv6Address, Ipv6Packet, Ipv6Repr, IPV6_MIN_MTU};
 | 
	
		
			
				|  |  |  #[cfg(feature = "proto-ipv4")]
 | 
	
		
			
				|  |  | -use wire::{Ipv4Packet, Ipv4Repr, IPV4_MIN_MTU};
 | 
	
		
			
				|  |  | +use wire::{Ipv4Address, Ipv4Packet, Ipv4Repr, IPV4_MIN_MTU};
 | 
	
		
			
				|  |  |  #[cfg(feature = "proto-ipv4")]
 | 
	
		
			
				|  |  |  use wire::{ArpPacket, ArpRepr, ArpOperation};
 | 
	
		
			
				|  |  |  #[cfg(feature = "proto-ipv4")]
 | 
	
		
			
				|  |  |  use wire::{Icmpv4Packet, Icmpv4Repr, Icmpv4DstUnreachable};
 | 
	
		
			
				|  |  | +#[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +use wire::{IgmpPacket, IgmpRepr, IgmpVersion};
 | 
	
		
			
				|  |  |  #[cfg(feature = "proto-ipv6")]
 | 
	
		
			
				|  |  |  use wire::{Icmpv6Packet, Icmpv6Repr, Icmpv6ParamProblem};
 | 
	
		
			
				|  |  |  #[cfg(all(feature = "socket-icmp", any(feature = "proto-ipv4", feature = "proto-ipv6")))]
 | 
	
	
		
			
				|  | @@ -70,17 +74,29 @@ struct InterfaceInner<'b, 'c, 'e> {
 | 
	
		
			
				|  |  |      ethernet_addr:          EthernetAddress,
 | 
	
		
			
				|  |  |      ip_addrs:               ManagedSlice<'c, IpCidr>,
 | 
	
		
			
				|  |  |      routes:                 Routes<'e>,
 | 
	
		
			
				|  |  | +    #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +    ipv4_multicast_groups:  ManagedMap<'e, Ipv4Address, ()>,
 | 
	
		
			
				|  |  | +    #[cfg(not(feature = "proto-igmp"))]
 | 
	
		
			
				|  |  | +    _ipv4_multicast_groups: PhantomData<&'e ()>,
 | 
	
		
			
				|  |  | +    /// When to report for (all or) the next multicast group membership via IGMP
 | 
	
		
			
				|  |  | +    #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +    igmp_report_state:      IgmpReportState,
 | 
	
		
			
				|  |  |      device_capabilities:    DeviceCapabilities,
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  /// A builder structure used for creating a Ethernet network
 | 
	
		
			
				|  |  |  /// interface.
 | 
	
		
			
				|  |  |  pub struct InterfaceBuilder <'b, 'c, 'e, DeviceT: for<'d> Device<'d>> {
 | 
	
		
			
				|  |  | -    device:              DeviceT,
 | 
	
		
			
				|  |  | -    ethernet_addr:       Option<EthernetAddress>,
 | 
	
		
			
				|  |  | -    neighbor_cache:      Option<NeighborCache<'b>>,
 | 
	
		
			
				|  |  | -    ip_addrs:            ManagedSlice<'c, IpCidr>,
 | 
	
		
			
				|  |  | -    routes:              Routes<'e>,
 | 
	
		
			
				|  |  | +    device:                 DeviceT,
 | 
	
		
			
				|  |  | +    ethernet_addr:          Option<EthernetAddress>,
 | 
	
		
			
				|  |  | +    neighbor_cache:         Option<NeighborCache<'b>>,
 | 
	
		
			
				|  |  | +    ip_addrs:               ManagedSlice<'c, IpCidr>,
 | 
	
		
			
				|  |  | +    routes:                 Routes<'e>,
 | 
	
		
			
				|  |  | +    /// Does not share storage with `ipv6_multicast_groups` to avoid IPv6 size overhead.
 | 
	
		
			
				|  |  | +    #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +    ipv4_multicast_groups:  ManagedMap<'e, Ipv4Address, ()>,
 | 
	
		
			
				|  |  | +    #[cfg(not(feature = "proto-igmp"))]
 | 
	
		
			
				|  |  | +    _ipv4_multicast_groups: PhantomData<&'e ()>,
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  impl<'b, 'c, 'e, DeviceT> InterfaceBuilder<'b, 'c, 'e, DeviceT>
 | 
	
	
		
			
				|  | @@ -110,13 +126,17 @@ impl<'b, 'c, 'e, DeviceT> InterfaceBuilder<'b, 'c, 'e, DeviceT>
 | 
	
		
			
				|  |  |      ///         .ip_addrs(ip_addrs)
 | 
	
		
			
				|  |  |      ///         .finalize();
 | 
	
		
			
				|  |  |      /// ```
 | 
	
		
			
				|  |  | -    pub fn new(device: DeviceT) -> InterfaceBuilder<'b, 'c, 'e, DeviceT> {
 | 
	
		
			
				|  |  | +    pub fn new(device: DeviceT) -> Self {
 | 
	
		
			
				|  |  |          InterfaceBuilder {
 | 
	
		
			
				|  |  |              device:              device,
 | 
	
		
			
				|  |  |              ethernet_addr:       None,
 | 
	
		
			
				|  |  |              neighbor_cache:      None,
 | 
	
		
			
				|  |  |              ip_addrs:            ManagedSlice::Borrowed(&mut []),
 | 
	
		
			
				|  |  |              routes:              Routes::new(ManagedMap::Borrowed(&mut [])),
 | 
	
		
			
				|  |  | +            #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +            ipv4_multicast_groups:   ManagedMap::Borrowed(&mut []),
 | 
	
		
			
				|  |  | +            #[cfg(not(feature = "proto-igmp"))]
 | 
	
		
			
				|  |  | +            _ipv4_multicast_groups:  PhantomData,
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -127,7 +147,7 @@ impl<'b, 'c, 'e, DeviceT> InterfaceBuilder<'b, 'c, 'e, DeviceT>
 | 
	
		
			
				|  |  |      /// This function panics if the address is not unicast.
 | 
	
		
			
				|  |  |      ///
 | 
	
		
			
				|  |  |      /// [ethernet_addr]: struct.EthernetInterface.html#method.ethernet_addr
 | 
	
		
			
				|  |  | -    pub fn ethernet_addr(mut self, addr: EthernetAddress) -> InterfaceBuilder<'b, 'c, 'e, DeviceT> {
 | 
	
		
			
				|  |  | +    pub fn ethernet_addr(mut self, addr: EthernetAddress) -> Self {
 | 
	
		
			
				|  |  |          InterfaceInner::check_ethernet_addr(&addr);
 | 
	
		
			
				|  |  |          self.ethernet_addr = Some(addr);
 | 
	
		
			
				|  |  |          self
 | 
	
	
		
			
				|  | @@ -140,7 +160,7 @@ impl<'b, 'c, 'e, DeviceT> InterfaceBuilder<'b, 'c, 'e, DeviceT>
 | 
	
		
			
				|  |  |      /// This function panics if any of the addresses are not unicast.
 | 
	
		
			
				|  |  |      ///
 | 
	
		
			
				|  |  |      /// [ip_addrs]: struct.EthernetInterface.html#method.ip_addrs
 | 
	
		
			
				|  |  | -    pub fn ip_addrs<T>(mut self, ip_addrs: T) -> InterfaceBuilder<'b, 'c, 'e, DeviceT>
 | 
	
		
			
				|  |  | +    pub fn ip_addrs<T>(mut self, ip_addrs: T) -> Self
 | 
	
		
			
				|  |  |          where T: Into<ManagedSlice<'c, IpCidr>>
 | 
	
		
			
				|  |  |      {
 | 
	
		
			
				|  |  |          let ip_addrs = ip_addrs.into();
 | 
	
	
		
			
				|  | @@ -160,9 +180,26 @@ impl<'b, 'c, 'e, DeviceT> InterfaceBuilder<'b, 'c, 'e, DeviceT>
 | 
	
		
			
				|  |  |          self
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    /// Provide storage for multicast groups.
 | 
	
		
			
				|  |  | +    ///
 | 
	
		
			
				|  |  | +    /// Join multicast groups by calling [`join_multicast_group()`] on an `Interface`.
 | 
	
		
			
				|  |  | +    /// Using [`join_multicast_group()`] will send initial membership reports.
 | 
	
		
			
				|  |  | +    ///
 | 
	
		
			
				|  |  | +    /// A previously destroyed interface can be recreated by reusing the multicast group
 | 
	
		
			
				|  |  | +    /// storage, i.e. providing a non-empty storage to `ipv4_multicast_groups()`.
 | 
	
		
			
				|  |  | +    /// Note that this way initial membership reports are **not** sent.
 | 
	
		
			
				|  |  | +    ///
 | 
	
		
			
				|  |  | +    /// [`join_multicast_group()`]: struct.EthernetInterface.html#method.join_multicast_group
 | 
	
		
			
				|  |  | +    #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +    pub fn ipv4_multicast_groups<T>(mut self, ipv4_multicast_groups: T) -> Self
 | 
	
		
			
				|  |  | +        where T: Into<ManagedMap<'e, Ipv4Address, ()>>
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        self.ipv4_multicast_groups = ipv4_multicast_groups.into();
 | 
	
		
			
				|  |  | +        self
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      /// Set the Neighbor Cache the interface will use.
 | 
	
		
			
				|  |  | -    pub fn neighbor_cache(mut self, neighbor_cache: NeighborCache<'b>) ->
 | 
	
		
			
				|  |  | -                         InterfaceBuilder<'b, 'c, 'e, DeviceT> {
 | 
	
		
			
				|  |  | +    pub fn neighbor_cache(mut self, neighbor_cache: NeighborCache<'b>) -> Self {
 | 
	
		
			
				|  |  |          self.neighbor_cache = Some(neighbor_cache);
 | 
	
		
			
				|  |  |          self
 | 
	
		
			
				|  |  |      }
 | 
	
	
		
			
				|  | @@ -182,12 +219,19 @@ impl<'b, 'c, 'e, DeviceT> InterfaceBuilder<'b, 'c, 'e, DeviceT>
 | 
	
		
			
				|  |  |          match (self.ethernet_addr, self.neighbor_cache) {
 | 
	
		
			
				|  |  |              (Some(ethernet_addr), Some(neighbor_cache)) => {
 | 
	
		
			
				|  |  |                  let device_capabilities = self.device.capabilities();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |                  Interface {
 | 
	
		
			
				|  |  |                      device: self.device,
 | 
	
		
			
				|  |  |                      inner: InterfaceInner {
 | 
	
		
			
				|  |  |                          ethernet_addr, device_capabilities, neighbor_cache,
 | 
	
		
			
				|  |  |                          ip_addrs: self.ip_addrs,
 | 
	
		
			
				|  |  |                          routes: self.routes,
 | 
	
		
			
				|  |  | +                        #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +                        ipv4_multicast_groups: self.ipv4_multicast_groups,
 | 
	
		
			
				|  |  | +                        #[cfg(not(feature = "proto-igmp"))]
 | 
	
		
			
				|  |  | +                        _ipv4_multicast_groups:  PhantomData,
 | 
	
		
			
				|  |  | +                        #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +                        igmp_report_state: IgmpReportState::Inactive,
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              },
 | 
	
	
		
			
				|  | @@ -203,6 +247,8 @@ enum Packet<'a> {
 | 
	
		
			
				|  |  |      Arp(ArpRepr),
 | 
	
		
			
				|  |  |      #[cfg(feature = "proto-ipv4")]
 | 
	
		
			
				|  |  |      Icmpv4((Ipv4Repr, Icmpv4Repr<'a>)),
 | 
	
		
			
				|  |  | +    #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +    Igmp((Ipv4Repr, IgmpRepr)),
 | 
	
		
			
				|  |  |      #[cfg(feature = "proto-ipv6")]
 | 
	
		
			
				|  |  |      Icmpv6((Ipv6Repr, Icmpv6Repr<'a>)),
 | 
	
		
			
				|  |  |      #[cfg(feature = "socket-raw")]
 | 
	
	
		
			
				|  | @@ -221,6 +267,8 @@ impl<'a> Packet<'a> {
 | 
	
		
			
				|  |  |              &Packet::Arp(_) => None,
 | 
	
		
			
				|  |  |              #[cfg(feature = "proto-ipv4")]
 | 
	
		
			
				|  |  |              &Packet::Icmpv4((ref ipv4_repr, _)) => Some(ipv4_repr.dst_addr.into()),
 | 
	
		
			
				|  |  | +            #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +            &Packet::Igmp((ref ipv4_repr, _)) => Some(ipv4_repr.dst_addr.into()),
 | 
	
		
			
				|  |  |              #[cfg(feature = "proto-ipv6")]
 | 
	
		
			
				|  |  |              &Packet::Icmpv6((ref ipv6_repr, _)) => Some(ipv6_repr.dst_addr.into()),
 | 
	
		
			
				|  |  |              #[cfg(feature = "socket-raw")]
 | 
	
	
		
			
				|  | @@ -246,6 +294,22 @@ fn icmp_reply_payload_len(len: usize, mtu: usize, header_len: usize) -> usize {
 | 
	
		
			
				|  |  |      cmp::min(len, mtu - header_len * 2 - 8)
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +#[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +enum IgmpReportState {
 | 
	
		
			
				|  |  | +    Inactive,
 | 
	
		
			
				|  |  | +    ToGeneralQuery {
 | 
	
		
			
				|  |  | +        version:    IgmpVersion,
 | 
	
		
			
				|  |  | +        timeout:    Instant,
 | 
	
		
			
				|  |  | +        interval:   Duration,
 | 
	
		
			
				|  |  | +        next_index: usize
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    ToSpecificQuery {
 | 
	
		
			
				|  |  | +        version:    IgmpVersion,
 | 
	
		
			
				|  |  | +        timeout:    Instant,
 | 
	
		
			
				|  |  | +        group:      Ipv4Address
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  impl<'b, 'c, 'e, DeviceT> Interface<'b, 'c, 'e, DeviceT>
 | 
	
		
			
				|  |  |          where DeviceT: for<'d> Device<'d> {
 | 
	
		
			
				|  |  |      /// Get the Ethernet address of the interface.
 | 
	
	
		
			
				|  | @@ -262,6 +326,65 @@ impl<'b, 'c, 'e, DeviceT> Interface<'b, 'c, 'e, DeviceT>
 | 
	
		
			
				|  |  |          InterfaceInner::check_ethernet_addr(&self.inner.ethernet_addr);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    /// Add an address to a list of subscribed multicast IP addresses.
 | 
	
		
			
				|  |  | +    ///
 | 
	
		
			
				|  |  | +    /// Returns `Ok(announce_sent)` if the address was added successfully, where `annouce_sent`
 | 
	
		
			
				|  |  | +    /// indicates whether an initial immediate announcement has been sent.
 | 
	
		
			
				|  |  | +    pub fn join_multicast_group<T: Into<IpAddress>>(&mut self, addr: T, _timestamp: Instant) -> Result<bool> {
 | 
	
		
			
				|  |  | +        match addr.into() {
 | 
	
		
			
				|  |  | +            #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +            IpAddress::Ipv4(addr) => {
 | 
	
		
			
				|  |  | +                let is_not_new = self.inner.ipv4_multicast_groups.insert(addr, ())
 | 
	
		
			
				|  |  | +                    .map_err(|_| Error::Exhausted)?
 | 
	
		
			
				|  |  | +                    .is_some();
 | 
	
		
			
				|  |  | +                if is_not_new {
 | 
	
		
			
				|  |  | +                    Ok(false)
 | 
	
		
			
				|  |  | +                } else if let Some(pkt) =
 | 
	
		
			
				|  |  | +                        self.inner.igmp_report_packet(IgmpVersion::Version2, addr) {
 | 
	
		
			
				|  |  | +                    // Send initial membership report
 | 
	
		
			
				|  |  | +                    let tx_token = self.device.transmit().ok_or(Error::Exhausted)?;
 | 
	
		
			
				|  |  | +                    self.inner.dispatch(tx_token, _timestamp, pkt)?;
 | 
	
		
			
				|  |  | +                    Ok(true)
 | 
	
		
			
				|  |  | +                } else {
 | 
	
		
			
				|  |  | +                    Ok(false)
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            // Multicast is not yet implemented for other address families
 | 
	
		
			
				|  |  | +            _ => Err(Error::Unaddressable)
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /// Remove an address from the subscribed multicast IP addresses.
 | 
	
		
			
				|  |  | +    ///
 | 
	
		
			
				|  |  | +    /// Returns `Ok(leave_sent)` if the address was removed successfully, where `leave_sent`
 | 
	
		
			
				|  |  | +    /// indicates whether an immediate leave packet has been sent.
 | 
	
		
			
				|  |  | +    pub fn leave_multicast_group<T: Into<IpAddress>>(&mut self, addr: T, _timestamp: Instant) -> Result<bool> {
 | 
	
		
			
				|  |  | +        match addr.into() {
 | 
	
		
			
				|  |  | +            #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +            IpAddress::Ipv4(addr) => {
 | 
	
		
			
				|  |  | +                let was_not_present = self.inner.ipv4_multicast_groups.remove(&addr)
 | 
	
		
			
				|  |  | +                    .is_none();
 | 
	
		
			
				|  |  | +                if was_not_present {
 | 
	
		
			
				|  |  | +                    Ok(false)
 | 
	
		
			
				|  |  | +                } else if let Some(pkt) = self.inner.igmp_leave_packet(addr) {
 | 
	
		
			
				|  |  | +                    // Send group leave packet
 | 
	
		
			
				|  |  | +                    let tx_token = self.device.transmit().ok_or(Error::Exhausted)?;
 | 
	
		
			
				|  |  | +                    self.inner.dispatch(tx_token, _timestamp, pkt)?;
 | 
	
		
			
				|  |  | +                    Ok(true)
 | 
	
		
			
				|  |  | +                } else {
 | 
	
		
			
				|  |  | +                    Ok(false)
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            // Multicast is not yet implemented for other address families
 | 
	
		
			
				|  |  | +            _ => Err(Error::Unaddressable)
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /// Check whether the interface listens to given destination multicast IP address.
 | 
	
		
			
				|  |  | +    pub fn has_multicast_group<T: Into<IpAddress>>(&self, addr: T) -> bool {
 | 
	
		
			
				|  |  | +        self.inner.has_multicast_group(addr)
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      /// Get the IP addresses of the interface.
 | 
	
		
			
				|  |  |      pub fn ip_addrs(&self) -> &[IpCidr] {
 | 
	
		
			
				|  |  |          self.inner.ip_addrs.as_ref()
 | 
	
	
		
			
				|  | @@ -281,6 +404,12 @@ impl<'b, 'c, 'e, DeviceT> Interface<'b, 'c, 'e, DeviceT>
 | 
	
		
			
				|  |  |          self.inner.has_ip_addr(addr)
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    /// Get the first IPv4 address of the interface.
 | 
	
		
			
				|  |  | +    #[cfg(feature = "proto-ipv4")]
 | 
	
		
			
				|  |  | +    pub fn ipv4_address(&self) -> Option<Ipv4Address> {
 | 
	
		
			
				|  |  | +        self.inner.ipv4_address()
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      pub fn routes(&self) -> &Routes<'e> {
 | 
	
		
			
				|  |  |          &self.inner.routes
 | 
	
		
			
				|  |  |      }
 | 
	
	
		
			
				|  | @@ -311,6 +440,10 @@ impl<'b, 'c, 'e, DeviceT> Interface<'b, 'c, 'e, DeviceT>
 | 
	
		
			
				|  |  |          loop {
 | 
	
		
			
				|  |  |              let processed_any = self.socket_ingress(sockets, timestamp)?;
 | 
	
		
			
				|  |  |              let emitted_any   = self.socket_egress(sockets, timestamp)?;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +            self.igmp_egress(timestamp)?;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |              if processed_any || emitted_any {
 | 
	
		
			
				|  |  |                  readiness_may_have_changed = true;
 | 
	
		
			
				|  |  |              } else {
 | 
	
	
		
			
				|  | @@ -463,6 +596,54 @@ impl<'b, 'c, 'e, DeviceT> Interface<'b, 'c, 'e, DeviceT>
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |          Ok(emitted_any)
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /// Depending on `igmp_report_state` and the therein contained
 | 
	
		
			
				|  |  | +    /// timeouts, send IGMP membership reports.
 | 
	
		
			
				|  |  | +    #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +    fn igmp_egress(&mut self, timestamp: Instant) -> Result<bool> {
 | 
	
		
			
				|  |  | +        match self.inner.igmp_report_state {
 | 
	
		
			
				|  |  | +            IgmpReportState::ToSpecificQuery { version, timeout, group }
 | 
	
		
			
				|  |  | +                    if timestamp >= timeout => {
 | 
	
		
			
				|  |  | +                if let Some(pkt) = self.inner.igmp_report_packet(version, group) {
 | 
	
		
			
				|  |  | +                    // Send initial membership report
 | 
	
		
			
				|  |  | +                    let tx_token = self.device.transmit().ok_or(Error::Exhausted)?;
 | 
	
		
			
				|  |  | +                    self.inner.dispatch(tx_token, timestamp, pkt)?;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                self.inner.igmp_report_state = IgmpReportState::Inactive;
 | 
	
		
			
				|  |  | +                Ok(true)
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            IgmpReportState::ToGeneralQuery { version, timeout, interval, next_index }
 | 
	
		
			
				|  |  | +                    if timestamp >= timeout => {
 | 
	
		
			
				|  |  | +                let addr = self.inner.ipv4_multicast_groups
 | 
	
		
			
				|  |  | +                    .iter()
 | 
	
		
			
				|  |  | +                    .nth(next_index)
 | 
	
		
			
				|  |  | +                    .map(|(addr, ())| *addr);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                match addr {
 | 
	
		
			
				|  |  | +                    Some(addr) => {
 | 
	
		
			
				|  |  | +                        if let Some(pkt) = self.inner.igmp_report_packet(version, addr) {
 | 
	
		
			
				|  |  | +                            // Send initial membership report
 | 
	
		
			
				|  |  | +                            let tx_token = self.device.transmit().ok_or(Error::Exhausted)?;
 | 
	
		
			
				|  |  | +                            self.inner.dispatch(tx_token, timestamp, pkt)?;
 | 
	
		
			
				|  |  | +                        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                        let next_timeout = (timeout + interval).max(timestamp);
 | 
	
		
			
				|  |  | +                        self.inner.igmp_report_state = IgmpReportState::ToGeneralQuery {
 | 
	
		
			
				|  |  | +                            version, timeout: next_timeout, interval, next_index: next_index + 1
 | 
	
		
			
				|  |  | +                        };
 | 
	
		
			
				|  |  | +                        Ok(true)
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                    None => {
 | 
	
		
			
				|  |  | +                        self.inner.igmp_report_state = IgmpReportState::Inactive;
 | 
	
		
			
				|  |  | +                        Ok(false)
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            _ => Ok(false)
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  impl<'b, 'c, 'e> InterfaceInner<'b, 'c, 'e> {
 | 
	
	
		
			
				|  | @@ -505,16 +686,44 @@ impl<'b, 'c, 'e> InterfaceInner<'b, 'c, 'e> {
 | 
	
		
			
				|  |  |          self.ip_addrs.iter().any(|probe| probe.address() == addr)
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    /// Get the first IPv4 address of the interface.
 | 
	
		
			
				|  |  | +    #[cfg(feature = "proto-ipv4")]
 | 
	
		
			
				|  |  | +    pub fn ipv4_address(&self) -> Option<Ipv4Address> {
 | 
	
		
			
				|  |  | +        self.ip_addrs.iter()
 | 
	
		
			
				|  |  | +            .filter_map(
 | 
	
		
			
				|  |  | +                |addr| match addr {
 | 
	
		
			
				|  |  | +                    &IpCidr::Ipv4(cidr) => Some(cidr.address()),
 | 
	
		
			
				|  |  | +                    _ => None,
 | 
	
		
			
				|  |  | +                })
 | 
	
		
			
				|  |  | +            .next()
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /// Check whether the interface listens to given destination multicast IP address.
 | 
	
		
			
				|  |  | +    ///
 | 
	
		
			
				|  |  | +    /// If built without feature `proto-igmp` this function will
 | 
	
		
			
				|  |  | +    /// always return `false`.
 | 
	
		
			
				|  |  | +    pub fn has_multicast_group<T: Into<IpAddress>>(&self, addr: T) -> bool {
 | 
	
		
			
				|  |  | +        match addr.into() {
 | 
	
		
			
				|  |  | +            #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +            IpAddress::Ipv4(key) =>
 | 
	
		
			
				|  |  | +                key == Ipv4Address::MULTICAST_ALL_SYSTEMS ||
 | 
	
		
			
				|  |  | +                self.ipv4_multicast_groups.get(&key).is_some(),
 | 
	
		
			
				|  |  | +            _ =>
 | 
	
		
			
				|  |  | +                false,
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      fn process_ethernet<'frame, T: AsRef<[u8]>>
 | 
	
		
			
				|  |  |                         (&mut self, sockets: &mut SocketSet, timestamp: Instant, frame: &'frame T) ->
 | 
	
		
			
				|  |  |                         Result<Packet<'frame>>
 | 
	
		
			
				|  |  |      {
 | 
	
		
			
				|  |  |          let eth_frame = EthernetFrame::new_checked(frame)?;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        // Ignore any packets not directed to our hardware address.
 | 
	
		
			
				|  |  | +        // Ignore any packets not directed to our hardware address or any of the multicast groups.
 | 
	
		
			
				|  |  |          if !eth_frame.dst_addr().is_broadcast() &&
 | 
	
		
			
				|  |  |             !eth_frame.dst_addr().is_multicast() &&
 | 
	
		
			
				|  |  | -                eth_frame.dst_addr() != self.ethernet_addr {
 | 
	
		
			
				|  |  | +           eth_frame.dst_addr() != self.ethernet_addr
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  |              return Ok(Packet::None)
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -705,10 +914,8 @@ impl<'b, 'c, 'e> InterfaceInner<'b, 'c, 'e> {
 | 
	
		
			
				|  |  |          #[cfg(feature = "socket-raw")]
 | 
	
		
			
				|  |  |          let handled_by_raw_socket = self.raw_socket_filter(sockets, &ip_repr, ip_payload);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        if !ipv4_repr.dst_addr.is_broadcast() &&
 | 
	
		
			
				|  |  | -           !ipv4_repr.dst_addr.is_multicast() &&
 | 
	
		
			
				|  |  | -           !self.has_ip_addr(ipv4_repr.dst_addr) {
 | 
	
		
			
				|  |  | -            // Ignore IP packets not directed at us.
 | 
	
		
			
				|  |  | +        if !self.has_ip_addr(ipv4_repr.dst_addr) && !self.has_multicast_group(ipv4_repr.dst_addr) {
 | 
	
		
			
				|  |  | +            // Ignore IP packets not directed at us or any of the multicast groups
 | 
	
		
			
				|  |  |              return Ok(Packet::None)
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -716,6 +923,10 @@ impl<'b, 'c, 'e> InterfaceInner<'b, 'c, 'e> {
 | 
	
		
			
				|  |  |              IpProtocol::Icmp =>
 | 
	
		
			
				|  |  |                  self.process_icmpv4(sockets, ip_repr, ip_payload),
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +            #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +            IpProtocol::Igmp =>
 | 
	
		
			
				|  |  | +                self.process_igmp(timestamp, ipv4_repr, ip_payload),
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |              #[cfg(feature = "socket-udp")]
 | 
	
		
			
				|  |  |              IpProtocol::Udp =>
 | 
	
		
			
				|  |  |                  self.process_udp(sockets, ip_repr, ip_payload),
 | 
	
	
		
			
				|  | @@ -742,6 +953,60 @@ impl<'b, 'c, 'e> InterfaceInner<'b, 'c, 'e> {
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    /// Host duties of the **IGMPv2** protocol.
 | 
	
		
			
				|  |  | +    ///
 | 
	
		
			
				|  |  | +    /// Sets up `igmp_report_state` for responding to IGMP general/specific membership queries.
 | 
	
		
			
				|  |  | +    /// Membership must not be reported immediately in order to avoid flooding the network
 | 
	
		
			
				|  |  | +    /// after a query is broadcasted by a router; this is not currently done.
 | 
	
		
			
				|  |  | +    #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +    fn process_igmp<'frame>(&mut self, timestamp: Instant, ipv4_repr: Ipv4Repr,
 | 
	
		
			
				|  |  | +                            ip_payload: &'frame [u8]) -> Result<Packet<'frame>> {
 | 
	
		
			
				|  |  | +        let igmp_packet = IgmpPacket::new_checked(ip_payload)?;
 | 
	
		
			
				|  |  | +        let igmp_repr = IgmpRepr::parse(&igmp_packet)?;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // FIXME: report membership after a delay
 | 
	
		
			
				|  |  | +        match igmp_repr {
 | 
	
		
			
				|  |  | +            IgmpRepr::MembershipQuery { group_addr, version, max_resp_time } => {
 | 
	
		
			
				|  |  | +                // General query
 | 
	
		
			
				|  |  | +                if group_addr.is_unspecified() &&
 | 
	
		
			
				|  |  | +                        ipv4_repr.dst_addr == Ipv4Address::MULTICAST_ALL_SYSTEMS {
 | 
	
		
			
				|  |  | +                    // Are we member in any groups?
 | 
	
		
			
				|  |  | +                    if self.ipv4_multicast_groups.iter().next().is_some() {
 | 
	
		
			
				|  |  | +                        let interval = match version {
 | 
	
		
			
				|  |  | +                            IgmpVersion::Version1 =>
 | 
	
		
			
				|  |  | +                                Duration::from_millis(100),
 | 
	
		
			
				|  |  | +                            IgmpVersion::Version2 => {
 | 
	
		
			
				|  |  | +                                // No dependence on a random generator
 | 
	
		
			
				|  |  | +                                // (see [#24](https://github.com/m-labs/smoltcp/issues/24))
 | 
	
		
			
				|  |  | +                                // but at least spread reports evenly across max_resp_time.
 | 
	
		
			
				|  |  | +                                let intervals = self.ipv4_multicast_groups.len() as u32 + 1;
 | 
	
		
			
				|  |  | +                                max_resp_time / intervals
 | 
	
		
			
				|  |  | +                            }
 | 
	
		
			
				|  |  | +                        };
 | 
	
		
			
				|  |  | +                        self.igmp_report_state = IgmpReportState::ToGeneralQuery {
 | 
	
		
			
				|  |  | +                            version, timeout: timestamp + interval, interval, next_index: 0
 | 
	
		
			
				|  |  | +                        };
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                } else {
 | 
	
		
			
				|  |  | +                    // Group-specific query
 | 
	
		
			
				|  |  | +                    if self.has_multicast_group(group_addr) && ipv4_repr.dst_addr == group_addr {
 | 
	
		
			
				|  |  | +                        // Don't respond immediately
 | 
	
		
			
				|  |  | +                        let timeout = max_resp_time / 4;
 | 
	
		
			
				|  |  | +                        self.igmp_report_state = IgmpReportState::ToSpecificQuery {
 | 
	
		
			
				|  |  | +                            version, timeout: timestamp + timeout, group: group_addr
 | 
	
		
			
				|  |  | +                        };
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            },
 | 
	
		
			
				|  |  | +            // Ignore membership reports
 | 
	
		
			
				|  |  | +            IgmpRepr::MembershipReport { .. } => (),
 | 
	
		
			
				|  |  | +            // Ignore hosts leaving groups
 | 
	
		
			
				|  |  | +            IgmpRepr::LeaveGroup{ .. } => (),
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        Ok(Packet::None)
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      #[cfg(feature = "proto-ipv6")]
 | 
	
		
			
				|  |  |      fn process_icmpv6<'frame>(&mut self, _sockets: &mut SocketSet, timestamp: Instant,
 | 
	
		
			
				|  |  |                                ip_repr: IpRepr, ip_payload: &'frame [u8]) -> Result<Packet<'frame>>
 | 
	
	
		
			
				|  | @@ -1087,6 +1352,12 @@ impl<'b, 'c, 'e> InterfaceInner<'b, 'c, 'e> {
 | 
	
		
			
				|  |  |                      icmpv4_repr.emit(&mut Icmpv4Packet::new_unchecked(payload), &checksum_caps);
 | 
	
		
			
				|  |  |                  })
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | +            #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +            Packet::Igmp((ipv4_repr, igmp_repr)) => {
 | 
	
		
			
				|  |  | +                self.dispatch_ip(tx_token, timestamp, IpRepr::Ipv4(ipv4_repr), |_ip_repr, payload| {
 | 
	
		
			
				|  |  | +                    igmp_repr.emit(&mut IgmpPacket::new_unchecked(payload));
 | 
	
		
			
				|  |  | +                })
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |              #[cfg(feature = "proto-ipv6")]
 | 
	
		
			
				|  |  |              Packet::Icmpv6((ipv6_repr, icmpv6_repr)) => {
 | 
	
		
			
				|  |  |                  self.dispatch_ip(tx_token, timestamp, IpRepr::Ipv6(ipv6_repr),
 | 
	
	
		
			
				|  | @@ -1167,7 +1438,7 @@ impl<'b, 'c, 'e> InterfaceInner<'b, 'c, 'e> {
 | 
	
		
			
				|  |  |      fn route(&self, addr: &IpAddress, timestamp: Instant) -> Result<IpAddress> {
 | 
	
		
			
				|  |  |          // Send directly.
 | 
	
		
			
				|  |  |          if self.in_same_network(addr) || addr.is_broadcast() {
 | 
	
		
			
				|  |  | -            return Ok(addr.clone())
 | 
	
		
			
				|  |  | +            return Ok(*addr)
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          // Route via a router.
 | 
	
	
		
			
				|  | @@ -1319,16 +1590,55 @@ impl<'b, 'c, 'e> InterfaceInner<'b, 'c, 'e> {
 | 
	
		
			
				|  |  |              f(ip_repr, payload)
 | 
	
		
			
				|  |  |          })
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +    fn igmp_report_packet<'any>(&self, version: IgmpVersion, group_addr: Ipv4Address) -> Option<Packet<'any>> {
 | 
	
		
			
				|  |  | +        let iface_addr = self.ipv4_address()?;
 | 
	
		
			
				|  |  | +        let igmp_repr = IgmpRepr::MembershipReport {
 | 
	
		
			
				|  |  | +            group_addr,
 | 
	
		
			
				|  |  | +            version,
 | 
	
		
			
				|  |  | +        };
 | 
	
		
			
				|  |  | +        let pkt = Packet::Igmp((Ipv4Repr {
 | 
	
		
			
				|  |  | +            src_addr:    iface_addr,
 | 
	
		
			
				|  |  | +            // Send to the group being reported
 | 
	
		
			
				|  |  | +            dst_addr:    group_addr,
 | 
	
		
			
				|  |  | +            protocol:    IpProtocol::Igmp,
 | 
	
		
			
				|  |  | +            payload_len: igmp_repr.buffer_len(),
 | 
	
		
			
				|  |  | +            hop_limit:   1,
 | 
	
		
			
				|  |  | +            // TODO: add Router Alert IPv4 header option. See
 | 
	
		
			
				|  |  | +            // [#183](https://github.com/m-labs/smoltcp/issues/183).
 | 
	
		
			
				|  |  | +        }, igmp_repr));
 | 
	
		
			
				|  |  | +        Some(pkt)
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +    fn igmp_leave_packet<'any>(&self, group_addr: Ipv4Address) -> Option<Packet<'any>> {
 | 
	
		
			
				|  |  | +        self.ipv4_address().map(|iface_addr| {
 | 
	
		
			
				|  |  | +            let igmp_repr = IgmpRepr::LeaveGroup { group_addr };
 | 
	
		
			
				|  |  | +            let pkt = Packet::Igmp((Ipv4Repr {
 | 
	
		
			
				|  |  | +                src_addr:    iface_addr,
 | 
	
		
			
				|  |  | +                dst_addr:    Ipv4Address::MULTICAST_ALL_ROUTERS,
 | 
	
		
			
				|  |  | +                protocol:    IpProtocol::Igmp,
 | 
	
		
			
				|  |  | +                payload_len: igmp_repr.buffer_len(),
 | 
	
		
			
				|  |  | +                hop_limit:   1,
 | 
	
		
			
				|  |  | +            }, igmp_repr));
 | 
	
		
			
				|  |  | +            pkt
 | 
	
		
			
				|  |  | +        })
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #[cfg(test)]
 | 
	
		
			
				|  |  |  mod test {
 | 
	
		
			
				|  |  | +    #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +    use std::vec::Vec;
 | 
	
		
			
				|  |  |      use std::collections::BTreeMap;
 | 
	
		
			
				|  |  |      use {Result, Error};
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      use super::InterfaceBuilder;
 | 
	
		
			
				|  |  |      use iface::{NeighborCache, EthernetInterface};
 | 
	
		
			
				|  |  |      use phy::{self, Loopback, ChecksumCapabilities};
 | 
	
		
			
				|  |  | +    #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +    use phy::{Device, RxToken, TxToken};
 | 
	
		
			
				|  |  |      use time::Instant;
 | 
	
		
			
				|  |  |      use socket::SocketSet;
 | 
	
		
			
				|  |  |      #[cfg(feature = "proto-ipv4")]
 | 
	
	
		
			
				|  | @@ -1337,8 +1647,12 @@ mod test {
 | 
	
		
			
				|  |  |      use wire::{IpAddress, IpCidr, IpProtocol, IpRepr};
 | 
	
		
			
				|  |  |      #[cfg(feature = "proto-ipv4")]
 | 
	
		
			
				|  |  |      use wire::{Ipv4Address, Ipv4Repr};
 | 
	
		
			
				|  |  | +    #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +    use wire::Ipv4Packet;
 | 
	
		
			
				|  |  |      #[cfg(feature = "proto-ipv4")]
 | 
	
		
			
				|  |  |      use wire::{Icmpv4Repr, Icmpv4DstUnreachable};
 | 
	
		
			
				|  |  | +    #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +    use wire::{IgmpPacket, IgmpRepr, IgmpVersion};
 | 
	
		
			
				|  |  |      #[cfg(all(feature = "socket-udp", any(feature = "proto-ipv4", feature = "proto-ipv6")))]
 | 
	
		
			
				|  |  |      use wire::{UdpPacket, UdpRepr};
 | 
	
		
			
				|  |  |      #[cfg(feature = "proto-ipv6")]
 | 
	
	
		
			
				|  | @@ -1365,15 +1679,31 @@ mod test {
 | 
	
		
			
				|  |  |              IpCidr::new(IpAddress::v6(0xfdbe, 0, 0, 0, 0, 0, 0, 1), 64),
 | 
	
		
			
				|  |  |          ];
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        let iface = InterfaceBuilder::new(device)
 | 
	
		
			
				|  |  | -                .ethernet_addr(EthernetAddress::default())
 | 
	
		
			
				|  |  | -                .neighbor_cache(NeighborCache::new(BTreeMap::new()))
 | 
	
		
			
				|  |  | -                .ip_addrs(ip_addrs)
 | 
	
		
			
				|  |  | -                .finalize();
 | 
	
		
			
				|  |  | +        let iface_builder = InterfaceBuilder::new(device)
 | 
	
		
			
				|  |  | +            .ethernet_addr(EthernetAddress::default())
 | 
	
		
			
				|  |  | +            .neighbor_cache(NeighborCache::new(BTreeMap::new()))
 | 
	
		
			
				|  |  | +            .ip_addrs(ip_addrs);
 | 
	
		
			
				|  |  | +        #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +        let iface_builder = iface_builder
 | 
	
		
			
				|  |  | +            .ipv4_multicast_groups(BTreeMap::new());
 | 
	
		
			
				|  |  | +        let iface = iface_builder
 | 
	
		
			
				|  |  | +            .finalize();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          (iface, SocketSet::new(vec![]))
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +    fn recv_all<'b>(iface: &mut EthernetInterface<'static, 'b, 'static, Loopback>, timestamp: Instant) -> Vec<Vec<u8>> {
 | 
	
		
			
				|  |  | +        let mut pkts = Vec::new();
 | 
	
		
			
				|  |  | +        while let Some((rx, _tx)) = iface.device.receive() {
 | 
	
		
			
				|  |  | +            rx.consume(timestamp, |pkt| {
 | 
	
		
			
				|  |  | +                pkts.push(pkt.iter().cloned().collect());
 | 
	
		
			
				|  |  | +                Ok(())
 | 
	
		
			
				|  |  | +            }).unwrap();
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        pkts
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      #[derive(Debug, PartialEq)]
 | 
	
		
			
				|  |  |      struct MockTxToken;
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -2043,4 +2373,92 @@ mod test {
 | 
	
		
			
				|  |  |              &IpAddress::Ipv6(remote_ip_addr)),
 | 
	
		
			
				|  |  |              Ok((remote_hw_addr, MockTxToken)));
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    #[test]
 | 
	
		
			
				|  |  | +    #[cfg(feature = "proto-igmp")]
 | 
	
		
			
				|  |  | +    fn test_handle_igmp() {
 | 
	
		
			
				|  |  | +        fn recv_igmp<'b>(mut iface: &mut EthernetInterface<'static, 'b, 'static, Loopback>, timestamp: Instant) -> Vec<(Ipv4Repr, IgmpRepr)> {
 | 
	
		
			
				|  |  | +            let checksum_caps = &iface.device.capabilities().checksum;
 | 
	
		
			
				|  |  | +            recv_all(&mut iface, timestamp)
 | 
	
		
			
				|  |  | +                .iter()
 | 
	
		
			
				|  |  | +                .filter_map(|frame| {
 | 
	
		
			
				|  |  | +                    let eth_frame = EthernetFrame::new_checked(frame).ok()?;
 | 
	
		
			
				|  |  | +                    let ipv4_packet = Ipv4Packet::new_checked(eth_frame.payload()).ok()?;
 | 
	
		
			
				|  |  | +                    let ipv4_repr = Ipv4Repr::parse(&ipv4_packet, &checksum_caps).ok()?;
 | 
	
		
			
				|  |  | +                    let ip_payload = ipv4_packet.payload();
 | 
	
		
			
				|  |  | +                    let igmp_packet = IgmpPacket::new_checked(ip_payload).ok()?;
 | 
	
		
			
				|  |  | +                    let igmp_repr = IgmpRepr::parse(&igmp_packet).ok()?;
 | 
	
		
			
				|  |  | +                    Some((ipv4_repr, igmp_repr))
 | 
	
		
			
				|  |  | +                })
 | 
	
		
			
				|  |  | +                .collect::<Vec<_>>()
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        let groups = [
 | 
	
		
			
				|  |  | +            Ipv4Address::new(224, 0, 0, 22),
 | 
	
		
			
				|  |  | +            Ipv4Address::new(224, 0, 0, 56),
 | 
	
		
			
				|  |  | +        ];
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        let (mut iface, mut socket_set) = create_loopback();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // Join multicast groups
 | 
	
		
			
				|  |  | +        let timestamp = Instant::now();
 | 
	
		
			
				|  |  | +        for group in &groups {
 | 
	
		
			
				|  |  | +            iface.join_multicast_group(*group, timestamp)
 | 
	
		
			
				|  |  | +                .unwrap();
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        let reports = recv_igmp(&mut iface, timestamp);
 | 
	
		
			
				|  |  | +        assert_eq!(reports.len(), 2);
 | 
	
		
			
				|  |  | +        for (i, group_addr) in groups.iter().enumerate() {
 | 
	
		
			
				|  |  | +            assert_eq!(reports[i].0.protocol, IpProtocol::Igmp);
 | 
	
		
			
				|  |  | +            assert_eq!(reports[i].0.dst_addr, *group_addr);
 | 
	
		
			
				|  |  | +            assert_eq!(reports[i].1, IgmpRepr::MembershipReport {
 | 
	
		
			
				|  |  | +                group_addr: *group_addr,
 | 
	
		
			
				|  |  | +                version: IgmpVersion::Version2,
 | 
	
		
			
				|  |  | +            });
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // General query
 | 
	
		
			
				|  |  | +        let timestamp = Instant::now();
 | 
	
		
			
				|  |  | +        const GENERAL_QUERY_BYTES: &[u8] = &[
 | 
	
		
			
				|  |  | +            0x01, 0x00, 0x5e, 0x00, 0x00, 0x01, 0x0a, 0x14,
 | 
	
		
			
				|  |  | +            0x48, 0x01, 0x21, 0x01, 0x08, 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
 | 
	
		
			
				|  |  | +        ];
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            // Transmit GENERAL_QUERY_BYTES into loopback
 | 
	
		
			
				|  |  | +            let tx_token = iface.device.transmit().unwrap();
 | 
	
		
			
				|  |  | +            tx_token.consume(
 | 
	
		
			
				|  |  | +                timestamp, GENERAL_QUERY_BYTES.len(),
 | 
	
		
			
				|  |  | +                |buffer| {
 | 
	
		
			
				|  |  | +                    buffer.copy_from_slice(GENERAL_QUERY_BYTES);
 | 
	
		
			
				|  |  | +                    Ok(())
 | 
	
		
			
				|  |  | +                }).unwrap();
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        // Trigger processing until all packets received through the
 | 
	
		
			
				|  |  | +        // loopback have been processed, including responses to
 | 
	
		
			
				|  |  | +        // GENERAL_QUERY_BYTES. Therefore `recv_all()` would return 0
 | 
	
		
			
				|  |  | +        // pkts that could be checked.
 | 
	
		
			
				|  |  | +        iface.socket_ingress(&mut socket_set, timestamp).unwrap();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // Leave multicast groups
 | 
	
		
			
				|  |  | +        let timestamp = Instant::now();
 | 
	
		
			
				|  |  | +        for group in &groups {
 | 
	
		
			
				|  |  | +            iface.leave_multicast_group(group.clone(), timestamp)
 | 
	
		
			
				|  |  | +                .unwrap();
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        let leaves = recv_igmp(&mut iface, timestamp);
 | 
	
		
			
				|  |  | +        assert_eq!(leaves.len(), 2);
 | 
	
		
			
				|  |  | +        for (i, group_addr) in groups.iter().cloned().enumerate() {
 | 
	
		
			
				|  |  | +            assert_eq!(leaves[i].0.protocol, IpProtocol::Igmp);
 | 
	
		
			
				|  |  | +            assert_eq!(leaves[i].0.dst_addr, Ipv4Address::MULTICAST_ALL_ROUTERS);
 | 
	
		
			
				|  |  | +            assert_eq!(leaves[i].1, IgmpRepr::LeaveGroup { group_addr });
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  }
 |