Repository: fujiapple852/trippy
Pull request #1183 (2024-06-16 02:47:03)

refactor(packet): separate `packet` module into `trippy-packet` crate


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 69f8f78..c8a7b0d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2220,6 +2220,7 @@ dependencies = [
  "tokio-util",
  "tracing",
  "tracing-subscriber",
+ "trippy-packet",
  "tun2",
  "widestring",
  "windows-sys 0.52.0",
@@ -2238,6 +2239,16 @@ dependencies = [
  "thiserror",
 ]
 
+[[package]]
+name = "trippy-packet"
+version = "0.11.0-dev"
+dependencies = [
+ "anyhow",
+ "hex-literal",
+ "itertools 0.13.0",
+ "thiserror",
+]
+
 [[package]]
 name = "trippy-privilege"
 version = "0.11.0-dev"
diff --git a/Cargo.toml b/Cargo.toml
index 898da55..5a3e590 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [workspace]
 resolver = "2"
-members = ["crates/trippy", "crates/trippy-core", "crates/trippy-privilege", "crates/trippy-dns"]
+members = ["crates/trippy", "crates/trippy-core", "crates/trippy-packet", "crates/trippy-privilege", "crates/trippy-dns"]
 
 [workspace.package]
 version = "0.11.0-dev"
@@ -22,4 +22,5 @@ serde_yaml = "=0.9.33"
 tracing = { version = "0.1.40" }
 derive_more = { version = "0.99.17", default-features = false, features = [ "mul", "add", "add_assign" ] }
 test-case = "3.3.1"
-parking_lot = "0.12.3"
\ No newline at end of file
+parking_lot = "0.12.3"
+hex-literal = "0.4.1"
\ No newline at end of file
diff --git a/crates/README.md b/crates/README.md
index 9cd8070..9f4de99 100644
--- a/crates/README.md
+++ b/crates/README.md
@@ -20,6 +20,15 @@ the crate you would use if you wish to provide the Trippy tracing functionality
 cargo add trippy-core
 ```
 
+### `trippy-packet`
+
+A library crate which provides packet wire formats and packet parsing functionality. This crate is used by the Trippy
+application and is the crate you would use if you wish to provide packet parsing functionality in your own application.
+
+```shell
+cargo add trippy-packet
+```
+
 ### `trippy-dns`
 
 A library crate for performing forward and reverse lazy DNS resolution. This crate is designed to be used by the Trippy
diff --git a/crates/trippy-core/Cargo.toml b/crates/trippy-core/Cargo.toml
index 7a34346..512c594 100644
--- a/crates/trippy-core/Cargo.toml
+++ b/crates/trippy-core/Cargo.toml
@@ -14,6 +14,7 @@ keywords = ["traceroute", "ping", "icmp"]
 categories = ["network-programming"]
 
 [dependencies]
+trippy-packet = { version = "0.11.0-dev", path = "../trippy-packet" }
 derive_more.workspace = true
 thiserror.workspace = true
 tracing.workspace = true
@@ -44,7 +45,7 @@ anyhow.workspace = true
 serde.workspace = true
 test-case.workspace = true
 serde_yaml.workspace = true
-hex-literal = "0.4.1"
+hex-literal.workspace = true
 rand = "0.8.5"
 tokio = { version = "1.38.0", features = [ "full" ] }
 tokio-util = "0.7.11"
diff --git a/crates/trippy-core/src/error.rs b/crates/trippy-core/src/error.rs
index cfff3b7..d187feb 100644
--- a/crates/trippy-core/src/error.rs
+++ b/crates/trippy-core/src/error.rs
@@ -1,9 +1,9 @@
-use crate::packet::error::PacketError;
 use std::fmt::{Display, Formatter};
 use std::io;
 use std::io::ErrorKind;
 use std::net::{IpAddr, SocketAddr};
 use thiserror::Error;
+use trippy_packet::error::PacketError;
 
 /// A tracer error result.
 pub type TraceResult<T> = Result<T, TracerError>;
diff --git a/crates/trippy-core/src/lib.rs b/crates/trippy-core/src/lib.rs
index b83c9d4..e1b200e 100644
--- a/crates/trippy-core/src/lib.rs
+++ b/crates/trippy-core/src/lib.rs
@@ -64,9 +64,6 @@ mod probe;
 mod tracer;
 mod types;
 
-/// Packet wire formats.
-pub mod packet;
-
 pub use builder::Builder;
 pub use config::{
     defaults, ChannelConfig, ChannelConfigBuilder, Config, ConfigBuilder, IcmpExtensionParseMode,
diff --git a/crates/trippy-core/src/net/extension.rs b/crates/trippy-core/src/net/extension.rs
index 3148524..8903d43 100644
--- a/crates/trippy-core/src/net/extension.rs
+++ b/crates/trippy-core/src/net/extension.rs
@@ -1,10 +1,10 @@
 use crate::error::TracerError;
-use crate::packet::icmp_extension::extension_header::ExtensionHeaderPacket;
-use crate::packet::icmp_extension::extension_object::{ClassNum, ExtensionObjectPacket};
-use crate::packet::icmp_extension::extension_structure::ExtensionsPacket;
-use crate::packet::icmp_extension::mpls_label_stack::MplsLabelStackPacket;
-use crate::packet::icmp_extension::mpls_label_stack_member::MplsLabelStackMemberPacket;
 use crate::probe::{Extension, Extensions, MplsLabelStack, MplsLabelStackMember, UnknownExtension};
+use trippy_packet::icmp_extension::extension_header::ExtensionHeaderPacket;
+use trippy_packet::icmp_extension::extension_object::{ClassNum, ExtensionObjectPacket};
+use trippy_packet::icmp_extension::extension_structure::ExtensionsPacket;
+use trippy_packet::icmp_extension::mpls_label_stack::MplsLabelStackPacket;
+use trippy_packet::icmp_extension::mpls_label_stack_member::MplsLabelStackMemberPacket;
 
 /// The supported ICMP extension version number.
 const ICMP_EXTENSION_VERSION: u8 = 2;
diff --git a/crates/trippy-core/src/net/ipv4.rs b/crates/trippy-core/src/net/ipv4.rs
index 511a4a8..d5d0c02 100644
--- a/crates/trippy-core/src/net/ipv4.rs
+++ b/crates/trippy-core/src/net/ipv4.rs
@@ -4,16 +4,6 @@ use crate::net::channel::MAX_PACKET_SIZE;
 use crate::net::common::process_result;
 use crate::net::platform;
 use crate::net::socket::{Socket, SocketError};
-use crate::packet::checksum::{icmp_ipv4_checksum, udp_ipv4_checksum};
-use crate::packet::icmpv4::destination_unreachable::DestinationUnreachablePacket;
-use crate::packet::icmpv4::echo_reply::EchoReplyPacket;
-use crate::packet::icmpv4::echo_request::EchoRequestPacket;
-use crate::packet::icmpv4::time_exceeded::TimeExceededPacket;
-use crate::packet::icmpv4::{IcmpCode, IcmpPacket, IcmpTimeExceededCode, IcmpType};
-use crate::packet::ipv4::Ipv4Packet;
-use crate::packet::tcp::TcpPacket;
-use crate::packet::udp::UdpPacket;
-use crate::packet::IpProtocol;
 use crate::probe::{
     Extensions, IcmpPacketCode, Probe, ProbeResponse, ProbeResponseData, ProbeResponseSeq,
     ProbeResponseSeqIcmp, ProbeResponseSeqTcp, ProbeResponseSeqUdp,
@@ -24,6 +14,16 @@ use std::io::ErrorKind;
 use std::net::{IpAddr, Ipv4Addr, SocketAddr};
 use std::time::SystemTime;
 use tracing::instrument;
+use trippy_packet::checksum::{icmp_ipv4_checksum, udp_ipv4_checksum};
+use trippy_packet::icmpv4::destination_unreachable::DestinationUnreachablePacket;
+use trippy_packet::icmpv4::echo_reply::EchoReplyPacket;
+use trippy_packet::icmpv4::echo_request::EchoRequestPacket;
+use trippy_packet::icmpv4::time_exceeded::TimeExceededPacket;
+use trippy_packet::icmpv4::{IcmpCode, IcmpPacket, IcmpTimeExceededCode, IcmpType};
+use trippy_packet::ipv4::Ipv4Packet;
+use trippy_packet::tcp::TcpPacket;
+use trippy_packet::udp::UdpPacket;
+use trippy_packet::IpProtocol;
 
 /// The maximum size of UDP packet we allow.
 const MAX_UDP_PACKET_BUF: usize = MAX_PACKET_SIZE - Ipv4Packet::minimum_packet_size();
diff --git a/crates/trippy-core/src/net/ipv6.rs b/crates/trippy-core/src/net/ipv6.rs
index 92a788a..b2c454d 100644
--- a/crates/trippy-core/src/net/ipv6.rs
+++ b/crates/trippy-core/src/net/ipv6.rs
@@ -3,16 +3,6 @@ use crate::error::{TraceResult, TracerError};
 use crate::net::channel::MAX_PACKET_SIZE;
 use crate::net::common::process_result;
 use crate::net::socket::{Socket, SocketError};
-use crate::packet::checksum::{icmp_ipv6_checksum, udp_ipv6_checksum};
-use crate::packet::icmpv6::destination_unreachable::DestinationUnreachablePacket;
-use crate::packet::icmpv6::echo_reply::EchoReplyPacket;
-use crate::packet::icmpv6::echo_request::EchoRequestPacket;
-use crate::packet::icmpv6::time_exceeded::TimeExceededPacket;
-use crate::packet::icmpv6::{IcmpCode, IcmpPacket, IcmpTimeExceededCode, IcmpType};
-use crate::packet::ipv6::Ipv6Packet;
-use crate::packet::tcp::TcpPacket;
-use crate::packet::udp::UdpPacket;
-use crate::packet::IpProtocol;
 use crate::probe::{
     Extensions, IcmpPacketCode, Probe, ProbeResponse, ProbeResponseData, ProbeResponseSeq,
     ProbeResponseSeqIcmp, ProbeResponseSeqTcp, ProbeResponseSeqUdp,
@@ -23,6 +13,16 @@ use std::io::ErrorKind;
 use std::net::{IpAddr, Ipv6Addr, SocketAddr};
 use std::time::SystemTime;
 use tracing::instrument;
+use trippy_packet::checksum::{icmp_ipv6_checksum, udp_ipv6_checksum};
+use trippy_packet::icmpv6::destination_unreachable::DestinationUnreachablePacket;
+use trippy_packet::icmpv6::echo_reply::EchoReplyPacket;
+use trippy_packet::icmpv6::echo_request::EchoRequestPacket;
+use trippy_packet::icmpv6::time_exceeded::TimeExceededPacket;
+use trippy_packet::icmpv6::{IcmpCode, IcmpPacket, IcmpTimeExceededCode, IcmpType};
+use trippy_packet::ipv6::Ipv6Packet;
+use trippy_packet::tcp::TcpPacket;
+use trippy_packet::udp::UdpPacket;
+use trippy_packet::IpProtocol;
 
 /// The maximum size of UDP packet we allow.
 const MAX_UDP_PACKET_BUF: usize = MAX_PACKET_SIZE - Ipv6Packet::minimum_packet_size();
diff --git a/crates/trippy-core/src/net/platform/unix.rs b/crates/trippy-core/src/net/platform/unix.rs
index 5352648..db832d5 100644
--- a/crates/trippy-core/src/net/platform/unix.rs
+++ b/crates/trippy-core/src/net/platform/unix.rs
@@ -72,20 +72,19 @@ mod address {
     #[cfg(not(target_os = "linux"))]
     #[instrument(ret)]
     fn test_send_local_ip4_packet(src_addr: Ipv4Addr, total_length: u16) -> TraceResult<()> {
-        use crate::packet;
         use socket2::Protocol;
-        let mut icmp_buf = [0_u8; packet::icmpv4::IcmpPacket::minimum_packet_size()];
-        let mut icmp = packet::icmpv4::echo_request::EchoRequestPacket::new(&mut icmp_buf)?;
-        icmp.set_icmp_type(packet::icmpv4::IcmpType::EchoRequest);
-        icmp.set_icmp_code(packet::icmpv4::IcmpCode(0));
+        let mut icmp_buf = [0_u8; trippy_packet::icmpv4::IcmpPacket::minimum_packet_size()];
+        let mut icmp = trippy_packet::icmpv4::echo_request::EchoRequestPacket::new(&mut icmp_buf)?;
+        icmp.set_icmp_type(trippy_packet::icmpv4::IcmpType::EchoRequest);
+        icmp.set_icmp_code(trippy_packet::icmpv4::IcmpCode(0));
         icmp.set_identifier(0);
         icmp.set_sequence(0);
-        icmp.set_checksum(packet::checksum::icmp_ipv4_checksum(icmp.packet()));
+        icmp.set_checksum(trippy_packet::checksum::icmp_ipv4_checksum(icmp.packet()));
         let mut ipv4_buf = [0_u8; TEST_PACKET_LENGTH as usize];
-        let mut ipv4 = packet::ipv4::Ipv4Packet::new(&mut ipv4_buf)?;
+        let mut ipv4 = trippy_packet::ipv4::Ipv4Packet::new(&mut ipv4_buf)?;
         ipv4.set_version(4);
         ipv4.set_header_length(5);
-        ipv4.set_protocol(packet::IpProtocol::Icmp);
+        ipv4.set_protocol(trippy_packet::IpProtocol::Icmp);
         ipv4.set_ttl(255);
         ipv4.set_source(src_addr);
         ipv4.set_destination(Ipv4Addr::LOCALHOST);
diff --git a/crates/trippy-core/src/packet.rs b/crates/trippy-core/src/packet.rs
deleted file mode 100644
index f3403bf..0000000
--- a/crates/trippy-core/src/packet.rs
+++ /dev/null
@@ -1,73 +0,0 @@
-mod buffer;
-
-/// Packet errors.
-pub mod error;
-
-/// Functions for calculating network checksums.
-pub mod checksum;
-
-/// `ICMPv4` packets.
-pub mod icmpv4;
-
-/// `ICMPv6` packets.
-pub mod icmpv6;
-
-/// `ICMP` extensions.
-pub mod icmp_extension;
-
-/// `IPv4` packets.
-pub mod ipv4;
-
-/// `IPv6` packets.
-pub mod ipv6;
-
-/// `UDP` packets.
-pub mod udp;
-
-/// `TCP` packets.
-pub mod tcp;
-
-fn fmt_payload(bytes: &[u8]) -> String {
-    use itertools::Itertools as _;
-    format!("{:02x}", bytes.iter().format(" "))
-}
-
-/// The IP packet next layer protocol.
-#[derive(Debug, Clone, Copy, Eq, PartialEq)]
-pub enum IpProtocol {
-    Icmp,
-    IcmpV6,
-    Udp,
-    Tcp,
-    Other(u8),
-}
-
-impl IpProtocol {
-    #[must_use]
-    pub fn id(self) -> u8 {
-        match self {
-            Self::Icmp => 1,
-            Self::IcmpV6 => 58,
-            Self::Udp => 17,
-            Self::Tcp => 6,
-            Self::Other(id) => id,
-        }
-    }
-
-    #[must_use]
-    pub fn new(value: u8) -> Self {
-        Self::Other(value)
-    }
-}
-
-impl From<u8> for IpProtocol {
-    fn from(id: u8) -> Self {
-        match id {
-            1 => Self::Icmp,
-            58 => Self::IcmpV6,
-            17 => Self::Udp,
-            6 => Self::Tcp,
-            p => Self::Other(p),
-        }
-    }
-}
diff --git a/crates/trippy-packet/Cargo.toml b/crates/trippy-packet/Cargo.toml
new file mode 100644
index 0000000..b0e8980
--- /dev/null
+++ b/crates/trippy-packet/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "trippy-packet"
+description = "Network packets for Trippy"
+version.workspace = true
+authors.workspace = true
+documentation.workspace = true
+homepage.workspace = true
+repository.workspace = true
+readme.workspace = true
+license.workspace = true
+edition.workspace = true
+rust-version.workspace = true
+keywords = ["traceroute", "ping", "icmp"]
+categories = ["network-programming"]
+
+[dependencies]
+itertools.workspace = true
+thiserror.workspace = true
+
+[dev-dependencies]
+hex-literal.workspace = true
+anyhow.workspace = true
\ No newline at end of file
diff --git a/crates/trippy-core/src/packet/buffer.rs b/crates/trippy-packet/src/buffer.rs
similarity index 100%
rename from crates/trippy-core/src/packet/buffer.rs
rename to crates/trippy-packet/src/buffer.rs
diff --git a/crates/trippy-core/src/packet/checksum.rs b/crates/trippy-packet/src/checksum.rs
similarity index 99%
rename from crates/trippy-core/src/packet/checksum.rs
rename to crates/trippy-packet/src/checksum.rs
index be99fdc..ae0fcf2 100644
--- a/crates/trippy-core/src/packet/checksum.rs
+++ b/crates/trippy-packet/src/checksum.rs
@@ -4,7 +4,7 @@
 //!
 //! [`libpnet`]: https://github.com/libpnet/libpnet
 
-use crate::packet::IpProtocol;
+use crate::IpProtocol;
 use std::net::{Ipv4Addr, Ipv6Addr};
 
 /// Calculate the checksum for an `Ipv4` header.
diff --git a/crates/trippy-core/src/packet/error.rs b/crates/trippy-packet/src/error.rs
similarity index 100%
rename from crates/trippy-core/src/packet/error.rs
rename to crates/trippy-packet/src/error.rs
diff --git a/crates/trippy-core/src/packet/icmp_extension.rs b/crates/trippy-packet/src/icmp_extension.rs
similarity index 95%
rename from crates/trippy-core/src/packet/icmp_extension.rs
rename to crates/trippy-packet/src/icmp_extension.rs
index 69f39a1..1b8384a 100644
--- a/crates/trippy-core/src/packet/icmp_extension.rs
+++ b/crates/trippy-packet/src/icmp_extension.rs
@@ -1,7 +1,7 @@
 pub mod extension_structure {
-    use crate::packet::buffer::Buffer;
-    use crate::packet::error::{PacketError, PacketResult};
-    use crate::packet::icmp_extension::extension_object::ExtensionObjectPacket;
+    use crate::buffer::Buffer;
+    use crate::error::{PacketError, PacketResult};
+    use crate::icmp_extension::extension_object::ExtensionObjectPacket;
 
     /// Represents an ICMP `ExtensionsPacket` pseudo object.
     ///
@@ -104,8 +104,8 @@ pub mod extension_structure {
     #[cfg(test)]
     mod tests {
         use super::*;
-        use crate::packet::icmp_extension::extension_header::ExtensionHeaderPacket;
-        use crate::packet::icmp_extension::extension_object::{
+        use crate::icmp_extension::extension_header::ExtensionHeaderPacket;
+        use crate::icmp_extension::extension_object::{
             ClassNum, ClassSubType, ExtensionObjectPacket,
         };
 
@@ -152,8 +152,8 @@ pub mod extension_structure {
 }
 
 pub mod extension_header {
-    use crate::packet::buffer::Buffer;
-    use crate::packet::error::{PacketError, PacketResult};
+    use crate::buffer::Buffer;
+    use crate::error::{PacketError, PacketResult};
     use std::fmt::{Debug, Formatter};
 
     const VERSION_OFFSET: usize = 0;
@@ -286,9 +286,9 @@ pub mod extension_header {
 }
 
 pub mod extension_object {
-    use crate::packet::buffer::Buffer;
-    use crate::packet::error::{PacketError, PacketResult};
-    use crate::packet::fmt_payload;
+    use crate::buffer::Buffer;
+    use crate::error::{PacketError, PacketResult};
+    use crate::fmt_payload;
     use std::fmt::{Debug, Formatter};
 
     /// The ICMP Extension Object Class Num.
@@ -518,9 +518,9 @@ pub mod extension_object {
 }
 
 pub mod mpls_label_stack {
-    use crate::packet::buffer::Buffer;
-    use crate::packet::error::{PacketError, PacketResult};
-    use crate::packet::icmp_extension::mpls_label_stack_member::MplsLabelStackMemberPacket;
+    use crate::buffer::Buffer;
+    use crate::error::{PacketError, PacketResult};
+    use crate::icmp_extension::mpls_label_stack_member::MplsLabelStackMemberPacket;
 
     /// Represents an ICMP `MplsLabelStackPacket`.
     ///
@@ -633,8 +633,8 @@ pub mod mpls_label_stack {
 }
 
 pub mod mpls_label_stack_member {
-    use crate::packet::buffer::Buffer;
-    use crate::packet::error::{PacketError, PacketResult};
+    use crate::buffer::Buffer;
+    use crate::error::{PacketError, PacketResult};
     use std::fmt::{Debug, Formatter};
 
     const LABEL_OFFSET: usize = 0;
@@ -842,7 +842,7 @@ pub mod mpls_label_stack_member {
 }
 
 pub mod extension_splitter {
-    use crate::packet::icmp_extension::extension_header::ExtensionHeaderPacket;
+    use crate::icmp_extension::extension_header::ExtensionHeaderPacket;
     const MIN_HEADER: usize = ExtensionHeaderPacket::minimum_packet_size();
 
     /// From rfc4884 (section 3) entitled "Summary of Changes to ICMP":
@@ -898,13 +898,13 @@ pub mod extension_splitter {
     #[cfg(test)]
     mod tests {
         use super::*;
-        use crate::packet::icmp_extension::extension_header::ExtensionHeaderPacket;
-        use crate::packet::icmp_extension::extension_object::{
+        use crate::icmp_extension::extension_header::ExtensionHeaderPacket;
+        use crate::icmp_extension::extension_object::{
             ClassNum, ClassSubType, ExtensionObjectPacket,
         };
-        use crate::packet::icmp_extension::extension_structure::ExtensionsPacket;
-        use crate::packet::icmp_extension::mpls_label_stack::MplsLabelStackPacket;
-        use crate::packet::icmp_extension::mpls_label_stack_member::MplsLabelStackMemberPacket;
+        use crate::icmp_extension::extension_structure::ExtensionsPacket;
+        use crate::icmp_extension::mpls_label_stack::MplsLabelStackPacket;
+        use crate::icmp_extension::mpls_label_stack_member::MplsLabelStackMemberPacket;
 
         #[test]
         fn test_split_empty_payload() {
@@ -959,10 +959,10 @@ pub mod extension_splitter {
 
         mod ipv4 {
             use super::*;
-            use crate::packet::icmpv4::echo_request::EchoRequestPacket;
-            use crate::packet::icmpv4::time_exceeded::TimeExceededPacket;
-            use crate::packet::icmpv4::{IcmpCode, IcmpType};
-            use crate::packet::ipv4::Ipv4Packet;
+            use crate::icmpv4::echo_request::EchoRequestPacket;
+            use crate::icmpv4::time_exceeded::TimeExceededPacket;
+            use crate::icmpv4::{IcmpCode, IcmpType};
+            use crate::ipv4::Ipv4Packet;
             use std::net::Ipv4Addr;
 
             // This ICMP TimeExceeded packet which contains single `MPLS` extension
@@ -1136,17 +1136,17 @@ pub mod extension_splitter {
         }
 
         mod ipv6 {
-            use crate::packet::icmp_extension::extension_header::ExtensionHeaderPacket;
-            use crate::packet::icmp_extension::extension_object::{
+            use crate::icmp_extension::extension_header::ExtensionHeaderPacket;
+            use crate::icmp_extension::extension_object::{
                 ClassNum, ClassSubType, ExtensionObjectPacket,
             };
-            use crate::packet::icmp_extension::extension_structure::ExtensionsPacket;
-            use crate::packet::icmp_extension::mpls_label_stack::MplsLabelStackPacket;
-            use crate::packet::icmp_extension::mpls_label_stack_member::MplsLabelStackMemberPacket;
-            use crate::packet::icmpv6::echo_request::EchoRequestPacket;
-            use crate::packet::icmpv6::time_exceeded::TimeExceededPacket;
-            use crate::packet::icmpv6::{IcmpCode, IcmpType};
-            use crate::packet::ipv6::Ipv6Packet;
+            use crate::icmp_extension::extension_structure::ExtensionsPacket;
+            use crate::icmp_extension::mpls_label_stack::MplsLabelStackPacket;
+            use crate::icmp_extension::mpls_label_stack_member::MplsLabelStackMemberPacket;
+            use crate::icmpv6::echo_request::EchoRequestPacket;
+            use crate::icmpv6::time_exceeded::TimeExceededPacket;
+            use crate::icmpv6::{IcmpCode, IcmpType};
+            use crate::ipv6::Ipv6Packet;
 
             // Real IPv6 example with an rfc4884 length of 10 (10 * 8 = 80
             // octets).
diff --git a/crates/trippy-core/src/packet/icmpv4.rs b/crates/trippy-packet/src/icmpv4.rs
similarity index 97%
rename from crates/trippy-core/src/packet/icmpv4.rs
rename to crates/trippy-packet/src/icmpv4.rs
index 4d766a5..489d546 100644
--- a/crates/trippy-core/src/packet/icmpv4.rs
+++ b/crates/trippy-packet/src/icmpv4.rs
@@ -1,5 +1,5 @@
-use crate::packet::buffer::Buffer;
-use crate::packet::error::{PacketError, PacketResult};
+use crate::buffer::Buffer;
+use crate::error::{PacketError, PacketResult};
 use std::fmt::{Debug, Formatter};
 
 /// The type of ICMP packet.
@@ -236,10 +236,10 @@ mod tests {
 }
 
 pub mod echo_request {
-    use crate::packet::buffer::Buffer;
-    use crate::packet::error::{PacketError, PacketResult};
-    use crate::packet::fmt_payload;
-    use crate::packet::icmpv4::{IcmpCode, IcmpType};
+    use crate::buffer::Buffer;
+    use crate::error::{PacketError, PacketResult};
+    use crate::fmt_payload;
+    use crate::icmpv4::{IcmpCode, IcmpType};
     use std::fmt::{Debug, Formatter};
 
     const TYPE_OFFSET: usize = 0;
@@ -496,10 +496,10 @@ pub mod echo_request {
 }
 
 pub mod echo_reply {
-    use crate::packet::buffer::Buffer;
-    use crate::packet::error::{PacketError, PacketResult};
-    use crate::packet::fmt_payload;
-    use crate::packet::icmpv4::{IcmpCode, IcmpType};
+    use crate::buffer::Buffer;
+    use crate::error::{PacketError, PacketResult};
+    use crate::fmt_payload;
+    use crate::icmpv4::{IcmpCode, IcmpType};
     use std::fmt::{Debug, Formatter};
 
     const TYPE_OFFSET: usize = 0;
@@ -756,11 +756,11 @@ pub mod echo_reply {
 }
 
 pub mod time_exceeded {
-    use crate::packet::buffer::Buffer;
-    use crate::packet::error::{PacketError, PacketResult};
-    use crate::packet::fmt_payload;
-    use crate::packet::icmp_extension::extension_splitter::split;
-    use crate::packet::icmpv4::{IcmpCode, IcmpType};
+    use crate::buffer::Buffer;
+    use crate::error::{PacketError, PacketResult};
+    use crate::fmt_payload;
+    use crate::icmp_extension::extension_splitter::split;
+    use crate::icmpv4::{IcmpCode, IcmpType};
     use std::fmt::{Debug, Formatter};
 
     const TYPE_OFFSET: usize = 0;
@@ -1011,11 +1011,11 @@ pub mod time_exceeded {
 }
 
 pub mod destination_unreachable {
-    use crate::packet::buffer::Buffer;
-    use crate::packet::error::{PacketError, PacketResult};
-    use crate::packet::fmt_payload;
-    use crate::packet::icmp_extension::extension_splitter::split;
-    use crate::packet::icmpv4::{IcmpCode, IcmpType};
+    use crate::buffer::Buffer;
+    use crate::error::{PacketError, PacketResult};
+    use crate::fmt_payload;
+    use crate::icmp_extension::extension_splitter::split;
+    use crate::icmpv4::{IcmpCode, IcmpType};
     use std::fmt::{Debug, Formatter};
 
     const TYPE_OFFSET: usize = 0;
diff --git a/crates/trippy-core/src/packet/icmpv6.rs b/crates/trippy-packet/src/icmpv6.rs
similarity index 97%
rename from crates/trippy-core/src/packet/icmpv6.rs
rename to crates/trippy-packet/src/icmpv6.rs
index 70feb23..a556345 100644
--- a/crates/trippy-core/src/packet/icmpv6.rs
+++ b/crates/trippy-packet/src/icmpv6.rs
@@ -1,5 +1,5 @@
-use crate::packet::buffer::Buffer;
-use crate::packet::error::{PacketError, PacketResult};
+use crate::buffer::Buffer;
+use crate::error::{PacketError, PacketResult};
 use std::fmt::{Debug, Formatter};
 
 /// The type of `ICMPv6` packet.
@@ -236,10 +236,10 @@ mod tests {
 }
 
 pub mod echo_request {
-    use crate::packet::buffer::Buffer;
-    use crate::packet::error::{PacketError, PacketResult};
-    use crate::packet::fmt_payload;
-    use crate::packet::icmpv6::{IcmpCode, IcmpType};
+    use crate::buffer::Buffer;
+    use crate::error::{PacketError, PacketResult};
+    use crate::fmt_payload;
+    use crate::icmpv6::{IcmpCode, IcmpType};
     use std::fmt::{Debug, Formatter};
 
     const TYPE_OFFSET: usize = 0;
@@ -496,10 +496,10 @@ pub mod echo_request {
 }
 
 pub mod echo_reply {
-    use crate::packet::buffer::Buffer;
-    use crate::packet::error::{PacketError, PacketResult};
-    use crate::packet::fmt_payload;
-    use crate::packet::icmpv6::{IcmpCode, IcmpType};
+    use crate::buffer::Buffer;
+    use crate::error::{PacketError, PacketResult};
+    use crate::fmt_payload;
+    use crate::icmpv6::{IcmpCode, IcmpType};
     use std::fmt::{Debug, Formatter};
 
     const TYPE_OFFSET: usize = 0;
@@ -756,11 +756,11 @@ pub mod echo_reply {
 }
 
 pub mod time_exceeded {
-    use crate::packet::buffer::Buffer;
-    use crate::packet::error::{PacketError, PacketResult};
-    use crate::packet::fmt_payload;
-    use crate::packet::icmp_extension::extension_splitter::split;
-    use crate::packet::icmpv6::{IcmpCode, IcmpType};
+    use crate::buffer::Buffer;
+    use crate::error::{PacketError, PacketResult};
+    use crate::fmt_payload;
+    use crate::icmp_extension::extension_splitter::split;
+    use crate::icmpv6::{IcmpCode, IcmpType};
     use std::fmt::{Debug, Formatter};
 
     const TYPE_OFFSET: usize = 0;
@@ -1008,11 +1008,11 @@ pub mod time_exceeded {
 }
 
 pub mod destination_unreachable {
-    use crate::packet::buffer::Buffer;
-    use crate::packet::error::{PacketError, PacketResult};
-    use crate::packet::fmt_payload;
-    use crate::packet::icmp_extension::extension_splitter::split;
-    use crate::packet::icmpv6::{IcmpCode, IcmpType};
+    use crate::buffer::Buffer;
+    use crate::error::{PacketError, PacketResult};
+    use crate::fmt_payload;
+    use crate::icmp_extension::extension_splitter::split;
+    use crate::icmpv6::{IcmpCode, IcmpType};
     use std::fmt::{Debug, Formatter};
 
     const TYPE_OFFSET: usize = 0;
diff --git a/crates/trippy-core/src/packet/ipv4.rs b/crates/trippy-packet/src/ipv4.rs
similarity index 99%
rename from crates/trippy-core/src/packet/ipv4.rs
rename to crates/trippy-packet/src/ipv4.rs
index 223f902..de63c42 100644
--- a/crates/trippy-core/src/packet/ipv4.rs
+++ b/crates/trippy-packet/src/ipv4.rs
@@ -1,6 +1,6 @@
-use crate::packet::buffer::Buffer;
-use crate::packet::error::{PacketError, PacketResult};
-use crate::packet::{fmt_payload, IpProtocol};
+use crate::buffer::Buffer;
+use crate::error::{PacketError, PacketResult};
+use crate::{fmt_payload, IpProtocol};
 use std::fmt::{Debug, Formatter};
 use std::net::Ipv4Addr;
 
diff --git a/crates/trippy-core/src/packet/ipv6.rs b/crates/trippy-packet/src/ipv6.rs
similarity index 99%
rename from crates/trippy-core/src/packet/ipv6.rs
rename to crates/trippy-packet/src/ipv6.rs
index 337b981..7595fd2 100644
--- a/crates/trippy-core/src/packet/ipv6.rs
+++ b/crates/trippy-packet/src/ipv6.rs
@@ -1,6 +1,6 @@
-use crate::packet::buffer::Buffer;
-use crate::packet::error::{PacketError, PacketResult};
-use crate::packet::{fmt_payload, IpProtocol};
+use crate::buffer::Buffer;
+use crate::error::{PacketError, PacketResult};
+use crate::{fmt_payload, IpProtocol};
 use std::fmt::{Debug, Formatter};
 use std::net::Ipv6Addr;
 
diff --git a/crates/trippy-packet/src/lib.rs b/crates/trippy-packet/src/lib.rs
new file mode 100644
index 0000000..9604540
--- /dev/null
+++ b/crates/trippy-packet/src/lib.rs
@@ -0,0 +1,140 @@
+//! Packet wire format parsing and building.
+//!
+//! The following packet are supported:
+//! - `ICMPv4`
+//! - `ICMPv6`
+//! - `IPv4`
+//! - `IPv6`
+//! - `UDP`
+//! - `TCP`
+//! - `ICMP` extensions
+//!
+//! # Endianness
+//!
+//! The internal representation is held in network byte order (big-endian) and
+//! all accessor methods take and return data in host byte order, converting as
+//! necessary for the given architecture.
+//!
+//! # Example
+//!
+//! The following example parses an `UDP` packet and asserts its fields:
+//!
+//! ```rust
+//! # fn main() -> anyhow::Result<()> {
+//! use trippy_packet::udp::UdpPacket;
+//!
+//! let buf = hex_literal::hex!("68 bf 81 b6 00 40 ac be");
+//! let packet = UdpPacket::new_view(&buf)?;
+//! assert_eq!(26815, packet.get_source());
+//! assert_eq!(33206, packet.get_destination());
+//! assert_eq!(64, packet.get_length());
+//! assert_eq!(44222, packet.get_checksum());
+//! assert!(packet.payload().is_empty());
+//! # Ok(())
+//! # }
+//! ```
+//!
+//! The following example builds an `ICMPv4` echo request packet:
+//!
+//! ```rust
+//! # fn main() -> anyhow::Result<()> {
+//! use trippy_packet::icmpv4::echo_request::EchoRequestPacket;
+//! use trippy_packet::icmpv4::{IcmpPacket, IcmpType, IcmpCode};
+//! use trippy_packet::checksum::icmp_ipv4_checksum;
+//!
+//! let mut buf = [0; IcmpPacket::minimum_packet_size()];
+//! let mut icmp = EchoRequestPacket::new(&mut buf)?;
+//! icmp.set_icmp_type(IcmpType::EchoRequest);
+//! icmp.set_icmp_code(IcmpCode(0));
+//! icmp.set_identifier(1234);
+//! icmp.set_sequence(10);
+//! icmp.set_checksum(icmp_ipv4_checksum(icmp.packet()));
+//! assert_eq!(icmp.packet(), &hex_literal::hex!("08 00 f3 23 04 d2 00 0a"));
+//! # Ok(())
+//! # }
+//! ```
+#![warn(clippy::all, clippy::pedantic, clippy::nursery, rust_2018_idioms)]
+#![allow(
+    clippy::missing_const_for_fn,
+    clippy::missing_errors_doc,
+    clippy::module_name_repetitions,
+    clippy::cast_possible_truncation,
+    clippy::use_self
+)]
+#![deny(unsafe_code)]
+
+mod buffer;
+
+/// Packet errors.
+pub mod error;
+
+/// Functions for calculating network checksums.
+pub mod checksum;
+
+/// `ICMPv4` packets.
+pub mod icmpv4;
+
+/// `ICMPv6` packets.
+pub mod icmpv6;
+
+/// `ICMP` extensions.
+pub mod icmp_extension;
+
+/// `IPv4` packets.
+pub mod ipv4;
+
+/// `IPv6` packets.
+pub mod ipv6;
+
+/// `UDP` packets.
+pub mod udp;
+
+/// `TCP` packets.
+pub mod tcp;
+
+/// The IP packet next layer protocol.
+#[derive(Debug, Clone, Copy, Eq, PartialEq)]
+pub enum IpProtocol {
+    Icmp,
+    IcmpV6,
+    Udp,
+    Tcp,
+    Other(u8),
+}
+
+impl IpProtocol {
+    #[must_use]
+    pub fn id(self) -> u8 {
+        match self {
+            Self::Icmp => 1,
+            Self::IcmpV6 => 58,
+            Self::Udp => 17,
+            Self::Tcp => 6,
+            Self::Other(id) => id,
+        }
+    }
+
+    #[must_use]
+    pub fn new(value: u8) -> Self {
+        Self::Other(value)
+    }
+}
+
+impl From<u8> for IpProtocol {
+    fn from(id: u8) -> Self {
+        match id {
+            1 => Self::Icmp,
+            58 => Self::IcmpV6,
+            17 => Self::Udp,
+            6 => Self::Tcp,
+            p => Self::Other(p),
+        }
+    }
+}
+
+/// Format a payload as a hexadecimal string.
+#[must_use]
+pub fn fmt_payload(bytes: &[u8]) -> String {
+    use itertools::Itertools as _;
+    format!("{:02x}", bytes.iter().format(" "))
+}
diff --git a/crates/trippy-core/src/packet/tcp.rs b/crates/trippy-packet/src/tcp.rs
similarity index 99%
rename from crates/trippy-core/src/packet/tcp.rs
rename to crates/trippy-packet/src/tcp.rs
index 48d723b..9533779 100644
--- a/crates/trippy-core/src/packet/tcp.rs
+++ b/crates/trippy-packet/src/tcp.rs
@@ -1,6 +1,6 @@
-use crate::packet::buffer::Buffer;
-use crate::packet::error::{PacketError, PacketResult};
-use crate::packet::fmt_payload;
+use crate::buffer::Buffer;
+use crate::error::{PacketError, PacketResult};
+use crate::fmt_payload;
 use std::fmt::{Debug, Formatter};
 
 const SOURCE_PORT_OFFSET: usize = 0;
diff --git a/crates/trippy-core/src/packet/udp.rs b/crates/trippy-packet/src/udp.rs
similarity index 98%
rename from crates/trippy-core/src/packet/udp.rs
rename to crates/trippy-packet/src/udp.rs
index 187ad9f..b7de871 100644
--- a/crates/trippy-core/src/packet/udp.rs
+++ b/crates/trippy-packet/src/udp.rs
@@ -1,6 +1,6 @@
-use crate::packet::buffer::Buffer;
-use crate::packet::error::{PacketError, PacketResult};
-use crate::packet::fmt_payload;
+use crate::buffer::Buffer;
+use crate::error::{PacketError, PacketResult};
+use crate::fmt_payload;
 use std::fmt::{Debug, Formatter};
 
 const SOURCE_PORT_OFFSET: usize = 0;


Test patch:
diff --git a/crates/trippy-core/tests/sim/network.rs b/crates/trippy-core/tests/sim/network.rs
index 2624670..0fd584d 100644
--- a/crates/trippy-core/tests/sim/network.rs
+++ b/crates/trippy-core/tests/sim/network.rs
@@ -8,16 +8,16 @@ use tokio::sync::Mutex;
 use tokio::task::JoinHandle;
 use tokio_util::sync::CancellationToken;
 use tracing::{debug, info};
-use trippy_core::packet::checksum::{icmp_ipv4_checksum, ipv4_header_checksum, tcp_ipv4_checksum};
-use trippy_core::packet::icmpv4::destination_unreachable::DestinationUnreachablePacket;
-use trippy_core::packet::icmpv4::echo_reply::EchoReplyPacket;
-use trippy_core::packet::icmpv4::echo_request::EchoRequestPacket;
-use trippy_core::packet::icmpv4::time_exceeded::TimeExceededPacket;
-use trippy_core::packet::icmpv4::{IcmpCode, IcmpType};
-use trippy_core::packet::ipv4::Ipv4Packet;
-use trippy_core::packet::tcp::TcpPacket;
-use trippy_core::packet::udp::UdpPacket;
-use trippy_core::packet::IpProtocol;
+use trippy_packet::checksum::{icmp_ipv4_checksum, ipv4_header_checksum, tcp_ipv4_checksum};
+use trippy_packet::icmpv4::destination_unreachable::DestinationUnreachablePacket;
+use trippy_packet::icmpv4::echo_reply::EchoReplyPacket;
+use trippy_packet::icmpv4::echo_request::EchoRequestPacket;
+use trippy_packet::icmpv4::time_exceeded::TimeExceededPacket;
+use trippy_packet::icmpv4::{IcmpCode, IcmpType};
+use trippy_packet::ipv4::Ipv4Packet;
+use trippy_packet::tcp::TcpPacket;
+use trippy_packet::udp::UdpPacket;
+use trippy_packet::IpProtocol;
 
 const READ_TIMEOUT: Duration = Duration::from_millis(10);