Compare commits

...

16 Commits

Author SHA1 Message Date
02b0714302
Merge pull request #4 from zaphar/allow_dgram_sockets
Add dgram sockets as an option
2022-11-23 18:07:35 -05:00
b01f831a61 Add dgram sockets as an option
Closes #3
2022-11-22 13:50:43 -05:00
6d12901286 Bump version to v0.2.0 2022-02-09 18:57:34 -05:00
4c666baf78 Merge branch 'master' of github.com:zaphar/icmp-socket 2022-02-09 18:53:38 -05:00
dad85434a4
Merge pull request #2 from ishbosamiya/correct_timeout_handling
fix: timeout value is overridden by rcv_from()
2022-02-08 08:25:05 -05:00
ishbosamiya
65ef9acf45 examples: fix examples for recent change to socket timeout 2022-02-08 13:10:02 +05:30
ishbosamiya
b30dfb48bd socket: fix: socket timeout handling
rcv_from() would always overwrite the timeout parameter set by the
user.

Instead of setting the timeout within the socket when set_timeout() is
called, store the timeout information within the options (along with
hops) and read that data when the sockets timeout is actually set in
rcv_from().
2022-02-08 13:06:35 +05:30
a798645326 Opts doesnt need to be public 2022-02-07 15:41:15 -05:00
07c88084d8 Minor Readme fixes 2022-02-07 15:39:26 -05:00
d5672b00c6
Merge pull request #1 from zaphar/socket2v0.4
Update to the v0.4x branch of socket2
2022-02-02 23:14:13 -05:00
3b5f3f9a25 Update to the v0.4x branch of socket2 2022-02-02 23:07:24 -05:00
5a00edf5e8 Update readme and docs 2022-02-02 22:48:32 -05:00
2dda318253 Bump Version 2021-02-04 17:13:34 -05:00
6eb42ddf36 Add other traits for packet construction 2021-02-01 18:18:08 -05:00
3a73734015 Better timeout management in the API 2021-02-01 17:13:55 -05:00
e331d94a26 Expose errors when setting timeouts 2021-02-01 17:06:23 -05:00
7 changed files with 350 additions and 114 deletions

View File

@ -1,7 +1,7 @@
[package]
name = "icmp-socket"
description = "ICMP sockets for both IPv4 and IPv6"
version = "0.1.0"
version = "0.2.0"
repository = "https://github.com/zaphar/icmp-socket"
authors = ["Jeremy Wall <jeremy@marzhillstudios.com>"]
edition = "2018"
@ -10,5 +10,8 @@ license = "Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
socket2 = "0.3.19"
byteorder = "1.3.4"
byteorder = "1.3.4"
[dependencies.socket2]
version = "0.4.4"
features=[ "all" ]

View File

@ -1 +1,21 @@
# ICMP Sockets for both IPv4 and IPv6
# ICMP Sockets for both IPv4 and IPv6
An implementation of ICMP Sockets for both IPv4 and IPv6.
Sockets can be created from IP addresses. IPv4 addresses will construct ICMP4 sockets. IPv6 will construct ICMP6 sockets.
```rust
let parsed_addr = "127.0.0.1".parse::<Ipv4Addr>().unwrap();
let socket = IcmpSocket4::try_from(parsed_addr).unwrap();
```
It can construct and parse the common ICMP packets for both ICMP4 and ICMP6.
```rust
let packet4 = Icmpv4Packet::with_echo_request(42, 1, "payload".to_bytes());
let packet6 = Icmpv6Packet::with_echo_request(42, 1, "payload".to_bytes());
```
# API Documentation
https://docs.rs/icmp-socket/0.2.0

View File

@ -74,15 +74,17 @@ pub fn main() {
socket4
.send_to(address.parse::<Ipv4Addr>().unwrap(), packet)
.unwrap();
socket4.set_timeout(Some(Duration::from_secs(1)));
loop {
let (resp, sock_addr) = match socket4.rcv_with_timeout(Duration::from_secs(1)) {
let (resp, sock_addr) = match socket4.rcv_from() {
Ok(tpl) => tpl,
Err(e) => {
//eprintln!("{:?}", e);
eprintln!("{:?}", e);
break;
}
};
if packet_handler(resp, send_time, *sock_addr.as_inet().unwrap().ip()).is_some() {
if packet_handler(resp, send_time, *sock_addr.as_socket_ipv4().unwrap().ip()).is_some()
{
std::thread::sleep(Duration::from_secs(1));
break;
}

View File

@ -72,15 +72,17 @@ pub fn main() {
socket6
.send_to(address.parse::<Ipv6Addr>().unwrap(), packet)
.unwrap();
socket6.set_timeout(Some(Duration::from_secs(1)));
loop {
let (resp, sock_addr) = match socket6.rcv_with_timeout(Duration::from_secs(1)) {
let (resp, sock_addr) = match socket6.rcv_from() {
Ok(tpl) => tpl,
Err(e) => {
//eprintln!("{:?}", e);
eprintln!("{:?}", e);
break;
}
};
if packet_handler(resp, send_time, *sock_addr.as_inet6().unwrap().ip()).is_some() {
if packet_handler(resp, send_time, *sock_addr.as_socket_ipv6().unwrap().ip()).is_some()
{
std::thread::sleep(Duration::from_millis(1000));
break;
}

View File

@ -11,7 +11,10 @@
// 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.
//! An ICMP socket library that tries to be ergonomic to use.
//!
//! The standard ping examples for both Ipv6 and IPv4 are in the examples
//! directory.
pub mod packet;
pub mod socket;

View File

@ -11,6 +11,38 @@
// 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.
//! Packet parsing and construction.
//!
//! Where possible we use traits to support a common API for constructing the
//! ICMPv4 and ICMPv6 versions of the packets.
//!
//! Both packet types can be constructed from a slice: `&[u8]` via the [`TryFrom`] trait.
//!
//! # Examples
//!
//! Constructing an ICMPv4 echo request.
//! ```
//! # use icmp_socket::packet::*;
//! let packet = Icmpv4Packet::with_echo_request(
//! 42, // An identifier so you can recognize responses to your own packets.
//! 0, // the first echo request packet in our sequence.
//! "a payload big enough to matter".as_bytes().to_vec()
//! ).unwrap();
//! ```
//!
//! Parsing an ICMPv4 packet from a byte buffer.
//! ```
//! # use icmp_socket::packet::*;
//! use std::convert::TryFrom;
//! # let packet = Icmpv4Packet::with_echo_request(
//! # 42, // An identifier so you can recognize responses to your own packets.
//! # 0, // the first echo request packet in our sequence.
//! # "a payload big enough to matter".as_bytes().to_vec()
//! # ).unwrap();
//! # let mut byte_buffer = vec![0; 20];
//! # byte_buffer.extend(packet.get_bytes(true)); // convert a packet to bytes with a checksum.
//! let parsed_packet = Icmpv4Packet::try_from(byte_buffer.as_slice()).unwrap();
//! ```
use std::convert::TryFrom;
use byteorder::{BigEndian, ByteOrder};
@ -42,6 +74,7 @@ fn sum_big_endian_words(bs: &[u8]) -> u32 {
return sum;
}
/// Construct a packet for the EchoRequest messages.
pub trait WithEchoRequest {
type Packet;
@ -52,6 +85,45 @@ pub trait WithEchoRequest {
) -> Result<Self::Packet, IcmpPacketBuildError>;
}
/// Construct a packet for Echo Reply messages.
/// This packet type is really only used for the ICMPv6 protocol.
pub trait WithEchoReply {
type Packet;
fn with_echo_reply(
identifier: u16,
sequence: u16,
payload: Vec<u8>,
) -> Result<Self::Packet, IcmpPacketBuildError>;
}
/// Construct a packet for Destination Unreachable messages.
pub trait WithUnreachable {
type Packet;
fn with_unreachable(code: u8, packet: Vec<u8>) -> Result<Self::Packet, IcmpPacketBuildError>;
}
/// Construct a packet for Parameter Problem messages.
pub trait WithParameterProblem {
type Packet;
type Pointer;
fn with_parameter_problem(
code: u8,
pointer: Self::Pointer,
packet: Vec<u8>,
) -> Result<Self::Packet, IcmpPacketBuildError>;
}
/// Construct a packet for Time Exceeded messages.
pub trait WithTimeExceeded {
type Packet;
fn with_time_exceeded(code: u8, packet: Vec<u8>) -> Result<Self::Packet, IcmpPacketBuildError>;
}
/// The possible Icmpv6 Message types.
#[derive(Debug, PartialEq)]
pub enum Icmpv6Message {
// NOTE(JWALL): All of the below integers should be parsed as big endian on the
@ -94,6 +166,7 @@ use Icmpv6Message::{
};
impl Icmpv6Message {
/// Get this Icmpv6Message serialized to bytes.
pub fn get_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
match self {
@ -155,9 +228,12 @@ pub struct Icmpv6Packet {
pub message: Icmpv6Message,
}
/// Error type returned by parsing the ICMP packets.
#[derive(Debug)]
pub enum PacketParseError {
/// Not enough bytes to properly parse the packet from.
PacketTooSmall(usize),
/// An unrecognized ICMP type.
UnrecognizedICMPType(u8),
}
@ -258,24 +334,6 @@ impl Icmpv6Packet {
self
}
/// Construct a packet for Destination Unreachable messages.
pub fn with_unreachable(code: u8, packet: Vec<u8>) -> Result<Self, IcmpPacketBuildError> {
if code > 6 {
return Err(IcmpPacketBuildError::InvalidCode(code));
}
Ok(Self {
typ: 1,
code: code,
checksum: 0,
// TODO(jwall): Should we enforce that the packet isn't too big?
// It is not supposed to be larger than the minimum IPv6 MTU
message: Unreachable {
_unused: 0,
invoking_packet: packet,
},
})
}
/// Construct a packet for Packet Too Big messages.
pub fn with_packet_too_big(mtu: u32, packet: Vec<u8>) -> Result<Self, IcmpPacketBuildError> {
Ok(Self {
@ -290,62 +348,6 @@ impl Icmpv6Packet {
},
})
}
/// Construct a packet for Time Exceeded messages.
pub fn with_time_exceeded(code: u8, packet: Vec<u8>) -> Result<Self, IcmpPacketBuildError> {
if code > 1 {
return Err(IcmpPacketBuildError::InvalidCode(code));
}
Ok(Self {
typ: 3,
code: code,
checksum: 0,
// TODO(jwall): Should we enforce that the packet isn't too big?
// It is not supposed to be larger than the minimum IPv6 MTU
message: TimeExceeded {
_unused: 0,
invoking_packet: packet,
},
})
}
/// Construct a packet for Parameter Problem messages.
pub fn with_parameter_problem(
code: u8,
pointer: u32,
packet: Vec<u8>,
) -> Result<Self, IcmpPacketBuildError> {
if code > 1 {
return Err(IcmpPacketBuildError::InvalidCode(code));
}
Ok(Self {
typ: 4,
code: code,
checksum: 0,
message: ParameterProblem {
pointer: pointer,
invoking_packet: packet,
},
})
}
/// Construct a packet for Echo Reply messages.
pub fn with_echo_reply(
identifier: u16,
sequence: u16,
payload: Vec<u8>,
) -> Result<Self, IcmpPacketBuildError> {
Ok(Self {
typ: 129,
code: 0,
checksum: 0,
message: EchoReply {
identifier: identifier,
sequence: sequence,
payload: payload,
},
})
}
}
impl WithEchoRequest for Icmpv6Packet {
@ -369,6 +371,93 @@ impl WithEchoRequest for Icmpv6Packet {
}
}
impl WithEchoReply for Icmpv6Packet {
type Packet = Icmpv6Packet;
fn with_echo_reply(
identifier: u16,
sequence: u16,
payload: Vec<u8>,
) -> Result<Self, IcmpPacketBuildError> {
Ok(Self {
typ: 129,
code: 0,
checksum: 0,
message: EchoReply {
identifier: identifier,
sequence: sequence,
payload: payload,
},
})
}
}
impl WithUnreachable for Icmpv6Packet {
type Packet = Icmpv6Packet;
fn with_unreachable(code: u8, packet: Vec<u8>) -> Result<Self, IcmpPacketBuildError> {
if code > 6 {
return Err(IcmpPacketBuildError::InvalidCode(code));
}
Ok(Self {
typ: 1,
code: code,
checksum: 0,
// TODO(jwall): Should we enforce that the packet isn't too big?
// It is not supposed to be larger than the minimum IPv6 MTU
message: Unreachable {
_unused: 0,
invoking_packet: packet,
},
})
}
}
impl WithParameterProblem for Icmpv6Packet {
type Packet = Icmpv6Packet;
type Pointer = u32;
fn with_parameter_problem(
code: u8,
pointer: Self::Pointer,
packet: Vec<u8>,
) -> Result<Self, IcmpPacketBuildError> {
if code > 1 {
return Err(IcmpPacketBuildError::InvalidCode(code));
}
Ok(Self {
typ: 4,
code: code,
checksum: 0,
message: ParameterProblem {
pointer: pointer,
invoking_packet: packet,
},
})
}
}
impl WithTimeExceeded for Icmpv6Packet {
type Packet = Icmpv6Packet;
fn with_time_exceeded(code: u8, packet: Vec<u8>) -> Result<Self, IcmpPacketBuildError> {
if code > 1 {
return Err(IcmpPacketBuildError::InvalidCode(code));
}
Ok(Self {
typ: 3,
code: code,
checksum: 0,
// TODO(jwall): Should we enforce that the packet isn't too big?
// It is not supposed to be larger than the minimum IPv6 MTU
message: TimeExceeded {
_unused: 0,
invoking_packet: packet,
},
})
}
}
impl TryFrom<&[u8]> for Icmpv6Packet {
type Error = PacketParseError;
fn try_from(b: &[u8]) -> Result<Self, Self::Error> {
@ -376,8 +465,10 @@ impl TryFrom<&[u8]> for Icmpv6Packet {
}
}
/// Errors returned by constructors for a given packet.
#[derive(Debug, PartialEq)]
pub enum IcmpPacketBuildError {
/// The code passed in for the payload was invalid for the message type.
InvalidCode(u8),
}
use IcmpPacketBuildError::InvalidCode;
@ -419,6 +510,7 @@ impl From<PacketParseError> for std::io::Error {
}
}
/// The various messages for an Icmpv4 packet.
#[derive(Debug)]
pub enum Icmpv4Message {
Unreachable {
@ -488,6 +580,7 @@ pub enum Icmpv4Message {
}
impl Icmpv4Message {
/// Get this Icmpv4Message serialized as bytes.
pub fn get_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(20);
match self {
@ -599,6 +692,7 @@ impl Icmpv4Message {
}
}
/// An Icmpv4 Packet.
#[derive(Debug)]
pub struct Icmpv4Packet {
pub typ: u8,
@ -608,6 +702,7 @@ pub struct Icmpv4Packet {
}
impl Icmpv4Packet {
/// Parse an Icmpv4Packet from bytes including the IPv4 header.
pub fn parse<B: AsRef<[u8]>>(bytes: B) -> Result<Self, PacketParseError> {
let mut bytes = bytes.as_ref();
let mut packet_len = bytes.len();
@ -721,6 +816,7 @@ impl Icmpv4Packet {
!sum as u16
}
/// Populate the checksum field of this Packet.
pub fn with_checksum(mut self) -> Self {
self.checksum = self.calculate_checksum();
self
@ -755,6 +851,69 @@ impl WithEchoRequest for Icmpv4Packet {
}
}
impl WithUnreachable for Icmpv4Packet {
type Packet = Icmpv4Packet;
fn with_unreachable(code: u8, packet: Vec<u8>) -> Result<Self::Packet, IcmpPacketBuildError> {
if code > 5 {
return Err(IcmpPacketBuildError::InvalidCode(code));
}
Ok(Self {
typ: 3,
code: code,
checksum: 0,
message: Icmpv4Message::Unreachable {
padding: 0,
header: packet,
},
})
}
}
impl WithParameterProblem for Icmpv4Packet {
type Packet = Icmpv4Packet;
type Pointer = u8;
fn with_parameter_problem(
code: u8,
pointer: Self::Pointer,
packet: Vec<u8>,
) -> Result<Self::Packet, IcmpPacketBuildError> {
if code != 0 {
return Err(IcmpPacketBuildError::InvalidCode(code));
}
Ok(Self {
typ: 12,
code: code,
checksum: 0,
message: Icmpv4Message::ParameterProblem {
pointer: pointer,
padding: (0, 0),
header: packet,
},
})
}
}
impl WithTimeExceeded for Icmpv4Packet {
type Packet = Icmpv4Packet;
fn with_time_exceeded(code: u8, packet: Vec<u8>) -> Result<Self::Packet, IcmpPacketBuildError> {
if code > 1 {
return Err(IcmpPacketBuildError::InvalidCode(code));
}
Ok(Self {
typ: 11,
code: code,
checksum: 0,
message: Icmpv4Message::TimeExceeded {
padding: 0,
header: packet,
},
})
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -11,10 +11,14 @@
// 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.
//! ICMP Socket implementations for both ICMP4 and ICMP6 protocols.
//!
//! There is a common IcmpSocket trait implemented for both the v4 and v6 protocols.
//! The socket is associated to both an address type and packet type.
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use std::{
convert::{Into, TryFrom, TryInto},
mem::MaybeUninit,
time::Duration,
};
@ -26,28 +30,38 @@ fn ip_to_socket(ip: &IpAddr) -> SocketAddr {
SocketAddr::new(*ip, 0)
}
/// Trait for an IcmpSocket implemented by Icmpv4Socket and Icmpv6Socket.
pub trait IcmpSocket {
/// The type of address this socket operates on.
type AddrType;
/// The type of packet this socket handles.
type PacketType;
/// Sets the timeout on the socket for rcv_from. A value of None
/// will cause rcv_from to block.
fn set_timeout(&mut self, timeout: Option<Duration>);
/// Sets the ttl for packets sent on this socket. Controls the number of
/// hops the packet will be allowed to traverse.
fn set_max_hops(&mut self, hops: u32);
/// Binds this socket to an address.
fn bind<A: Into<Self::AddrType>>(&mut self, addr: A) -> std::io::Result<()>;
/// Sends the packet to the given destination.
fn send_to(&mut self, dest: Self::AddrType, packet: Self::PacketType) -> std::io::Result<()>;
/// Receive a packet on this socket.
fn rcv_from(&mut self) -> std::io::Result<(Self::PacketType, SockAddr)>;
fn rcv_with_timeout(
&mut self,
timeout: Duration,
) -> std::io::Result<(Self::PacketType, SockAddr)>;
}
pub struct Opts {
/// Options for this socket.
struct Opts {
hops: u32,
timeout: Option<Duration>,
}
/// An ICMPv4 socket.
pub struct IcmpSocket4 {
bound_to: Option<Ipv4Addr>,
buf: Vec<u8>,
@ -56,16 +70,32 @@ pub struct IcmpSocket4 {
}
impl IcmpSocket4 {
/// Construct a new raw socket. The socket must be bound to an address using `bind_to`
/// before it can be used to send and receive packets.
pub fn new() -> std::io::Result<Self> {
let socket = Socket::new(Domain::ipv4(), Type::raw(), Some(Protocol::icmpv4()))?;
let socket = Socket::new(Domain::IPV4, Type::RAW, Some(Protocol::ICMPV4))?;
Self::new_from_socket(socket)
}
fn new_from_socket(socket: Socket) -> std::io::Result<Self> {
socket.set_recv_buffer_size(512)?;
Ok(Self {
bound_to: None,
inner: socket,
buf: vec![0; 512],
opts: Opts { hops: 50 },
opts: Opts {
hops: 50,
timeout: None,
},
})
}
/// Construct a new dgram socket. The socket must be bound to an address using `bind_to`
/// before it can be used to send and receive packets.
pub fn new_dgram_socket() -> std::io::Result<Self> {
let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::ICMPV4))?;
Self::new_from_socket(socket)
}
}
impl IcmpSocket for IcmpSocket4 {
@ -93,21 +123,22 @@ impl IcmpSocket for IcmpSocket4 {
}
fn rcv_from(&mut self) -> std::io::Result<(Self::PacketType, SockAddr)> {
self.inner.set_read_timeout(None);
let (read_count, addr) = self.inner.recv_from(&mut self.buf)?;
self.inner.set_read_timeout(self.opts.timeout)?;
// NOTE(jwall): the `recv_from` implementation promises not to write uninitialised
// bytes to the `buf`fer, so this casting is safe.
// TODO(jwall): change to `Vec::spare_capacity_mut` when it stabilizes.
let mut buf =
unsafe { &mut *(self.buf.as_mut_slice() as *mut [u8] as *mut [MaybeUninit<u8>]) };
let (read_count, addr) = self.inner.recv_from(&mut buf)?;
Ok((self.buf[0..read_count].try_into()?, addr))
}
fn rcv_with_timeout(
&mut self,
timeout: Duration,
) -> std::io::Result<(Self::PacketType, SockAddr)> {
self.inner.set_read_timeout(Some(timeout));
let (read_count, addr) = self.inner.recv_from(&mut self.buf)?;
Ok((self.buf[0..read_count].try_into()?, addr))
fn set_timeout(&mut self, timeout: Option<Duration>) {
self.opts.timeout = timeout;
}
}
/// An Icmpv6 socket.
pub struct IcmpSocket6 {
bound_to: Option<Ipv6Addr>,
inner: Socket,
@ -116,16 +147,32 @@ pub struct IcmpSocket6 {
}
impl IcmpSocket6 {
/// Construct a new raw socket. The socket must be bound to an address using `bind_to`
/// before it can be used to send and receive packets.
pub fn new() -> std::io::Result<Self> {
let socket = Socket::new(Domain::ipv6(), Type::raw(), Some(Protocol::icmpv6()))?;
let socket = Socket::new(Domain::IPV6, Type::RAW, Some(Protocol::ICMPV6))?;
Self::new_from_socket(socket)
}
fn new_from_socket(socket: Socket) -> std::io::Result<Self> {
socket.set_recv_buffer_size(512)?;
Ok(Self {
bound_to: None,
inner: socket,
buf: vec![0; 512],
opts: Opts { hops: 50 },
opts: Opts {
hops: 50,
timeout: None,
},
})
}
/// Construct a new dgram socket. The socket must be bound to an address using `bind_to`
/// before it can be used to send and receive packets.
pub fn new_dgram_socket() -> std::io::Result<Self> {
let socket = Socket::new(Domain::IPV6, Type::DGRAM, Some(Protocol::ICMPV6))?;
Self::new_from_socket(socket)
}
}
impl IcmpSocket for IcmpSocket6 {
@ -167,18 +214,18 @@ impl IcmpSocket for IcmpSocket6 {
}
fn rcv_from(&mut self) -> std::io::Result<(Self::PacketType, SockAddr)> {
self.inner.set_read_timeout(None);
let (read_count, addr) = self.inner.recv_from(&mut self.buf)?;
self.inner.set_read_timeout(self.opts.timeout)?;
// NOTE(jwall): the `recv_from` implementation promises not to write uninitialised
// bytes to the `buf`fer, so this casting is safe.
// TODO(jwall): change to `Vec::spare_capacity_mut` when it stabilizes.
let mut buf =
unsafe { &mut *(self.buf.as_mut_slice() as *mut [u8] as *mut [MaybeUninit<u8>]) };
let (read_count, addr) = self.inner.recv_from(&mut buf)?;
Ok((self.buf[0..read_count].try_into()?, addr))
}
fn rcv_with_timeout(
&mut self,
timeout: Duration,
) -> std::io::Result<(Self::PacketType, SockAddr)> {
self.inner.set_read_timeout(Some(timeout));
let (read_count, addr) = self.inner.recv_from(&mut self.buf)?;
Ok((self.buf[0..read_count].try_into()?, addr))
fn set_timeout(&mut self, timeout: Option<Duration>) {
self.opts.timeout = timeout;
}
}