Add basic ICMPv6 reply

Add basic ICMPv6 handling

 - ICMPv6
   - Handle ICMPv6 echo requests
   - Send ICMPv6 error messages
     - When know listening UDP socket is found in process_udp
     - When the next header is not known
   - Update icmpv6 test to be more useful
 - ICMPv4
   - Handle one more case where we are sending too much data
v0.7.x
Dan Robertson 2018-02-05 16:24:25 +00:00 committed by whitequark
parent 49afb3a45a
commit e09e20df95
2 changed files with 145 additions and 32 deletions

View File

@ -1,7 +1,6 @@
// Heads up! Before working on this file you should read the parts
// of RFC 1122 that discuss Ethernet, ARP and IP.
#[cfg(feature = "proto-ipv4")]
use core::cmp;
use managed::ManagedSlice;
@ -11,17 +10,19 @@ use wire::pretty_print::PrettyPrinter;
use wire::{EthernetAddress, EthernetProtocol, EthernetFrame};
use wire::{IpAddress, IpProtocol, IpRepr, IpCidr};
#[cfg(feature = "proto-ipv6")]
use wire::{Ipv6Packet, Ipv6Repr};
use wire::{Ipv6Packet, Ipv6Repr, IPV6_MIN_MTU};
#[cfg(feature = "proto-ipv4")]
use wire::{Ipv4Address, Ipv4Packet, Ipv4Repr};
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-ipv6")]
use wire::{Icmpv6Packet, Icmpv6Repr, Icmpv6ParamProblem};
#[cfg(all(feature = "proto-ipv6", feature = "socket-udp"))]
use wire::Icmpv6DstUnreachable;
#[cfg(feature = "socket-udp")]
use wire::{UdpPacket, UdpRepr};
#[cfg(all(feature = "proto-ipv4", feature = "socket-udp"))]
use wire::IPV4_MIN_MTU;
#[cfg(feature = "socket-tcp")]
use wire::{TcpPacket, TcpRepr, TcpControl};
@ -201,6 +202,8 @@ enum Packet<'a> {
Arp(ArpRepr),
#[cfg(feature = "proto-ipv4")]
Icmpv4((Ipv4Repr, Icmpv4Repr<'a>)),
#[cfg(feature = "proto-ipv6")]
Icmpv6((Ipv6Repr, Icmpv6Repr<'a>)),
#[cfg(feature = "socket-raw")]
Raw((IpRepr, &'a [u8])),
#[cfg(feature = "socket-udp")]
@ -217,6 +220,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-ipv6")]
&Packet::Icmpv6((ref ipv6_repr, _)) => Some(ipv6_repr.dst_addr.into()),
#[cfg(feature = "socket-raw")]
&Packet::Raw((ref ip_repr, _)) => Some(ip_repr.dst_addr()),
#[cfg(feature = "socket-udp")]
@ -227,6 +232,19 @@ impl<'a> Packet<'a> {
}
}
#[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))]
fn icmp_reply_payload_len(len: usize, mtu: usize, header_len: usize) -> usize {
// Send back as much of the original payload as will fit within
// the minimum MTU required by IPv4. See RFC 1812 § 4.3.2.3 for
// more details.
//
// Since the entire network layer packet must fit within the minumum
// MTU supported, the payload must not exceed the following:
//
// <min mtu> - IP Header Size * 2 - ICMPv4 DstUnreachable hdr size
cmp::min(len, mtu - header_len * 2 - 8)
}
impl<'b, 'c, DeviceT> Interface<'b, 'c, DeviceT>
where DeviceT: for<'d> Device<'d> {
/// Get the Ethernet address of the interface.
@ -596,6 +614,9 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
let handled_by_raw_socket = self.raw_socket_filter(sockets, &ip_repr, ip_payload);
match ipv6_repr.next_header {
IpProtocol::Icmpv6 =>
self.process_icmpv6(sockets, ip_repr, ip_payload),
#[cfg(feature = "socket-udp")]
IpProtocol::Udp =>
self.process_udp(sockets, ip_repr, ip_payload),
@ -608,10 +629,20 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
_ if handled_by_raw_socket =>
Ok(Packet::None),
// TODO: send error responses when appropriate.
_ => Ok(Packet::None)
_ => {
// Send back as much of the original payload as we can.
let payload_len = icmp_reply_payload_len(ip_payload.len(), IPV6_MIN_MTU,
ipv6_repr.buffer_len());
let icmp_reply_repr = Icmpv6Repr::ParamProblem {
reason: Icmpv6ParamProblem::UnrecognizedNxtHdr,
// The offending packet is after the IPv6 header.
pointer: ipv6_repr.buffer_len() as u32,
header: ipv6_repr,
data: &ip_payload[0..payload_len]
};
Ok(self.icmpv6_reply(ipv6_repr, icmp_reply_repr))
},
}
}
#[cfg(feature = "proto-ipv4")]
@ -666,9 +697,9 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
Ok(Packet::None),
_ => {
// Send back as much of the original payload as we can
let payload_len = cmp::min(
ip_payload.len(), self.device_capabilities.max_transmission_unit);
// Send back as much of the original payload as we can.
let payload_len = icmp_reply_payload_len(ip_payload.len(), IPV4_MIN_MTU,
ipv4_repr.buffer_len());
let icmp_reply_repr = Icmpv4Repr::DstUnreachable {
reason: Icmpv4DstUnreachable::ProtoUnreachable,
header: ipv4_repr,
@ -679,6 +710,38 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
}
}
#[cfg(feature = "proto-ipv6")]
fn process_icmpv6<'frame>(&self, _sockets: &mut SocketSet, ip_repr: IpRepr,
ip_payload: &'frame [u8]) -> Result<Packet<'frame>>
{
let icmp_packet = Icmpv6Packet::new_checked(ip_payload)?;
let checksum_caps = self.device_capabilities.checksum.clone();
let icmp_repr = Icmpv6Repr::parse(&icmp_packet, &checksum_caps)?;
match icmp_repr {
// Respond to echo requests.
Icmpv6Repr::EchoRequest { ident, seq_no, data } => {
match ip_repr {
IpRepr::Ipv6(ipv6_repr) => {
let icmp_reply_repr = Icmpv6Repr::EchoReply {
ident: ident,
seq_no: seq_no,
data: data
};
Ok(self.icmpv6_reply(ipv6_repr, icmp_reply_repr))
},
_ => Err(Error::Unrecognized),
}
}
// Ignore any echo replies.
Icmpv6Repr::EchoReply { .. } => Ok(Packet::None),
// FIXME: do something correct here?
_ => Err(Error::Unrecognized),
}
}
#[cfg(feature = "proto-ipv4")]
fn process_icmpv4<'frame>(&self, _sockets: &mut SocketSet, ip_repr: IpRepr,
ip_payload: &'frame [u8]) -> Result<Packet<'frame>>
@ -751,6 +814,26 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
}
}
#[cfg(feature = "proto-ipv6")]
fn icmpv6_reply<'frame, 'icmp: 'frame>
(&self, ipv6_repr: Ipv6Repr, icmp_repr: Icmpv6Repr<'icmp>) ->
Packet<'frame>
{
if ipv6_repr.dst_addr.is_unicast() {
let ipv6_reply_repr = Ipv6Repr {
src_addr: ipv6_repr.dst_addr,
dst_addr: ipv6_repr.src_addr,
next_header: IpProtocol::Icmpv6,
payload_len: icmp_repr.buffer_len(),
hop_limit: 64
};
Packet::Icmpv6((ipv6_reply_repr, icmp_repr))
} else {
// Do not send any ICMP replies to a broadcast destination address.
Packet::None
}
}
#[cfg(feature = "socket-udp")]
fn process_udp<'frame>(&self, sockets: &mut SocketSet,
ip_repr: IpRepr, ip_payload: &'frame [u8]) ->
@ -776,18 +859,8 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
match ip_repr {
#[cfg(feature = "proto-ipv4")]
IpRepr::Ipv4(ipv4_repr) => {
// Send back as much of the original payload as will fit within
// the minimum MTU required by IPv4. See RFC 1812 § 4.3.2.3 for
// more details.
//
// Since the entire network layer packet must fit within 576
// bytes, the payload must not exceed the following:
//
// 576 - New IP hdr size - Old IP hdr size - ICMPv4 DstUnreachable hdr size
//
// We do no support IP options, so this becomes 576 - 20 - 20 - 8.
const DST_UNREACHABLE_HDR_SIZE: usize = 48;
let payload_len = cmp::min(ip_payload.len(), IPV4_MIN_MTU - DST_UNREACHABLE_HDR_SIZE);
let payload_len = icmp_reply_payload_len(ip_payload.len(), IPV4_MIN_MTU,
ipv4_repr.buffer_len());
let icmpv4_reply_repr = Icmpv4Repr::DstUnreachable {
reason: Icmpv4DstUnreachable::PortUnreachable,
header: ipv4_repr,
@ -796,7 +869,16 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
Ok(self.icmpv4_reply(ipv4_repr, icmpv4_reply_repr))
},
#[cfg(feature = "proto-ipv6")]
IpRepr::Ipv6(_) => Err(Error::Unaddressable),
IpRepr::Ipv6(ipv6_repr) => {
let payload_len = icmp_reply_payload_len(ip_payload.len(), IPV6_MIN_MTU,
ipv6_repr.buffer_len());
let icmpv6_reply_repr = Icmpv6Repr::DstUnreachable {
reason: Icmpv6DstUnreachable::PortUnreachable,
header: ipv6_repr,
data: &ip_payload[0..payload_len]
};
Ok(self.icmpv6_reply(ipv6_repr, icmpv6_reply_repr))
},
IpRepr::Unspecified { .. } |
IpRepr::__Nonexhaustive => Err(Error::Unaddressable),
}
@ -862,6 +944,13 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
icmpv4_repr.emit(&mut Icmpv4Packet::new(payload), &checksum_caps);
})
}
#[cfg(feature = "proto-ipv6")]
Packet::Icmpv6((ipv6_repr, icmpv6_repr)) => {
self.dispatch_ip(tx_token, timestamp, IpRepr::Ipv6(ipv6_repr),
|_ip_repr, payload| {
icmpv6_repr.emit(&mut Icmpv6Packet::new(payload), &checksum_caps);
})
}
#[cfg(feature = "socket-raw")]
Packet::Raw((ip_repr, raw_packet)) => {
self.dispatch_ip(tx_token, timestamp, ip_repr, |_ip_repr, payload| {
@ -1051,6 +1140,8 @@ mod test {
use wire::{UdpPacket, UdpRepr};
#[cfg(feature = "proto-ipv6")]
use wire::{Ipv6Address, Ipv6Repr};
#[cfg(feature = "proto-ipv6")]
use wire::{Icmpv6Repr, Icmpv6ParamProblem};
use super::Packet;
@ -1535,33 +1626,53 @@ mod test {
#[test]
#[cfg(feature = "proto-ipv6")]
fn test_raw_socket() {
fn test_icmpv6_nxthdr_unknown() {
let (mut iface, mut socket_set) = create_loopback();
let remote_ip_addr = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
let remote_hw_addr = EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x01]);
let mut eth_bytes = vec![0; 54];
let mut eth_bytes = vec![0; 58];
let payload = [0x12, 0x34, 0x56, 0x78];
let repr = IpRepr::Ipv6(Ipv6Repr {
let ipv6_repr = Ipv6Repr {
src_addr: remote_ip_addr,
dst_addr: Ipv6Address::LOOPBACK,
next_header: IpProtocol::Unknown(0x0c),
payload_len: 0,
hop_limit: 0x40
});
payload_len: 4,
hop_limit: 0x40,
};
let ip_repr = IpRepr::Ipv6(ipv6_repr);
let frame = {
let mut frame = EthernetFrame::new(&mut eth_bytes);
frame.set_dst_addr(EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x00]));
frame.set_src_addr(remote_hw_addr);
frame.set_ethertype(EthernetProtocol::Ipv6);
repr.emit(frame.payload_mut(), &ChecksumCapabilities::default());
ip_repr.emit(frame.payload_mut(), &ChecksumCapabilities::default());
frame.payload_mut()[ip_repr.buffer_len()..].copy_from_slice(&payload);
EthernetFrame::new(&*frame.into_inner())
};
let reply_icmp_repr = Icmpv6Repr::ParamProblem {
reason: Icmpv6ParamProblem::UnrecognizedNxtHdr,
pointer: 40,
header: ipv6_repr,
data: &payload[..]
};
let reply_ipv6_repr = Ipv6Repr {
src_addr: Ipv6Address::LOOPBACK,
dst_addr: remote_ip_addr,
next_header: IpProtocol::Icmpv6,
payload_len: reply_icmp_repr.buffer_len(),
hop_limit: 0x40,
};
// Ensure the unknown next header causes a ICMPv6 Parameter Problem
// error message to be sent to the sender.
assert_eq!(iface.inner.process_ipv6(&mut socket_set, 0, &frame),
Ok(Packet::None));
Ok(Packet::Icmpv6((reply_ipv6_repr, reply_icmp_repr))));
// Ensure the address of the requestor was entered in the cache
assert_eq!(iface.inner.lookup_hardware_addr(MockTxToken, 0,

View File

@ -144,6 +144,8 @@ pub use self::icmpv4::{Message as Icmpv4Message,
#[cfg(feature = "proto-ipv6")]
pub use self::icmpv6::{Message as Icmpv6Message,
DstUnreachable as Icmpv6DstUnreachable,
TimeExceeded as Icmpv6TimeExceeded,
ParamProblem as Icmpv6ParamProblem,
Packet as Icmpv6Packet,
Repr as Icmpv6Repr};