From 2afc538fd9d2ce992f49c12ac7fed4b55b77afcf Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Wed, 9 May 2018 15:27:51 +0200 Subject: [PATCH] Add support for IPv6 to ICMP sockets. Closes: #205 Approved by: whitequark --- src/iface/ethernet.rs | 40 ++++- src/socket/icmp.rs | 375 +++++++++++++++++++++++++++++++++++------- src/socket/mod.rs | 10 +- src/socket/ref_.rs | 4 +- src/socket/set.rs | 2 +- src/wire/icmp.rs | 24 +++ src/wire/mod.rs | 4 + 7 files changed, 384 insertions(+), 75 deletions(-) create mode 100644 src/wire/icmp.rs diff --git a/src/iface/ethernet.rs b/src/iface/ethernet.rs index 94391fe..529140a 100644 --- a/src/iface/ethernet.rs +++ b/src/iface/ethernet.rs @@ -20,6 +20,8 @@ use wire::{ArpPacket, ArpRepr, ArpOperation}; use wire::{Icmpv4Packet, Icmpv4Repr, Icmpv4DstUnreachable}; #[cfg(feature = "proto-ipv6")] use wire::{Icmpv6Packet, Icmpv6Repr, Icmpv6ParamProblem}; +#[cfg(all(feature = "socket-icmp", 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"))] @@ -32,7 +34,7 @@ use wire::{TcpPacket, TcpRepr, TcpControl}; use socket::{Socket, SocketSet, AnySocket}; #[cfg(feature = "socket-raw")] use socket::RawSocket; -#[cfg(all(feature = "socket-icmp", feature = "proto-ipv4"))] +#[cfg(all(feature = "socket-icmp", any(feature = "proto-ipv4", feature = "proto-ipv6")))] use socket::IcmpSocket; #[cfg(feature = "socket-udp")] use socket::UdpSocket; @@ -424,13 +426,16 @@ impl<'b, 'c, DeviceT> Interface<'b, 'c, DeviceT> Socket::Raw(ref mut socket) => socket.dispatch(&caps.checksum, |response| respond!(Packet::Raw(response))), - #[cfg(all(feature = "socket-icmp", feature = "proto-ipv4"))] + #[cfg(all(feature = "socket-icmp", any(feature = "proto-ipv4", feature = "proto-ipv6")))] Socket::Icmp(ref mut socket) => 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) } }), @@ -746,6 +751,23 @@ impl<'b, 'c> InterfaceInner<'b, 'c> { let icmp_repr = Icmpv6Repr::parse(&ip_repr.src_addr(), &ip_repr.dst_addr(), &icmp_packet, &checksum_caps)?; + #[cfg(feature = "socket-icmp")] + let mut handled_by_icmp_socket = false; + + #[cfg(all(feature = "socket-icmp", feature = "proto-ipv6"))] + for mut icmp_socket in _sockets.iter_mut().filter_map(IcmpSocket::downcast) { + if !icmp_socket.accepts(&ip_repr, &icmp_repr.into(), &checksum_caps) { continue } + + match icmp_socket.process(&ip_repr, &icmp_repr.into(), &checksum_caps) { + // The packet is valid and handled by socket. + Ok(()) => handled_by_icmp_socket = true, + // The socket buffer is full. + Err(Error::Exhausted) => (), + // ICMP sockets don't validate the packets in any way. + Err(_) => unreachable!(), + } + } + match icmp_repr { // Respond to echo requests. Icmpv6Repr::EchoRequest { ident, seq_no, data } => { @@ -771,6 +793,11 @@ impl<'b, 'c> InterfaceInner<'b, 'c> { _ => Ok(Packet::None) }, + // Don't report an error if a packet with unknown type + // has been handled by an ICMP socket + #[cfg(feature = "socket-icmp")] + _ if handled_by_icmp_socket => Ok(Packet::None), + // FIXME: do something correct here? _ => Err(Error::Unrecognized), } @@ -839,9 +866,9 @@ impl<'b, 'c> InterfaceInner<'b, 'c> { #[cfg(all(feature = "socket-icmp", feature = "proto-ipv4"))] for mut icmp_socket in _sockets.iter_mut().filter_map(IcmpSocket::downcast) { - if !icmp_socket.accepts(&ip_repr, &icmp_repr, &checksum_caps) { continue } + if !icmp_socket.accepts(&ip_repr, &icmp_repr.into(), &checksum_caps) { continue } - match icmp_socket.process(&ip_repr, &icmp_repr, &checksum_caps) { + match icmp_socket.process(&ip_repr, &icmp_repr.into(), &checksum_caps) { // The packet is valid and handled by socket. Ok(()) => handled_by_icmp_socket = true, // The socket buffer is full. @@ -853,6 +880,7 @@ impl<'b, 'c> InterfaceInner<'b, 'c> { match icmp_repr { // Respond to echo requests. + #[cfg(feature = "proto-ipv4")] Icmpv4Repr::EchoRequest { ident, seq_no, data } => { let icmp_reply_repr = Icmpv4Repr::EchoReply { ident: ident, @@ -863,7 +891,7 @@ impl<'b, 'c> InterfaceInner<'b, 'c> { IpRepr::Ipv4(ipv4_repr) => Ok(self.icmpv4_reply(ipv4_repr, icmp_reply_repr)), _ => Err(Error::Unrecognized), } - } + }, // Ignore any echo replies. Icmpv4Repr::EchoReply { .. } => Ok(Packet::None), diff --git a/src/socket/icmp.rs b/src/socket/icmp.rs index 24ad347..747caaa 100644 --- a/src/socket/icmp.rs +++ b/src/socket/icmp.rs @@ -6,8 +6,12 @@ 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}; + +#[cfg(feature = "proto-ipv4")] +use wire::{Ipv4Address, Ipv4Repr, Icmpv4Packet, Icmpv4Repr}; +#[cfg(feature = "proto-ipv6")] +use wire::{Ipv6Address, Ipv6Repr, Icmpv6Packet, Icmpv6Repr}; +use wire::IcmpRepr; use wire::{UdpPacket, UdpRepr}; /// Type of endpoint to bind the ICMP socket to. See [IcmpSocket::bind] for @@ -244,13 +248,23 @@ 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, .. }) + #[cfg(feature = "proto-ipv4")] + (&Endpoint::Udp(endpoint), &IcmpRepr::Ipv4(Icmpv4Repr::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) { + Ok(repr) => endpoint.port == repr.src_port, + Err(_) => false, + } + } + #[cfg(feature = "proto-ipv6")] + (&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) { @@ -261,25 +275,44 @@ 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, .. }) => + #[cfg(feature = "proto-ipv4")] + (&Endpoint::Ident(bound_ident), &IcmpRepr::Ipv4(Icmpv4Repr::EchoRequest { ident, .. })) | + (&Endpoint::Ident(bound_ident), &IcmpRepr::Ipv4(Icmpv4Repr::EchoReply { ident, .. })) => + ident == bound_ident, + #[cfg(feature = "proto-ipv6")] + (&Endpoint::Ident(bound_ident), &IcmpRepr::Ipv6(Icmpv6Repr::EchoRequest { 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 { + #[cfg(feature = "proto-ipv4")] + &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()); + }, + #[cfg(feature = "proto-ipv6")] + &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(&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); @@ -287,9 +320,10 @@ impl<'a, 'b> IcmpSocket<'a, 'b> { net_trace!("{}:{}: sending {} octets", handle, remote_endpoint, packet_buf.len()); match *remote_endpoint { + #[cfg(feature = "proto-ipv4")] IpAddress::Ipv4(ipv4_addr) => { let packet = Icmpv4Packet::new(&*packet_buf); - let repr = Icmpv4Repr::parse(&packet, &ChecksumCapabilities::default())?; + let repr = Icmpv4Repr::parse(&packet, &ChecksumCapabilities::ignored())?; let ip_repr = IpRepr::Ipv4(Ipv4Repr { src_addr: Ipv4Address::default(), dst_addr: ipv4_addr, @@ -297,7 +331,21 @@ 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))) + }, + #[cfg(feature = "proto-ipv6")] + 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::ignored())?; + 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) } @@ -320,40 +368,46 @@ impl<'a, 'b> Into> for IcmpSocket<'a, 'b> { } #[cfg(test)] -mod test { - use phy::DeviceCapabilities; - use wire::{IpAddress, Icmpv4DstUnreachable}; - use super::*; +mod tests_common { + pub use phy::DeviceCapabilities; + pub use wire::IpAddress; + pub use super::*; - fn buffer(packets: usize) -> IcmpSocketBuffer<'static, 'static> { - IcmpSocketBuffer::new(vec![IcmpPacketMetadata::EMPTY; packets], vec![0; 46 * packets]) + pub fn buffer(packets: usize) -> IcmpSocketBuffer<'static, 'static> { + IcmpSocketBuffer::new(vec![IcmpPacketMetadata::EMPTY; packets], vec![0; 66 * packets]) } - fn socket(rx_buffer: IcmpSocketBuffer<'static, 'static>, + pub fn socket(rx_buffer: IcmpSocketBuffer<'static, 'static>, tx_buffer: IcmpSocketBuffer<'static, 'static>) -> IcmpSocket<'static, 'static> { IcmpSocket::new(rx_buffer, tx_buffer) } + pub const LOCAL_PORT: u16 = 53; + + pub static UDP_REPR: UdpRepr = UdpRepr { + src_port: 53, + dst_port: 9090, + payload: &[0xff; 10] + }; +} + +#[cfg(all(test, feature = "proto-ipv4"))] +mod test_ipv4 { + use super::tests_common::*; + + use wire::Icmpv4DstUnreachable; + const REMOTE_IPV4: Ipv4Address = Ipv4Address([0x7f, 0x00, 0x00, 0x02]); const LOCAL_IPV4: Ipv4Address = Ipv4Address([0x7f, 0x00, 0x00, 0x01]); - const REMOTE_IP: IpAddress = IpAddress::Ipv4(REMOTE_IPV4); - const LOCAL_IP: IpAddress = IpAddress::Ipv4(LOCAL_IPV4); - const LOCAL_PORT: u16 = 53; - const LOCAL_END: IpEndpoint = IpEndpoint { addr: LOCAL_IP, port: LOCAL_PORT }; + const LOCAL_END_V4: IpEndpoint = IpEndpoint { addr: IpAddress::Ipv4(LOCAL_IPV4), port: LOCAL_PORT }; - static ECHO_REPR: Icmpv4Repr = Icmpv4Repr::EchoRequest { + static ECHOV4_REPR: Icmpv4Repr = Icmpv4Repr::EchoRequest { ident: 0x1234, seq_no: 0x5678, data: &[0xff; 16] }; - static UDP_REPR: UdpRepr = UdpRepr { - src_port: 53, - dst_port: 9090, - payload: &[0xff; 10] - }; - - static LOCAL_IP_REPR: IpRepr = IpRepr::Ipv4(Ipv4Repr { + static LOCAL_IPV4_REPR: IpRepr = IpRepr::Ipv4(Ipv4Repr { src_addr: Ipv4Address::UNSPECIFIED, dst_addr: REMOTE_IPV4, protocol: IpProtocol::Icmp, @@ -361,7 +415,7 @@ mod test { hop_limit: 0x40 }); - static REMOTE_IP_REPR: IpRepr = IpRepr::Ipv4(Ipv4Repr { + static REMOTE_IPV4_REPR: IpRepr = IpRepr::Ipv4(Ipv4Repr { src_addr: REMOTE_IPV4, dst_addr: LOCAL_IPV4, protocol: IpProtocol::Icmp, @@ -374,7 +428,7 @@ mod test { let mut socket = socket(buffer(0), buffer(1)); assert_eq!(socket.send_slice(b"abcdef", IpAddress::default()), Err(Error::Unaddressable)); - assert_eq!(socket.send_slice(b"abcdef", REMOTE_IP), Ok(())); + assert_eq!(socket.send_slice(b"abcdef", REMOTE_IPV4.into()), Ok(())); } #[test] @@ -386,28 +440,28 @@ mod test { Err(Error::Exhausted)); // This buffer is too long - assert_eq!(socket.send_slice(&[0xff; 47], REMOTE_IP), Err(Error::Truncated)); + assert_eq!(socket.send_slice(&[0xff; 67], REMOTE_IPV4.into()), Err(Error::Truncated)); assert!(socket.can_send()); let mut bytes = [0xff; 24]; let mut packet = Icmpv4Packet::new(&mut bytes); - ECHO_REPR.emit(&mut packet, &caps.checksum); + ECHOV4_REPR.emit(&mut packet, &caps.checksum); - assert_eq!(socket.send_slice(&packet.into_inner()[..], REMOTE_IP), Ok(())); - assert_eq!(socket.send_slice(b"123456", REMOTE_IP), Err(Error::Exhausted)); + assert_eq!(socket.send_slice(&packet.into_inner()[..], REMOTE_IPV4.into()), Ok(())); + assert_eq!(socket.send_slice(b"123456", REMOTE_IPV4.into()), Err(Error::Exhausted)); 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!(ip_repr, LOCAL_IPV4_REPR); + assert_eq!(icmp_repr, ECHOV4_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!(ip_repr, LOCAL_IPV4_REPR); + assert_eq!(icmp_repr, ECHOV4_REPR.into()); Ok(()) }), Ok(())); // buffer is taken off of the queue this time @@ -415,23 +469,23 @@ mod test { } #[test] - fn test_set_hop_limit() { + fn test_set_hop_limit_v4() { let mut s = socket(buffer(0), buffer(1)); let caps = DeviceCapabilities::default(); let mut bytes = [0xff; 24]; let mut packet = Icmpv4Packet::new(&mut bytes); - ECHO_REPR.emit(&mut packet, &caps.checksum); + ECHOV4_REPR.emit(&mut packet, &caps.checksum); s.set_hop_limit(Some(0x2a)); - assert_eq!(s.send_slice(&packet.into_inner()[..], REMOTE_IP), Ok(())); + assert_eq!(s.send_slice(&packet.into_inner()[..], REMOTE_IPV4.into()), Ok(())); assert_eq!(s.dispatch(&caps, |(ip_repr, _)| { assert_eq!(ip_repr, IpRepr::Ipv4(Ipv4Repr { src_addr: Ipv4Address::UNSPECIFIED, dst_addr: REMOTE_IPV4, protocol: IpProtocol::Icmp, - payload_len: ECHO_REPR.buffer_len(), + payload_len: ECHOV4_REPR.buffer_len(), hop_limit: 0x2a, })); Ok(()) @@ -450,19 +504,19 @@ mod test { let mut bytes = [0xff; 24]; let mut packet = Icmpv4Packet::new(&mut bytes); - ECHO_REPR.emit(&mut packet, &caps.checksum); + ECHOV4_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_IPV4_REPR, &ECHOV4_REPR.into(), &caps.checksum)); + assert_eq!(socket.process(&REMOTE_IPV4_REPR, &ECHOV4_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_IPV4_REPR, &ECHOV4_REPR.into(), &caps.checksum)); + assert_eq!(socket.process(&REMOTE_IPV4_REPR, &ECHOV4_REPR.into(), &caps.checksum), Err(Error::Exhausted)); - assert_eq!(socket.recv(), Ok((&data[..], REMOTE_IP))); + assert_eq!(socket.recv(), Ok((&data[..], REMOTE_IPV4.into()))); assert!(!socket.can_recv()); } @@ -483,19 +537,19 @@ 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_IPV4_REPR, &icmp_repr.into(), &caps.checksum)); } #[test] fn test_accepts_udp() { let mut socket = socket(buffer(1), buffer(1)); - assert_eq!(socket.bind(Endpoint::Udp(LOCAL_END)), Ok(())); + assert_eq!(socket.bind(Endpoint::Udp(LOCAL_END_V4)), Ok(())); let caps = DeviceCapabilities::default(); let mut bytes = [0xff; 18]; let mut packet = UdpPacket::new(&mut bytes); - UDP_REPR.emit(&mut packet, &REMOTE_IP, &LOCAL_IP, &caps.checksum); + UDP_REPR.emit(&mut packet, &REMOTE_IPV4.into(), &LOCAL_IPV4.into(), &caps.checksum); let data = &packet.into_inner()[..]; @@ -511,8 +565,8 @@ mod test { data: data }; let ip_repr = IpRepr::Unspecified { - src_addr: REMOTE_IP, - dst_addr: LOCAL_IP, + src_addr: REMOTE_IPV4.into(), + dst_addr: LOCAL_IPV4.into(), protocol: IpProtocol::Icmp, payload_len: icmp_repr.buffer_len(), hop_limit: 0x40 @@ -522,15 +576,214 @@ 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()); let mut bytes = [0x00; 46]; let mut packet = Icmpv4Packet::new(&mut bytes[..]); icmp_repr.emit(&mut packet, &caps.checksum); - assert_eq!(socket.recv(), Ok((&packet.into_inner()[..], REMOTE_IP))); + assert_eq!(socket.recv(), Ok((&packet.into_inner()[..], REMOTE_IPV4.into()))); + assert!(!socket.can_recv()); + } +} + +#[cfg(all(test, feature = "proto-ipv6"))] +mod test_ipv6 { + use super::tests_common::*; + + use wire::Icmpv6DstUnreachable; + + const REMOTE_IPV6: Ipv6Address = Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1]); + const LOCAL_IPV6: Ipv6Address = Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 2]); + const LOCAL_END_V6: IpEndpoint = IpEndpoint { addr: IpAddress::Ipv6(LOCAL_IPV6), port: LOCAL_PORT }; + static ECHOV6_REPR: Icmpv6Repr = Icmpv6Repr::EchoRequest { + ident: 0x1234, + seq_no: 0x5678, + data: &[0xff; 16] + }; + + static LOCAL_IPV6_REPR: IpRepr = IpRepr::Ipv6(Ipv6Repr { + src_addr: Ipv6Address::UNSPECIFIED, + dst_addr: REMOTE_IPV6, + next_header: IpProtocol::Icmp, + payload_len: 24, + hop_limit: 0x40 + }); + + static REMOTE_IPV6_REPR: IpRepr = IpRepr::Ipv6(Ipv6Repr { + src_addr: REMOTE_IPV6, + dst_addr: LOCAL_IPV6, + next_header: IpProtocol::Icmp, + payload_len: 24, + hop_limit: 0x40 + }); + + #[test] + fn test_send_unaddressable() { + let mut socket = socket(buffer(0), buffer(1)); + assert_eq!(socket.send_slice(b"abcdef", IpAddress::default()), + Err(Error::Unaddressable)); + assert_eq!(socket.send_slice(b"abcdef", REMOTE_IPV6.into()), Ok(())); + } + + #[test] + fn test_send_dispatch() { + let mut socket = socket(buffer(0), buffer(1)); + let caps = DeviceCapabilities::default(); + + assert_eq!(socket.dispatch(&caps, |_| unreachable!()), + Err(Error::Exhausted)); + + // This buffer is too long + assert_eq!(socket.send_slice(&[0xff; 67], REMOTE_IPV6.into()), Err(Error::Truncated)); + assert!(socket.can_send()); + + let mut bytes = vec![0xff; 24]; + let mut packet = Icmpv6Packet::new(&mut bytes); + ECHOV6_REPR.emit(&LOCAL_IPV6.into(), &REMOTE_IPV6.into(), &mut packet, &caps.checksum); + + assert_eq!(socket.send_slice(&packet.into_inner()[..], REMOTE_IPV6.into()), Ok(())); + assert_eq!(socket.send_slice(b"123456", REMOTE_IPV6.into()), Err(Error::Exhausted)); + assert!(!socket.can_send()); + + assert_eq!(socket.dispatch(&caps, |(ip_repr, icmp_repr)| { + assert_eq!(ip_repr, LOCAL_IPV6_REPR); + assert_eq!(icmp_repr, ECHOV6_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_IPV6_REPR); + assert_eq!(icmp_repr, ECHOV6_REPR.into()); + Ok(()) + }), Ok(())); + // buffer is taken off of the queue this time + assert!(socket.can_send()); + } + + #[test] + fn test_set_hop_limit() { + let mut s = socket(buffer(0), buffer(1)); + let caps = DeviceCapabilities::default(); + + let mut bytes = vec![0xff; 24]; + let mut packet = Icmpv6Packet::new(&mut bytes); + ECHOV6_REPR.emit(&LOCAL_IPV6.into(), &REMOTE_IPV6.into(), &mut packet, &caps.checksum); + + s.set_hop_limit(Some(0x2a)); + + assert_eq!(s.send_slice(&packet.into_inner()[..], REMOTE_IPV6.into()), Ok(())); + assert_eq!(s.dispatch(&caps, |(ip_repr, _)| { + assert_eq!(ip_repr, IpRepr::Ipv6(Ipv6Repr { + src_addr: Ipv6Address::UNSPECIFIED, + dst_addr: REMOTE_IPV6, + next_header: IpProtocol::Icmp, + payload_len: ECHOV6_REPR.buffer_len(), + hop_limit: 0x2a, + })); + Ok(()) + }), Ok(())); + } + + #[test] + fn test_recv_process() { + let mut socket = socket(buffer(1), buffer(1)); + assert_eq!(socket.bind(Endpoint::Ident(0x1234)), Ok(())); + + assert!(!socket.can_recv()); + assert_eq!(socket.recv(), Err(Error::Exhausted)); + + let caps = DeviceCapabilities::default(); + + let mut bytes = [0xff; 24]; + let mut packet = Icmpv6Packet::new(&mut bytes); + ECHOV6_REPR.emit(&LOCAL_IPV6.into(), &REMOTE_IPV6.into(), &mut packet, &caps.checksum); + let data = &packet.into_inner()[..]; + + assert!(socket.accepts(&REMOTE_IPV6_REPR, &ECHOV6_REPR.into(), &caps.checksum)); + assert_eq!(socket.process(&REMOTE_IPV6_REPR, &ECHOV6_REPR.into(), &caps.checksum), + Ok(())); + assert!(socket.can_recv()); + + assert!(socket.accepts(&REMOTE_IPV6_REPR, &ECHOV6_REPR.into(), &caps.checksum)); + assert_eq!(socket.process(&REMOTE_IPV6_REPR, &ECHOV6_REPR.into(), &caps.checksum), + Err(Error::Exhausted)); + + assert_eq!(socket.recv(), Ok((&data[..], REMOTE_IPV6.into()))); + assert!(!socket.can_recv()); + } + + #[test] + fn test_accept_bad_id() { + let mut socket = socket(buffer(1), buffer(1)); + assert_eq!(socket.bind(Endpoint::Ident(0x1234)), Ok(())); + + let caps = DeviceCapabilities::default(); + let mut bytes = [0xff; 20]; + let mut packet = Icmpv6Packet::new(&mut bytes); + let icmp_repr = Icmpv6Repr::EchoRequest { + ident: 0x4321, + seq_no: 0x5678, + data: &[0xff; 16] + }; + icmp_repr.emit(&LOCAL_IPV6.into(), &REMOTE_IPV6.into(), &mut packet, &caps.checksum); + + // Ensure that a packet with an identifier that isn't the bound + // ID is not accepted + assert!(!socket.accepts(&REMOTE_IPV6_REPR, &icmp_repr.into(), &caps.checksum)); + } + + #[test] + fn test_accepts_udp() { + let mut socket = socket(buffer(1), buffer(1)); + assert_eq!(socket.bind(Endpoint::Udp(LOCAL_END_V6)), Ok(())); + + let caps = DeviceCapabilities::default(); + + let mut bytes = [0xff; 18]; + let mut packet = UdpPacket::new(&mut bytes); + UDP_REPR.emit(&mut packet, &REMOTE_IPV6.into(), &LOCAL_IPV6.into(), &caps.checksum); + + let data = &packet.into_inner()[..]; + + let icmp_repr = Icmpv6Repr::DstUnreachable { + reason: Icmpv6DstUnreachable::PortUnreachable, + header: Ipv6Repr { + src_addr: LOCAL_IPV6, + dst_addr: REMOTE_IPV6, + next_header: IpProtocol::Icmp, + payload_len: 12, + hop_limit: 0x40 + }, + data: data + }; + let ip_repr = IpRepr::Unspecified { + src_addr: REMOTE_IPV6.into(), + dst_addr: LOCAL_IPV6.into(), + protocol: IpProtocol::Icmp, + payload_len: icmp_repr.buffer_len(), + hop_limit: 0x40 + }; + + assert!(!socket.can_recv()); + + // Ensure we can accept ICMP error response to the bound + // UDP port + 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()); + + let mut bytes = [0x00; 66]; + let mut packet = Icmpv6Packet::new(&mut bytes[..]); + icmp_repr.emit(&LOCAL_IPV6.into(), &REMOTE_IPV6.into(), &mut packet, &caps.checksum); + assert_eq!(socket.recv(), Ok((&packet.into_inner()[..], REMOTE_IPV6.into()))); assert!(!socket.can_recv()); } } diff --git a/src/socket/mod.rs b/src/socket/mod.rs index 8c4833c..8cdf542 100644 --- a/src/socket/mod.rs +++ b/src/socket/mod.rs @@ -17,7 +17,7 @@ use time::Instant; mod meta; #[cfg(feature = "socket-raw")] mod raw; -#[cfg(all(feature = "socket-icmp", feature = "proto-ipv4"))] +#[cfg(all(feature = "socket-icmp", any(feature = "proto-ipv4", feature = "proto-ipv6")))] mod icmp; #[cfg(feature = "socket-udp")] mod udp; @@ -33,7 +33,7 @@ pub use self::raw::{RawPacketMetadata, RawSocketBuffer, RawSocket}; -#[cfg(all(feature = "socket-icmp", feature = "proto-ipv4"))] +#[cfg(all(feature = "socket-icmp", any(feature = "proto-ipv4", feature = "proto-ipv6")))] pub use self::icmp::{IcmpPacketMetadata, IcmpSocketBuffer, Endpoint as IcmpEndpoint, @@ -69,7 +69,7 @@ pub(crate) use self::ref_::Session as SocketSession; pub enum Socket<'a, 'b: 'a> { #[cfg(feature = "socket-raw")] Raw(RawSocket<'a, 'b>), - #[cfg(all(feature = "socket-icmp", feature = "proto-ipv4"))] + #[cfg(all(feature = "socket-icmp", any(feature = "proto-ipv4", feature = "proto-ipv6")))] Icmp(IcmpSocket<'a, 'b>), #[cfg(feature = "socket-udp")] Udp(UdpSocket<'a, 'b>), @@ -90,7 +90,7 @@ macro_rules! dispatch_socket { match $self_ { #[cfg(feature = "socket-raw")] &$( $mut_ )* Socket::Raw(ref $( $mut_ )* $socket) => $code, - #[cfg(all(feature = "socket-icmp", feature = "proto-ipv4"))] + #[cfg(all(feature = "socket-icmp", any(feature = "proto-ipv4", feature = "proto-ipv6")))] &$( $mut_ )* Socket::Icmp(ref $( $mut_ )* $socket) => $code, #[cfg(feature = "socket-udp")] &$( $mut_ )* Socket::Udp(ref $( $mut_ )* $socket) => $code, @@ -149,7 +149,7 @@ macro_rules! from_socket { #[cfg(feature = "socket-raw")] from_socket!(RawSocket<'a, 'b>, Raw); -#[cfg(all(feature = "socket-icmp", feature = "proto-ipv4"))] +#[cfg(all(feature = "socket-icmp", any(feature = "proto-ipv4", feature = "proto-ipv6")))] from_socket!(IcmpSocket<'a, 'b>, Icmp); #[cfg(feature = "socket-udp")] from_socket!(UdpSocket<'a, 'b>, Udp); diff --git a/src/socket/ref_.rs b/src/socket/ref_.rs index beef748..e7f34f3 100644 --- a/src/socket/ref_.rs +++ b/src/socket/ref_.rs @@ -2,7 +2,7 @@ use core::ops::{Deref, DerefMut}; #[cfg(feature = "socket-raw")] use socket::RawSocket; -#[cfg(all(feature = "socket-icmp", feature = "proto-ipv4"))] +#[cfg(all(feature = "socket-icmp", any(feature = "proto-ipv4", feature = "proto-ipv6")))] use socket::IcmpSocket; #[cfg(feature = "socket-udp")] use socket::UdpSocket; @@ -21,7 +21,7 @@ pub trait Session { #[cfg(feature = "socket-raw")] impl<'a, 'b> Session for RawSocket<'a, 'b> {} -#[cfg(all(feature = "socket-icmp", feature = "proto-ipv4"))] +#[cfg(all(feature = "socket-icmp", any(feature = "proto-ipv4", feature = "proto-ipv6")))] impl<'a, 'b> Session for IcmpSocket<'a, 'b> {} #[cfg(feature = "socket-udp")] impl<'a, 'b> Session for UdpSocket<'a, 'b> {} diff --git a/src/socket/set.rs b/src/socket/set.rs index 93936fe..9c81aee 100644 --- a/src/socket/set.rs +++ b/src/socket/set.rs @@ -144,7 +144,7 @@ impl<'a, 'b: 'a, 'c: 'a + 'b> Set<'a, 'b, 'c> { #[cfg(feature = "socket-raw")] &mut Socket::Raw(_) => may_remove = true, - #[cfg(all(feature = "socket-icmp", feature = "proto-ipv4"))] + #[cfg(all(feature = "socket-icmp", any(feature = "proto-ipv4", feature = "proto-ipv6")))] &mut Socket::Icmp(_) => may_remove = true, #[cfg(feature = "socket-udp")] diff --git a/src/wire/icmp.rs b/src/wire/icmp.rs new file mode 100644 index 0000000..684ea5e --- /dev/null +++ b/src/wire/icmp.rs @@ -0,0 +1,24 @@ +#[cfg(feature = "proto-ipv4")] +use super::icmpv4; +#[cfg(feature = "proto-ipv6")] +use super::icmpv6; + +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum Repr<'a> { + #[cfg(feature = "proto-ipv4")] + Ipv4(icmpv4::Repr<'a>), + #[cfg(feature = "proto-ipv6")] + Ipv6(icmpv6::Repr<'a>), +} +#[cfg(feature = "proto-ipv4")] +impl<'a> From> for Repr<'a> { + fn from(s: icmpv4::Repr<'a>) -> Self { + Repr::Ipv4(s) + } +} +#[cfg(feature = "proto-ipv6")] +impl<'a> From> for Repr<'a> { + fn from(s: icmpv6::Repr<'a>) -> Self { + Repr::Ipv6(s) + } +} diff --git a/src/wire/mod.rs b/src/wire/mod.rs index e64974c..024acc7 100644 --- a/src/wire/mod.rs +++ b/src/wire/mod.rs @@ -95,6 +95,8 @@ mod ipv6fragment; mod icmpv4; #[cfg(feature = "proto-ipv6")] mod icmpv6; +#[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))] +mod icmp; #[cfg(feature = "proto-ipv4")] mod igmp; #[cfg(feature = "proto-ipv6")] @@ -174,6 +176,8 @@ 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::Repr as IcmpRepr; #[cfg(feature = "proto-ipv6")] pub use self::icmpv6::{RouterFlags as NdiscRouterFlags,