From a7079afbc47f010762b81ee91efeee9e5b7ec3ae Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Tue, 26 Jan 2021 20:15:33 -0500 Subject: [PATCH] More ergonomics and some packet parsing bug fixes --- examples/ping4.rs | 44 ++++++++++++++++ examples/ping6.rs | 4 +- src/echo.rs | 80 +++++++++------------------- src/lib.rs | 2 +- src/packet.rs | 131 ++++++++++++++++++++++++---------------------- 5 files changed, 139 insertions(+), 122 deletions(-) create mode 100644 examples/ping4.rs diff --git a/examples/ping4.rs b/examples/ping4.rs new file mode 100644 index 0000000..4e571c7 --- /dev/null +++ b/examples/ping4.rs @@ -0,0 +1,44 @@ +// Copyright 2021 Jeremy Wall +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use std::net::Ipv4Addr; + +use icmp_socket::socket::IcmpSocket; +use icmp_socket::*; + +pub fn main() { + let mut socket4 = IcmpSocket4::new().unwrap(); + socket4 + .bind("0.0.0.0".parse::().unwrap()) + .unwrap(); + let mut echo_socket = echo::EchoSocket::new(socket4); + echo_socket + .send_ping( + "127.0.0.1".parse::().unwrap(), + 42, + &[ + 0x20, 0x20, 0x75, 0x73, 0x74, 0x20, 0x61, 0x20, 0x66, 0x6c, 0x65, 0x73, 0x68, 0x20, + 0x77, 0x6f, 0x75, 0x6e, 0x64, 0x20, 0x20, 0x74, 0x69, 0x73, 0x20, 0x62, 0x75, 0x74, + 0x20, 0x61, 0x20, 0x73, 0x63, 0x72, 0x61, 0x74, 0x63, 0x68, 0x20, 0x20, 0x6b, 0x6e, + 0x69, 0x67, 0x68, 0x74, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x6e, 0x69, 0x20, 0x20, 0x20, + ], + ) + .unwrap(); + let resp = echo_socket.recv_ping().unwrap(); + println!( + "seq: {}, identifier: {} payload: {}", + resp.sequence, + resp.identifier, + resp.payload.len() + ); +} diff --git a/examples/ping6.rs b/examples/ping6.rs index 8d1b561..989e318 100644 --- a/examples/ping6.rs +++ b/examples/ping6.rs @@ -19,7 +19,7 @@ use icmp_socket::*; pub fn main() { let mut socket6 = IcmpSocket6::new().unwrap(); socket6.bind("::1".parse::().unwrap()).unwrap(); - let mut echo_socket = echo::EchoSocket6::new(socket6); + let mut echo_socket = echo::EchoSocket::new(socket6); echo_socket .send_ping( "::1".parse::().unwrap(), @@ -32,6 +32,8 @@ pub fn main() { ], ) .unwrap(); + // TODO(jwall): The first packet we recieve will be the one we sent. + // We need to implement packet filtering for the socket. let _ = echo_socket.recv_ping(); let resp = echo_socket.recv_ping().unwrap(); println!( diff --git a/src/echo.rs b/src/echo.rs index 48cfcf7..0f41ed7 100644 --- a/src/echo.rs +++ b/src/echo.rs @@ -12,11 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. use std::convert::{From, TryFrom, TryInto}; -use std::net::{Ipv4Addr, Ipv6Addr}; use crate::{ - packet::{Icmpv4Message, Icmpv4Packet, Icmpv6Message, Icmpv6Packet}, - socket::{IcmpSocket, IcmpSocket4, IcmpSocket6}, + packet::{Icmpv4Message, Icmpv4Packet, Icmpv6Message, Icmpv6Packet, WithEchoRequest}, + socket::IcmpSocket, }; #[derive(Debug)] @@ -80,15 +79,21 @@ impl TryFrom for EchoResponse { } } -pub struct EchoSocket4 { +pub struct EchoSocket { sequence: u16, - inner: IcmpSocket4, + inner: S, } // TODO(jwall): Make this a trait -impl EchoSocket4 { - pub fn new(sock: IcmpSocket4) -> Self { - EchoSocket4 { +impl EchoSocket +where + S: IcmpSocket, + S::PacketType: WithEchoRequest + + TryInto + + std::fmt::Debug, +{ + pub fn new(sock: S) -> Self { + EchoSocket { inner: sock, sequence: 0, } @@ -100,12 +105,12 @@ impl EchoSocket4 { pub fn send_ping( &mut self, - dest: Ipv4Addr, + dest: S::AddrType, identifier: u16, payload: &[u8], ) -> std::io::Result<()> { let packet = - Icmpv4Packet::with_echo_request(identifier, self.sequence, payload.to_owned())?; + S::PacketType::with_echo_request(identifier, self.sequence, payload.to_owned())?; self.sequence += 1; self.inner.send_to(dest, packet)?; Ok(()) @@ -117,51 +122,14 @@ impl EchoSocket4 { } } -impl From for EchoSocket4 { - fn from(sock: IcmpSocket4) -> Self { - EchoSocket4::new(sock) - } -} - -pub struct EchoSocket6 { - sequence: u16, - inner: IcmpSocket6, -} - -impl EchoSocket6 { - pub fn new(sock: IcmpSocket6) -> Self { - // TODO(jwall): How to set ICMPv6 filters. - EchoSocket6 { - inner: sock, - sequence: 0, - } - } - - pub fn set_max_hops(&mut self, hops: u32) { - self.inner.set_max_hops(hops); - } - - pub fn send_ping( - &mut self, - dest: Ipv6Addr, - identifier: u16, - payload: &[u8], - ) -> std::io::Result<()> { - let packet = - Icmpv6Packet::with_echo_request(identifier, self.sequence, payload.to_owned())?; - self.sequence += 1; - self.inner.send_to(dest, packet)?; - Ok(()) - } - - pub fn recv_ping(&mut self) -> std::io::Result { - let pkt = self.inner.rcv_from()?; - Ok(pkt.try_into()?) - } -} - -impl From for EchoSocket6 { - fn from(sock: IcmpSocket6) -> Self { - EchoSocket6::new(sock) +impl From for EchoSocket +where + S: IcmpSocket, + S::PacketType: WithEchoRequest + + TryInto + + std::fmt::Debug, +{ + fn from(sock: S) -> Self { + EchoSocket::new(sock) } } diff --git a/src/lib.rs b/src/lib.rs index cc1928f..af4f17a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,6 @@ pub mod echo; pub mod packet; pub mod socket; -pub use echo::{EchoSocket4, EchoSocket6}; +pub use echo::EchoSocket; pub use packet::{Icmpv4Message, Icmpv4Packet, Icmpv6Message, Icmpv6Packet}; pub use socket::{IcmpSocket, IcmpSocket4, IcmpSocket6}; diff --git a/src/packet.rs b/src/packet.rs index 8f3830a..e6b307c 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -42,6 +42,16 @@ fn sum_big_endian_words(bs: &[u8]) -> u32 { return sum; } +pub trait WithEchoRequest { + type Packet; + + fn with_echo_request( + identifier: u16, + sequence: u16, + payload: Vec, + ) -> Result; +} + #[derive(Debug, PartialEq)] pub enum Icmpv6Message { // NOTE(JWALL): All of the below integers should be parsed as big endian on the @@ -148,7 +158,7 @@ pub struct Icmpv6Packet { #[derive(Debug)] pub enum PacketParseError { PacketTooSmall(usize), - UnrecognizedICMPType, + UnrecognizedICMPType(u8), } impl Icmpv6Packet { @@ -193,7 +203,7 @@ impl Icmpv6Packet { sequence: BigEndian::read_u16(&bytes[6..8]), payload: payload, }, - _ => return Err(PacketParseError::UnrecognizedICMPType), + t => return Err(PacketParseError::UnrecognizedICMPType(t)), }; return Ok(Icmpv6Packet { typ: typ, @@ -249,9 +259,9 @@ impl Icmpv6Packet { } /// Construct a packet for Destination Unreachable messages. - pub fn with_unreachable(code: u8, packet: Vec) -> Result { + pub fn with_unreachable(code: u8, packet: Vec) -> Result { if code > 6 { - return Err(Icmpv6PacketBuildError::InvalidCode(code)); + return Err(IcmpPacketBuildError::InvalidCode(code)); } Ok(Self { typ: 1, @@ -267,7 +277,7 @@ impl Icmpv6Packet { } /// Construct a packet for Packet Too Big messages. - pub fn with_packet_too_big(mtu: u32, packet: Vec) -> Result { + pub fn with_packet_too_big(mtu: u32, packet: Vec) -> Result { Ok(Self { typ: 2, code: 0, @@ -282,9 +292,9 @@ impl Icmpv6Packet { } /// Construct a packet for Time Exceeded messages. - pub fn with_time_exceeded(code: u8, packet: Vec) -> Result { + pub fn with_time_exceeded(code: u8, packet: Vec) -> Result { if code > 1 { - return Err(Icmpv6PacketBuildError::InvalidCode(code)); + return Err(IcmpPacketBuildError::InvalidCode(code)); } Ok(Self { typ: 3, @@ -304,9 +314,9 @@ impl Icmpv6Packet { code: u8, pointer: u32, packet: Vec, - ) -> Result { + ) -> Result { if code > 1 { - return Err(Icmpv6PacketBuildError::InvalidCode(code)); + return Err(IcmpPacketBuildError::InvalidCode(code)); } Ok(Self { typ: 4, @@ -319,35 +329,38 @@ impl Icmpv6Packet { }) } - /// Construct a packet for Echo Request messages. - pub fn with_echo_request( + /// Construct a packet for Echo Reply messages. + pub fn with_echo_reply( identifier: u16, sequence: u16, payload: Vec, - ) -> Result { + ) -> Result { Ok(Self { - typ: 128, + typ: 129, code: 0, checksum: 0, - message: EchoRequest { + message: EchoReply { identifier: identifier, sequence: sequence, payload: payload, }, }) } +} - /// Construct a packet for Echo Reply messages. - pub fn with_echo_reply( +impl WithEchoRequest for Icmpv6Packet { + type Packet = Icmpv6Packet; + + fn with_echo_request( identifier: u16, sequence: u16, payload: Vec, - ) -> Result { + ) -> Result { Ok(Self { - typ: 129, + typ: 128, code: 0, checksum: 0, - message: EchoReply { + message: EchoRequest { identifier: identifier, sequence: sequence, payload: payload, @@ -364,12 +377,12 @@ impl TryFrom<&[u8]> for Icmpv6Packet { } #[derive(Debug, PartialEq)] -pub enum Icmpv6PacketBuildError { +pub enum IcmpPacketBuildError { InvalidCode(u8), } -use Icmpv6PacketBuildError::InvalidCode; +use IcmpPacketBuildError::InvalidCode; -impl std::fmt::Display for Icmpv6PacketBuildError { +impl std::fmt::Display for IcmpPacketBuildError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, @@ -388,14 +401,14 @@ impl std::fmt::Display for PacketParseError { "{}", match self { PacketParseError::PacketTooSmall(c) => format!("Packet Too Small size: {}", c), - PacketParseError::UnrecognizedICMPType => "UnrecognizedIcmpType".to_owned(), + PacketParseError::UnrecognizedICMPType(t) => format!("UnrecognizedIcmpType({})", t), } ) } } -impl From for std::io::Error { - fn from(err: Icmpv6PacketBuildError) -> Self { +impl From for std::io::Error { + fn from(err: IcmpPacketBuildError) -> Self { std::io::Error::new(std::io::ErrorKind::Other, format!("{}", err)) } } @@ -596,12 +609,15 @@ pub struct Icmpv4Packet { impl Icmpv4Packet { pub fn parse>(bytes: B) -> Result { - let bytes = bytes.as_ref(); - // NOTE(jwall): All ICMP packets are at least 8 bytes long. - let packet_len = bytes.len(); - if packet_len < 8 { - return Err(PacketParseError::PacketTooSmall(bytes.len())); + let mut bytes = bytes.as_ref(); + let mut packet_len = bytes.len(); + if bytes.len() < 28 { + return Err(PacketParseError::PacketTooSmall(packet_len)); } + // NOTE(jwall) Because we use raw sockets the first 20 bytes are the IPv4 header. + bytes = &bytes[20..]; + // NOTE(jwall): All ICMP packets are at least 8 bytes long. + packet_len = bytes.len(); let (typ, code, checksum) = (bytes[0], bytes[1], BigEndian::read_u16(&bytes[2..4])); let message = match typ { 3 => Icmpv4Message::Unreachable { @@ -638,8 +654,6 @@ impl Icmpv4Packet { identifier: BigEndian::read_u16(&bytes[4..6]), sequence: BigEndian::read_u16(&bytes[6..8]), }, - // FIXME(jwall): Some of the below have size requirements larger than - // 8 13 => { if packet_len < 20 { return Err(PacketParseError::PacketTooSmall(bytes.len())); @@ -664,7 +678,10 @@ impl Icmpv4Packet { transmit: BigEndian::read_u32(&bytes[16..20]), } } - _ => return Err(PacketParseError::UnrecognizedICMPType), + t => { + dbg!(bytes); + return Err(PacketParseError::UnrecognizedICMPType(t)); + } }; return Ok(Icmpv4Packet { typ: typ, @@ -679,8 +696,7 @@ impl Icmpv4Packet { let mut bytes = Vec::new(); bytes.push(self.typ); bytes.push(self.code); - let mut buf = Vec::with_capacity(2); - buf.resize(2, 0); + let mut buf = vec![0; 2]; BigEndian::write_u16(&mut buf, if with_checksum { self.checksum } else { 0 }); bytes.append(&mut buf); bytes.append(&mut self.message.get_bytes()); @@ -709,12 +725,23 @@ impl Icmpv4Packet { self.checksum = self.calculate_checksum(); self } +} - pub fn with_echo_request( +impl TryFrom<&[u8]> for Icmpv4Packet { + type Error = PacketParseError; + fn try_from(b: &[u8]) -> Result { + Icmpv4Packet::parse(b) + } +} + +impl WithEchoRequest for Icmpv4Packet { + type Packet = Icmpv4Packet; + + fn with_echo_request( identifier: u16, sequence: u16, payload: Vec, - ) -> Result { + ) -> Result { Ok(Self { typ: 8, code: 0, @@ -726,34 +753,10 @@ impl Icmpv4Packet { }, }) } - - pub fn with_echo_reply( - identifier: u16, - sequence: u16, - payload: Vec, - ) -> Result { - Ok(Self { - typ: 0, - code: 0, - checksum: 0, - message: Icmpv4Message::EchoReply { - identifier, - sequence, - payload, - }, - }) - } -} - -impl TryFrom<&[u8]> for Icmpv4Packet { - type Error = PacketParseError; - fn try_from(b: &[u8]) -> Result { - Icmpv4Packet::parse(b) - } } #[cfg(test)] -mod checksum_tests { +mod tests { use super::*; #[test] @@ -819,7 +822,7 @@ mod checksum_tests { let pkt = Icmpv6Packet::with_time_exceeded(2, vec![1, 2, 3, 4]); assert!(pkt.is_err()); let e = pkt.unwrap_err(); - assert_eq!(e, Icmpv6PacketBuildError::InvalidCode(2)); + assert_eq!(e, IcmpPacketBuildError::InvalidCode(2)); } #[test] @@ -842,7 +845,7 @@ mod checksum_tests { let pkt = Icmpv6Packet::with_parameter_problem(3, 30, vec![1, 2, 3, 4]); assert!(pkt.is_err()); let e = pkt.unwrap_err(); - assert_eq!(e, Icmpv6PacketBuildError::InvalidCode(3)); + assert_eq!(e, IcmpPacketBuildError::InvalidCode(3)); } #[test]