Skip to content

Commit

Permalink
Start adding support for ICMPv6.
Browse files Browse the repository at this point in the history
  • Loading branch information
progval+git@progval.net authored and progval+git@progval.net committed May 9, 2018
1 parent 4397004 commit 3375edb
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 31 deletions.
39 changes: 30 additions & 9 deletions src/iface/ethernet.rs
Expand Up @@ -20,6 +20,8 @@ use wire::{ArpPacket, ArpRepr, ArpOperation};
use wire::{Icmpv4Packet, Icmpv4Repr, Icmpv4DstUnreachable};
#[cfg(feature = "proto-ipv6")]
use wire::{Icmpv6Packet, Icmpv6Repr, Icmpv6ParamProblem};
#[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))]
use wire::IcmpRepr;
#[cfg(feature = "proto-ipv6")]
use wire::{NdiscNeighborFlags, NdiscRepr};
#[cfg(all(feature = "proto-ipv6", feature = "socket-udp"))]
Expand Down Expand Up @@ -429,8 +431,11 @@ impl<'b, 'c, DeviceT> Interface<'b, 'c, DeviceT>
socket.dispatch(&caps, |response| {
match response {
#[cfg(feature = "proto-ipv4")]
(IpRepr::Ipv4(ipv4_repr), icmpv4_repr) =>
(IpRepr::Ipv4(ipv4_repr), IcmpRepr::Ipv4(icmpv4_repr)) =>
respond!(Packet::Icmpv4((ipv4_repr, icmpv4_repr))),
#[cfg(feature = "proto-ipv6")]
(IpRepr::Ipv6(ipv6_repr), IcmpRepr::Ipv6(icmpv6_repr)) =>
respond!(Packet::Icmpv6((ipv6_repr, icmpv6_repr))),
_ => Err(Error::Unaddressable)
}
}),
Expand Down Expand Up @@ -709,7 +714,7 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {

match ipv4_repr.protocol {
IpProtocol::Icmp =>
self.process_icmpv4(sockets, ip_repr, ip_payload),
self.process_icmp(sockets, ip_repr, ip_payload),

#[cfg(feature = "socket-udp")]
IpProtocol::Udp =>
Expand Down Expand Up @@ -826,13 +831,13 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
packet
}

#[cfg(feature = "proto-ipv4")]
fn process_icmpv4<'frame>(&self, _sockets: &mut SocketSet, ip_repr: IpRepr,
ip_payload: &'frame [u8]) -> Result<Packet<'frame>>
#[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))]
fn process_icmp<'frame>(&self, _sockets: &mut SocketSet, ip_repr: IpRepr,
ip_payload: &'frame [u8]) -> Result<Packet<'frame>>
{
let icmp_packet = Icmpv4Packet::new_checked(ip_payload)?;
let checksum_caps = self.device_capabilities.checksum.clone();
let icmp_repr = Icmpv4Repr::parse(&icmp_packet, &checksum_caps)?;
let icmp_repr = Icmpv4Repr::parse(&icmp_packet, &checksum_caps)?.into();

#[cfg(feature = "socket-icmp")]
let mut handled_by_icmp_socket = false;
Expand All @@ -853,7 +858,8 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {

match icmp_repr {
// Respond to echo requests.
Icmpv4Repr::EchoRequest { ident, seq_no, data } => {
#[cfg(feature = "proto-ipv4")]
IcmpRepr::Ipv4(Icmpv4Repr::EchoRequest { ident, seq_no, data }) => {
let icmp_reply_repr = Icmpv4Repr::EchoReply {
ident: ident,
seq_no: seq_no,
Expand All @@ -863,10 +869,25 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
IpRepr::Ipv4(ipv4_repr) => Ok(self.icmpv4_reply(ipv4_repr, icmp_reply_repr)),
_ => Err(Error::Unrecognized),
}
},
#[cfg(feature = "proto-ipv6")]
IcmpRepr::Ipv6(Icmpv6Repr::EchoRequest { ident, seq_no, data }) => {
let icmp_reply_repr = Icmpv6Repr::EchoReply {
ident: ident,
seq_no: seq_no,
data: data
};
match ip_repr {
IpRepr::Ipv6(ipv6_repr) => Ok(self.icmpv6_reply(ipv6_repr, icmp_reply_repr)),
_ => Err(Error::Unrecognized),
}
}

// Ignore any echo replies.
Icmpv4Repr::EchoReply { .. } => Ok(Packet::None),
#[cfg(feature = "proto-ipv4")]
IcmpRepr::Ipv4(Icmpv4Repr::EchoReply { .. }) => Ok(Packet::None),
#[cfg(feature = "proto-ipv6")]
IcmpRepr::Ipv6(Icmpv6Repr::EchoReply { .. }) => Ok(Packet::None),

// Don't report an error if a packet with unknown type
// has been handled by an ICMP socket
Expand Down Expand Up @@ -1823,7 +1844,7 @@ mod test {
dst_addr: ipv4_repr.src_addr,
..ipv4_repr
};
assert_eq!(iface.inner.process_icmpv4(&mut socket_set, ip_repr, icmp_data),
assert_eq!(iface.inner.process_icmp(&mut socket_set, ip_repr, icmp_data),
Ok(Packet::Icmpv4((ipv4_reply, echo_reply))));

{
Expand Down
73 changes: 51 additions & 22 deletions src/socket/icmp.rs
Expand Up @@ -6,8 +6,9 @@ use socket::{Socket, SocketMeta, SocketHandle};
use storage::{PacketBuffer, PacketMetadata};
use time::Instant;
use wire::{IpAddress, IpEndpoint, IpProtocol, IpRepr};
use wire::{Ipv4Address, Ipv4Repr};
use wire::{Icmpv4Packet, Icmpv4Repr};
use wire::{Ipv4Address, Ipv4Repr, Ipv6Address, Ipv6Repr};
use wire::{Icmpv4Packet, Icmpv6Packet};
use wire::{Icmpv4Repr, Icmpv6Repr, IcmpRepr};
use wire::{UdpPacket, UdpRepr};

/// Type of endpoint to bind the ICMP socket to. See [IcmpSocket::bind] for
Expand Down Expand Up @@ -244,13 +245,14 @@ impl<'a, 'b> IcmpSocket<'a, 'b> {

/// Filter determining which packets received by the interface are appended to
/// the given sockets received buffer.
pub(crate) fn accepts(&self, ip_repr: &IpRepr, icmp_repr: &Icmpv4Repr,
pub(crate) fn accepts(&self, ip_repr: &IpRepr, icmp_repr: &IcmpRepr,
cksum: &ChecksumCapabilities) -> bool {
match (&self.endpoint, icmp_repr) {
// If we are bound to ICMP errors associated to a UDP port, only
// accept Destination Unreachable messages with the data containing
// a UDP packet send from the local port we are bound to.
(&Endpoint::Udp(endpoint), &Icmpv4Repr::DstUnreachable { data, .. })
(&Endpoint::Udp(endpoint), &IcmpRepr::Ipv4(Icmpv4Repr::DstUnreachable { data, .. })) |
(&Endpoint::Udp(endpoint), &IcmpRepr::Ipv6(Icmpv6Repr::DstUnreachable { data, .. }))
if endpoint.addr.is_unspecified() || endpoint.addr == ip_repr.dst_addr() => {
let packet = UdpPacket::new(data);
match UdpRepr::parse(&packet, &ip_repr.src_addr(), &ip_repr.dst_addr(), cksum) {
Expand All @@ -261,25 +263,39 @@ impl<'a, 'b> IcmpSocket<'a, 'b> {
// If we are bound to a specific ICMP identifier value, only accept an
// Echo Request/Reply with the identifier field matching the endpoint
// port.
(&Endpoint::Ident(bound_ident), &Icmpv4Repr::EchoRequest { ident, .. }) |
(&Endpoint::Ident(bound_ident), &Icmpv4Repr::EchoReply { ident, .. }) =>
(&Endpoint::Ident(bound_ident), &IcmpRepr::Ipv4(Icmpv4Repr::EchoRequest { ident, .. })) |
(&Endpoint::Ident(bound_ident), &IcmpRepr::Ipv6(Icmpv6Repr::EchoRequest { ident, .. })) |
(&Endpoint::Ident(bound_ident), &IcmpRepr::Ipv4(Icmpv4Repr::EchoReply { ident, .. })) |
(&Endpoint::Ident(bound_ident), &IcmpRepr::Ipv6(Icmpv6Repr::EchoReply { ident, .. })) =>
ident == bound_ident,
_ => false,
}
}

pub(crate) fn process(&mut self, ip_repr: &IpRepr, icmp_repr: &Icmpv4Repr,
pub(crate) fn process(&mut self, ip_repr: &IpRepr, icmp_repr: &IcmpRepr,
_cksum: &ChecksumCapabilities) -> Result<()> {
let packet_buf = self.rx_buffer.enqueue(icmp_repr.buffer_len(), ip_repr.src_addr())?;
icmp_repr.emit(&mut Icmpv4Packet::new(packet_buf), &ChecksumCapabilities::default());
match icmp_repr {
&IcmpRepr::Ipv4(ref icmp_repr) => {
let packet_buf = self.rx_buffer.enqueue(icmp_repr.buffer_len(), ip_repr.src_addr())?;
icmp_repr.emit(&mut Icmpv4Packet::new(packet_buf), &ChecksumCapabilities::default());

net_trace!("{}:{}: receiving {} octets",
self.meta.handle, icmp_repr.buffer_len(), packet_buf.len());
net_trace!("{}:{}: receiving {} octets",
self.meta.handle, icmp_repr.buffer_len(), packet_buf.len());
},
&IcmpRepr::Ipv6(ref icmp_repr) => {
let packet_buf = self.rx_buffer.enqueue(icmp_repr.buffer_len(), ip_repr.src_addr())?;
icmp_repr.emit(&ip_repr.src_addr(), &ip_repr.dst_addr(),
&mut Icmpv6Packet::new(packet_buf), &ChecksumCapabilities::default());

net_trace!("{}:{}: receiving {} octets",
self.meta.handle, icmp_repr.buffer_len(), packet_buf.len());
},
}
Ok(())
}

pub(crate) fn dispatch<F>(&mut self, _caps: &DeviceCapabilities, emit: F) -> Result<()>
where F: FnOnce((IpRepr, Icmpv4Repr)) -> Result<()>
where F: FnOnce((IpRepr, IcmpRepr)) -> Result<()>
{
let handle = self.meta.handle;
let hop_limit = self.hop_limit.unwrap_or(64);
Expand All @@ -297,7 +313,20 @@ impl<'a, 'b> IcmpSocket<'a, 'b> {
payload_len: repr.buffer_len(),
hop_limit: hop_limit,
});
emit((ip_repr, repr))
emit((ip_repr, IcmpRepr::Ipv4(repr)))
},
IpAddress::Ipv6(ipv6_addr) => {
let packet = Icmpv6Packet::new(&*packet_buf);
let src_addr = Ipv6Address::default();
let repr = Icmpv6Repr::parse(&src_addr.into(), &ipv6_addr.into(), &packet, &ChecksumCapabilities::default())?;
let ip_repr = IpRepr::Ipv6(Ipv6Repr {
src_addr: src_addr,
dst_addr: ipv6_addr,
next_header: IpProtocol::Icmp,
payload_len: repr.buffer_len(),
hop_limit: hop_limit,
});
emit((ip_repr, IcmpRepr::Ipv6(repr)))
},
_ => Err(Error::Unaddressable)
}
Expand Down Expand Up @@ -399,15 +428,15 @@ mod test {

assert_eq!(socket.dispatch(&caps, |(ip_repr, icmp_repr)| {
assert_eq!(ip_repr, LOCAL_IP_REPR);
assert_eq!(icmp_repr, ECHO_REPR);
assert_eq!(icmp_repr, ECHO_REPR.into());
Err(Error::Unaddressable)
}), Err(Error::Unaddressable));
// buffer is not taken off of the tx queue due to the error
assert!(!socket.can_send());

assert_eq!(socket.dispatch(&caps, |(ip_repr, icmp_repr)| {
assert_eq!(ip_repr, LOCAL_IP_REPR);
assert_eq!(icmp_repr, ECHO_REPR);
assert_eq!(icmp_repr, ECHO_REPR.into());
Ok(())
}), Ok(()));
// buffer is taken off of the queue this time
Expand Down Expand Up @@ -453,13 +482,13 @@ mod test {
ECHO_REPR.emit(&mut packet, &caps.checksum);
let data = &packet.into_inner()[..];

assert!(socket.accepts(&REMOTE_IP_REPR, &ECHO_REPR, &caps.checksum));
assert_eq!(socket.process(&REMOTE_IP_REPR, &ECHO_REPR, &caps.checksum),
assert!(socket.accepts(&REMOTE_IP_REPR, &ECHO_REPR.into(), &caps.checksum));
assert_eq!(socket.process(&REMOTE_IP_REPR, &ECHO_REPR.into(), &caps.checksum),
Ok(()));
assert!(socket.can_recv());

assert!(socket.accepts(&REMOTE_IP_REPR, &ECHO_REPR, &caps.checksum));
assert_eq!(socket.process(&REMOTE_IP_REPR, &ECHO_REPR, &caps.checksum),
assert!(socket.accepts(&REMOTE_IP_REPR, &ECHO_REPR.into(), &caps.checksum));
assert_eq!(socket.process(&REMOTE_IP_REPR, &ECHO_REPR.into(), &caps.checksum),
Err(Error::Exhausted));

assert_eq!(socket.recv(), Ok((&data[..], REMOTE_IP)));
Expand All @@ -483,7 +512,7 @@ mod test {

// Ensure that a packet with an identifier that isn't the bound
// ID is not accepted
assert!(!socket.accepts(&REMOTE_IP_REPR, &icmp_repr, &caps.checksum));
assert!(!socket.accepts(&REMOTE_IP_REPR, &icmp_repr.into(), &caps.checksum));
}

#[test]
Expand Down Expand Up @@ -522,8 +551,8 @@ mod test {

// Ensure we can accept ICMP error response to the bound
// UDP port
assert!(socket.accepts(&ip_repr, &icmp_repr, &caps.checksum));
assert_eq!(socket.process(&ip_repr, &icmp_repr, &caps.checksum),
assert!(socket.accepts(&ip_repr, &icmp_repr.into(), &caps.checksum));
assert_eq!(socket.process(&ip_repr, &icmp_repr.into(), &caps.checksum),
Ok(()));
assert!(socket.can_recv());

Expand Down
63 changes: 63 additions & 0 deletions src/wire/icmp.rs
@@ -0,0 +1,63 @@
use super::icmpv4;
use super::icmpv6;

macro_rules! v4v6 {
( $class:ident ) => {
/// icmpv4::$class or icmpv6::$class
#[derive(Clone, PartialEq, Debug)]
pub enum $class {
#[cfg(feature = "proto-ipv4")]
Ipv4(icmpv4::$class),
#[cfg(feature = "proto-ipv6")]
Ipv6(icmpv6::$class),
}

impl From<icmpv4::$class> for $class {
fn from(s: icmpv4::$class) -> Self {
$class::Ipv4(s)
}
}
impl From<icmpv6::$class> for $class {
fn from(s: icmpv6::$class) -> Self {
$class::Ipv6(s)
}
}
};
( $class:ident < $typearg:tt > ) => {
/// icmpv4::$class or icmpv6::$class
#[derive(Clone, PartialEq, Debug)]
pub enum $class<$typearg> {
#[cfg(feature = "proto-ipv4")]
Ipv4(icmpv4::$class<$typearg>),
#[cfg(feature = "proto-ipv6")]
Ipv6(icmpv6::$class<$typearg>),
}
impl<$typearg> From<icmpv4::$class<$typearg>> for $class<$typearg> {
fn from(s: icmpv4::$class<$typearg>) -> Self {
$class::Ipv4(s)
}
}
impl<$typearg> From<icmpv6::$class<$typearg>> for $class<$typearg> {
fn from(s: icmpv6::$class<$typearg>) -> Self {
$class::Ipv6(s)
}
}
};
( $class:ident < $typearg:tt : $typebound:path > ) => {
/// icmpv4::$class or icmpv6::$class
#[derive(Clone, PartialEq, Debug)]
pub enum $class<$typearg : $typebound> {
#[cfg(feature = "proto-ipv4")]
Ipv4(icmpv4::$class<$typearg>),
#[cfg(feature = "proto-ipv6")]
IPv6(icmpv6::$class<$typearg>),
}
};
}

v4v6!(Message);
v4v6!(DstUnreachable);
v4v6!(TimeExceeded);
v4v6!(ParamProblem);
v4v6!(Packet<T: AsRef<[u8]>>);
v4v6!(Repr<'a>);
9 changes: 9 additions & 0 deletions src/wire/mod.rs
Expand Up @@ -95,6 +95,8 @@ mod ipv6fragment;
mod icmpv4;
#[cfg(feature = "proto-ipv6")]
mod icmpv6;
#[cfg(all(feature = "proto-ipv4", feature = "proto-ipv6"))]
mod icmp;
#[cfg(feature = "proto-ipv4")]
mod igmp;
#[cfg(feature = "proto-ipv6")]
Expand Down Expand Up @@ -174,6 +176,13 @@ pub use self::icmpv6::{Message as Icmpv6Message,
ParamProblem as Icmpv6ParamProblem,
Packet as Icmpv6Packet,
Repr as Icmpv6Repr};
#[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))]
pub use self::icmp::{Message as IcmpMessage,
DstUnreachable as IcmpDstUnreachable,
TimeExceeded as IcmpTimeExceeded,
ParamProblem as IcmpParamProblem,
Packet as IcmpPacket,
Repr as IcmpRepr};

#[cfg(feature = "proto-ipv6")]
pub use self::icmpv6::{RouterFlags as NdiscRouterFlags,
Expand Down

0 comments on commit 3375edb

Please sign in to comment.