mirror of
https://github.com/zaphar/icmp-socket.git
synced 2025-07-21 19:29:47 -04:00
More ergonomics and some packet parsing bug fixes
This commit is contained in:
parent
5a7287f10c
commit
a7079afbc4
44
examples/ping4.rs
Normal file
44
examples/ping4.rs
Normal file
@ -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::<Ipv4Addr>().unwrap())
|
||||
.unwrap();
|
||||
let mut echo_socket = echo::EchoSocket::new(socket4);
|
||||
echo_socket
|
||||
.send_ping(
|
||||
"127.0.0.1".parse::<Ipv4Addr>().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()
|
||||
);
|
||||
}
|
@ -19,7 +19,7 @@ use icmp_socket::*;
|
||||
pub fn main() {
|
||||
let mut socket6 = IcmpSocket6::new().unwrap();
|
||||
socket6.bind("::1".parse::<Ipv6Addr>().unwrap()).unwrap();
|
||||
let mut echo_socket = echo::EchoSocket6::new(socket6);
|
||||
let mut echo_socket = echo::EchoSocket::new(socket6);
|
||||
echo_socket
|
||||
.send_ping(
|
||||
"::1".parse::<Ipv6Addr>().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!(
|
||||
|
80
src/echo.rs
80
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<Icmpv4Packet> for EchoResponse {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EchoSocket4 {
|
||||
pub struct EchoSocket<S> {
|
||||
sequence: u16,
|
||||
inner: IcmpSocket4,
|
||||
inner: S,
|
||||
}
|
||||
|
||||
// TODO(jwall): Make this a trait
|
||||
impl EchoSocket4 {
|
||||
pub fn new(sock: IcmpSocket4) -> Self {
|
||||
EchoSocket4 {
|
||||
impl<S> EchoSocket<S>
|
||||
where
|
||||
S: IcmpSocket,
|
||||
S::PacketType: WithEchoRequest<Packet = S::PacketType>
|
||||
+ TryInto<EchoResponse, Error = std::io::Error>
|
||||
+ 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<IcmpSocket4> 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<EchoResponse> {
|
||||
let pkt = self.inner.rcv_from()?;
|
||||
Ok(pkt.try_into()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IcmpSocket6> for EchoSocket6 {
|
||||
fn from(sock: IcmpSocket6) -> Self {
|
||||
EchoSocket6::new(sock)
|
||||
impl<S> From<S> for EchoSocket<S>
|
||||
where
|
||||
S: IcmpSocket,
|
||||
S::PacketType: WithEchoRequest<Packet = S::PacketType>
|
||||
+ TryInto<EchoResponse, Error = std::io::Error>
|
||||
+ std::fmt::Debug,
|
||||
{
|
||||
fn from(sock: S) -> Self {
|
||||
EchoSocket::new(sock)
|
||||
}
|
||||
}
|
||||
|
@ -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};
|
||||
|
131
src/packet.rs
131
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<u8>,
|
||||
) -> Result<Self::Packet, IcmpPacketBuildError>;
|
||||
}
|
||||
|
||||
#[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<u8>) -> Result<Self, Icmpv6PacketBuildError> {
|
||||
pub fn with_unreachable(code: u8, packet: Vec<u8>) -> Result<Self, IcmpPacketBuildError> {
|
||||
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<u8>) -> Result<Self, Icmpv6PacketBuildError> {
|
||||
pub fn with_packet_too_big(mtu: u32, packet: Vec<u8>) -> Result<Self, IcmpPacketBuildError> {
|
||||
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<u8>) -> Result<Self, Icmpv6PacketBuildError> {
|
||||
pub fn with_time_exceeded(code: u8, packet: Vec<u8>) -> Result<Self, IcmpPacketBuildError> {
|
||||
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<u8>,
|
||||
) -> Result<Self, Icmpv6PacketBuildError> {
|
||||
) -> Result<Self, IcmpPacketBuildError> {
|
||||
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<u8>,
|
||||
) -> Result<Self, Icmpv6PacketBuildError> {
|
||||
) -> Result<Self, IcmpPacketBuildError> {
|
||||
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<u8>,
|
||||
) -> Result<Self, Icmpv6PacketBuildError> {
|
||||
) -> Result<Self::Packet, IcmpPacketBuildError> {
|
||||
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<Icmpv6PacketBuildError> for std::io::Error {
|
||||
fn from(err: Icmpv6PacketBuildError) -> Self {
|
||||
impl From<IcmpPacketBuildError> 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<B: AsRef<[u8]>>(bytes: B) -> Result<Self, PacketParseError> {
|
||||
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<Self, Self::Error> {
|
||||
Icmpv4Packet::parse(b)
|
||||
}
|
||||
}
|
||||
|
||||
impl WithEchoRequest for Icmpv4Packet {
|
||||
type Packet = Icmpv4Packet;
|
||||
|
||||
fn with_echo_request(
|
||||
identifier: u16,
|
||||
sequence: u16,
|
||||
payload: Vec<u8>,
|
||||
) -> Result<Self, Icmpv6PacketBuildError> {
|
||||
) -> Result<Self::Packet, IcmpPacketBuildError> {
|
||||
Ok(Self {
|
||||
typ: 8,
|
||||
code: 0,
|
||||
@ -726,34 +753,10 @@ impl Icmpv4Packet {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub fn with_echo_reply(
|
||||
identifier: u16,
|
||||
sequence: u16,
|
||||
payload: Vec<u8>,
|
||||
) -> Result<Self, Icmpv6PacketBuildError> {
|
||||
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<Self, Self::Error> {
|
||||
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]
|
||||
|
Loading…
x
Reference in New Issue
Block a user