Implement IGMPv1/v2 processing.

Closes: #178
Approved by: whitequark
v0.7.x
Astro 2018-03-05 17:10:22 +01:00 committed by Homu
parent 06c7e6eb54
commit a8f2725784
9 changed files with 593 additions and 35 deletions

View File

@ -16,7 +16,7 @@ matrix:
- rust: nightly
env: FEATURES='std phy-tap_interface proto-ipv6 socket-udp' MODE='test'
- rust: nightly
env: FEATURES='std proto-ipv4 socket-raw' MODE='test'
env: FEATURES='std proto-ipv4 proto-igmp socket-raw' MODE='test'
- rust: nightly
env: FEATURES='std proto-ipv6 socket-udp' MODE='test'
- rust: nightly
@ -33,7 +33,7 @@ matrix:
env: FEATURES='proto-ipv4 proto-ipv6 socket-raw socket-udp socket-tcp socket-icmp alloc'
MODE='test'
- rust: nightly
env: FEATURES='proto-ipv4 proto-ipv6 socket-raw socket-udp socket-tcp socket-icmp'
env: FEATURES='proto-ipv4 proto-ipv6 proto-igmp socket-raw socket-udp socket-tcp socket-icmp'
MODE='build'
- rust: nightly
env: MODE='fuzz run' ARGS='packet_parser -- -max_len=1536 -max_total_time=30'

View File

@ -34,6 +34,7 @@ verbose = []
"phy-raw_socket" = ["std", "libc"]
"phy-tap_interface" = ["std", "libc"]
"proto-ipv4" = []
"proto-igmp" = ["proto-ipv4"]
"proto-ipv6" = []
"socket-raw" = []
"socket-udp" = []
@ -43,7 +44,7 @@ verbose = []
default = [
"std", "log", # needed for `cargo test --no-default-features --features default` :/
"phy-raw_socket", "phy-tap_interface",
"proto-ipv4", "proto-ipv6",
"proto-ipv4", "proto-igmp", "proto-ipv6",
"socket-raw", "socket-icmp", "socket-udp", "socket-tcp"
]
@ -76,9 +77,13 @@ required-features = ["std", "phy-tap_interface", "proto-ipv4", "socket-tcp", "so
name = "loopback"
required-features = ["log", "proto-ipv4", "socket-tcp"]
[[example]]
name = "multicast"
required-features = ["std", "phy-tap_interface", "proto-ipv4", "proto-igmp", "socket-udp"]
[[example]]
name = "benchmark"
required-features = ["std", "phy-tap_interface", "proto-ipv4", "socket-tcp"]
required-features = ["std", "phy-tap_interface", "proto-ipv4", "socket-raw", "socket-udp"]
[profile.release]
debug = 2

View File

@ -47,6 +47,16 @@ The only supported medium is Ethernet.
* IPv6 default gateway is **not** supported.
* IPv6 extension headers are **not** supported.
### IP multicast
#### IGMP
The IGMPv1 and IGMPv2 protocols are supported, and IPv4 multicast is available.
* Membership reports are sent in response to membership queries at
equal intervals equal to the maximum response time divided by the
number of groups to be reported.
### ICMP layer
#### ICMPv4

113
examples/multicast.rs Normal file
View File

@ -0,0 +1,113 @@
#[macro_use]
extern crate log;
extern crate env_logger;
extern crate getopts;
extern crate smoltcp;
extern crate byteorder;
mod utils;
use std::collections::BTreeMap;
use std::os::unix::io::AsRawFd;
use smoltcp::phy::wait as phy_wait;
use smoltcp::wire::{EthernetAddress, IpVersion, IpProtocol, IpAddress, IpCidr, Ipv4Address,
Ipv4Packet, IgmpPacket, IgmpRepr};
use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder};
use smoltcp::socket::{SocketSet,
RawSocket, RawSocketBuffer, RawPacketMetadata,
UdpSocket, UdpSocketBuffer, UdpPacketMetadata};
use smoltcp::time::Instant;
const MDNS_PORT: u16 = 5353;
const MDNS_GROUP: [u8; 4] = [224, 0, 0, 251];
fn main() {
utils::setup_logging("warn");
let (mut opts, mut free) = utils::create_options();
utils::add_tap_options(&mut opts, &mut free);
utils::add_middleware_options(&mut opts, &mut free);
let mut matches = utils::parse_options(&opts, free);
let device = utils::parse_tap_options(&mut matches);
let fd = device.as_raw_fd();
let device = utils::parse_middleware_options(&mut matches,
device,
/*loopback=*/
false);
let neighbor_cache = NeighborCache::new(BTreeMap::new());
let local_addr = Ipv4Address::new(192, 168, 69, 2);
let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]);
let ip_addr = IpCidr::new(IpAddress::from(local_addr), 24);
let mut ipv4_multicast_storage = [None; 1];
let mut iface = EthernetInterfaceBuilder::new(device)
.ethernet_addr(ethernet_addr)
.neighbor_cache(neighbor_cache)
.ip_addrs([ip_addr])
.ipv4_multicast_groups(&mut ipv4_multicast_storage[..])
.finalize();
let now = Instant::now();
// Join a multicast group to receive mDNS traffic
iface.join_multicast_group(Ipv4Address::from_bytes(&MDNS_GROUP), now).unwrap();
let mut sockets = SocketSet::new(vec![]);
// Must fit at least one IGMP packet
let raw_rx_buffer = RawSocketBuffer::new(vec![RawPacketMetadata::EMPTY; 2], vec![0; 512]);
// Will not send IGMP
let raw_tx_buffer = RawSocketBuffer::new(vec![], vec![]);
let raw_socket = RawSocket::new(
IpVersion::Ipv4, IpProtocol::Igmp,
raw_rx_buffer, raw_tx_buffer
);
let raw_handle = sockets.add(raw_socket);
// Must fit mDNS payload of at least one packet
let udp_rx_buffer = UdpSocketBuffer::new(vec![UdpPacketMetadata::EMPTY; 4], vec![0; 1024]);
// Will not send mDNS
let udp_tx_buffer = UdpSocketBuffer::new(vec![UdpPacketMetadata::EMPTY], vec![0; 0]);
let udp_socket = UdpSocket::new(udp_rx_buffer, udp_tx_buffer);
let udp_handle = sockets.add(udp_socket);
loop {
let timestamp = Instant::now();
match iface.poll(&mut sockets, timestamp) {
Ok(_) => {},
Err(e) => {
debug!("poll error: {}",e);
}
}
{
let mut socket = sockets.get::<RawSocket>(raw_handle);
if socket.can_recv() {
// For display purposes only - normally we wouldn't process incoming IGMP packets
// in the application layer
socket.recv()
.and_then(|payload| Ipv4Packet::new_checked(payload))
.and_then(|ipv4_packet| IgmpPacket::new_checked(ipv4_packet.payload()))
.and_then(|igmp_packet| IgmpRepr::parse(&igmp_packet))
.map(|igmp_repr| println!("IGMP packet: {:?}", igmp_repr))
.unwrap_or_else(|e| println!("Recv IGMP error: {:?}", e));
}
}
{
let mut socket = sockets.get::<UdpSocket>(udp_handle);
if !socket.is_open() {
socket.bind(MDNS_PORT).unwrap()
}
if socket.can_recv() {
socket.recv()
.map(|(data, sender)| println!("mDNS traffic: {} UDP bytes from {}", data.len(), sender))
.unwrap_or_else(|e| println!("Recv UDP error: {:?}", e));
}
}
phy_wait(fd, iface.poll_delay(&sockets, timestamp)).expect("wait error");
}
}

View File

@ -18,6 +18,7 @@ use smoltcp::phy::{Device, EthernetTracer, FaultInjector};
#[cfg(feature = "phy-tap_interface")]
use smoltcp::phy::TapInterface;
use smoltcp::phy::{PcapWriter, PcapSink, PcapMode, PcapLinkType};
use smoltcp::phy::RawSocket;
use smoltcp::time::{Duration, Instant};
#[cfg(feature = "log")]
@ -87,6 +88,11 @@ pub fn parse_tap_options(matches: &mut Matches) -> TapInterface {
TapInterface::new(&interface).unwrap()
}
pub fn parse_raw_socket_options(matches: &mut Matches) -> RawSocket {
let interface = matches.free.remove(0);
RawSocket::new(&interface).unwrap()
}
pub fn add_middleware_options(opts: &mut Options, _free: &mut Vec<&str>) {
opts.optopt("", "pcap", "Write a packet capture file", "FILE");
opts.optopt("", "drop-chance", "Chance of dropping a packet (%)", "CHANCE");

View File

@ -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 });
}
}
}

View File

@ -154,13 +154,13 @@ impl<'a, 'b> RawSocket<'a, 'b> {
Result<()>
where F: FnOnce((IpRepr, &[u8])) -> Result<()> {
fn prepare<'a>(protocol: IpProtocol, buffer: &'a mut [u8],
checksum_caps: &ChecksumCapabilities) -> Result<(IpRepr, &'a [u8])> {
_checksum_caps: &ChecksumCapabilities) -> Result<(IpRepr, &'a [u8])> {
match IpVersion::of_packet(buffer.as_ref())? {
#[cfg(feature = "proto-ipv4")]
IpVersion::Ipv4 => {
let mut packet = Ipv4Packet::new_checked(buffer.as_mut())?;
if packet.protocol() != protocol { return Err(Error::Unaddressable) }
if checksum_caps.ipv4.tx() {
if _checksum_caps.ipv4.tx() {
packet.fill_checksum();
} else {
// make sure we get a consistently zeroed checksum,
@ -168,8 +168,8 @@ impl<'a, 'b> RawSocket<'a, 'b> {
packet.set_checksum(0);
}
let packet = Ipv4Packet::new_unchecked(&*packet.into_inner());
let ipv4_repr = Ipv4Repr::parse(&packet, checksum_caps)?;
let packet = Ipv4Packet::new_checked(&*packet.into_inner())?;
let ipv4_repr = Ipv4Repr::parse(&packet, _checksum_caps)?;
Ok((IpRepr::Ipv4(ipv4_repr), packet.payload()))
}
#[cfg(feature = "proto-ipv6")]

View File

@ -27,10 +27,16 @@ pub struct Address(pub [u8; 4]);
impl Address {
/// An unspecified address.
pub const UNSPECIFIED: Address = Address([0x00; 4]);
pub const UNSPECIFIED: Address = Address([0x00; 4]);
/// The broadcast address.
pub const BROADCAST: Address = Address([0xff; 4]);
pub const BROADCAST: Address = Address([0xff; 4]);
/// All multicast-capable nodes
pub const MULTICAST_ALL_SYSTEMS: Address = Address([224, 0, 0, 1]);
/// All multicast-capable routers
pub const MULTICAST_ALL_ROUTERS: Address = Address([224, 0, 0, 2]);
/// Construct an IPv4 address from parts.
pub fn new(a0: u8, a1: u8, a2: u8, a3: u8) -> Address {

View File

@ -99,7 +99,7 @@ mod icmpv4;
mod icmpv6;
#[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))]
mod icmp;
#[cfg(feature = "proto-ipv4")]
#[cfg(feature = "proto-igmp")]
mod igmp;
#[cfg(feature = "proto-ipv6")]
mod ndisc;
@ -173,7 +173,7 @@ pub use self::icmpv4::{Message as Icmpv4Message,
Packet as Icmpv4Packet,
Repr as Icmpv4Repr};
#[cfg(feature = "proto-ipv4")]
#[cfg(feature = "proto-igmp")]
pub use self::igmp::{Packet as IgmpPacket,
Repr as IgmpRepr,
IgmpVersion};