Repository: fujiapple852/trippy (language: rust)

Issue:
Separate lib and bin dependencies
**Describe the feature you'd like**
Separate dependencies of trippy's public library components and it's CLI binary tool. This way, when trying to use trippy within another rust project, I can `cargo add trippy` without its version of `clap` (or any of the other TUI/CLI related dependencies) clashing with my own.

**Describe alternatives you've considered**
I'm not sure there is an alternative, besides me not using trippy as a library.

**Additional context**
I'm trying to use trippy's tracer within another rust project.


Discussion / hints:
Hi there @wgreenberg and thank you for raising this topic. 

I previously attempted to split the library and bin out as you describe (see https://github.com/fujiapple852/trippy/pull/640) but eventually abandoned the PR as it became very out of date. 

I’m still undecided on whether this separation makes sense for Trippy, which will mostly be used as a stand-alone tool rather than a library, but I’m open to being persuaded.

what is your use case for using Trippy as a library?
@fujiapple852 I'm working on software that detects machine-in-the-middle attacks via timing analysis (https://arxiv.org/abs/2308.09213) written in rust, and I'd love to be able to use trippy in-process rather than having to start something like https://github.com/heistp/irtt as a child process.

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3bd651621..682056a0f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -120,8 +120,9 @@ jobs:
       - name: Copy wintun.dll to current dir
         if: startsWith(matrix.build, 'windows')
         shell: bash
+        # The simulation tests run from the crates/trippy-core directory and so `wintun.dll` needs to be copied there
         run: |
-          cp "tests/resources/wintun.dll" "."
+          cp "crates/trippy-core/tests/resources/wintun.dll" "./crates/trippy-core/"
       - name: Allow ICMPv4 and ICMPv6 in Windows defender firewall
         if: startsWith(matrix.build, 'windows')
         shell: pwsh
@@ -194,6 +195,12 @@ jobs:
       - uses: actions/checkout@v4
       - uses: Swatinem/rust-cache@v2
       - name: install cargo-msrv
-        run: cargo install cargo-msrv
-      - name: check msrv
-        run: cargo msrv --output-format json verify -- cargo check
\ No newline at end of file
+        run: cargo install --git https://github.com/foresterre/cargo-msrv.git cargo-msrv
+      - name: check msrv for trippy
+        run: cargo msrv verify --output-format json --manifest-path crates/trippy/Cargo.toml -- cargo check
+      - name: check msrv for trippy-core
+        run: cargo msrv verify --output-format json --manifest-path crates/trippy-core/Cargo.toml -- cargo check
+      - name: check msrv for trippy-dns
+        run: cargo msrv verify --output-format json --manifest-path crates/trippy-dns/Cargo.toml -- cargo check
+      - name: check msrv for trippy-privilege
+        run: cargo msrv verify --output-format json --manifest-path crates/trippy-privilege/Cargo.toml -- cargo check
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index af448f011..cc7921cf6 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -151,7 +151,7 @@ jobs:
         if: startsWith(matrix.build, 'x86_64-linux-gnu')
         run: |
           cargo install cargo-generate-rpm
-          cargo generate-rpm --target ${{ matrix.target }} -o target/${{ matrix.target }}/generate-rpm/trippy-${{ needs.create-release.outputs.trip_version }}-x86_64.rpm
+          cargo generate-rpm -p crates/trippy --target ${{ matrix.target }} -o target/${{ matrix.target }}/generate-rpm/trippy-${{ needs.create-release.outputs.trip_version }}-x86_64.rpm
 
       - name: Upload RPM package
         if: startsWith(matrix.build, 'x86_64-linux-gnu')
diff --git a/Cargo.lock b/Cargo.lock
index 3e396d618..604746c19 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -110,9 +110,9 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
-version = "1.0.83"
+version = "1.0.85"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3"
+checksum = "27a4bd113ab6da4cd0f521068a6e2ee1065eab54107266a11835d02c8ec86a37"
 
 [[package]]
 name = "arrayvec"
@@ -128,7 +128,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.62",
+ "syn 2.0.64",
 ]
 
 [[package]]
@@ -292,7 +292,7 @@ dependencies = [
  "heck 0.5.0",
  "proc-macro2",
  "quote",
- "syn 2.0.62",
+ "syn 2.0.64",
 ]
 
 [[package]]
@@ -470,7 +470,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "strsim",
- "syn 2.0.62",
+ "syn 2.0.64",
 ]
 
 [[package]]
@@ -481,7 +481,7 @@ checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178"
 dependencies = [
  "darling_core",
  "quote",
- "syn 2.0.62",
+ "syn 2.0.64",
 ]
 
 [[package]]
@@ -537,9 +537,9 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1"
 
 [[package]]
 name = "either"
-version = "1.11.0"
+version = "1.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2"
+checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
 
 [[package]]
 name = "encoding_rs"
@@ -568,7 +568,7 @@ dependencies = [
  "heck 0.4.1",
  "proc-macro2",
  "quote",
- "syn 2.0.62",
+ "syn 2.0.64",
 ]
 
 [[package]]
@@ -947,9 +947,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
 [[package]]
 name = "libc"
-version = "0.2.154"
+version = "0.2.155"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
+checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
 
 [[package]]
 name = "libloading"
@@ -969,9 +969,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
 
 [[package]]
 name = "linux-raw-sys"
-version = "0.4.13"
+version = "0.4.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
+checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
 
 [[package]]
 name = "lock_api"
@@ -1051,9 +1051,9 @@ dependencies = [
 
 [[package]]
 name = "miniz_oxide"
-version = "0.7.2"
+version = "0.7.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
+checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae"
 dependencies = [
  "adler",
 ]
@@ -1094,7 +1094,7 @@ dependencies = [
  "cfg-if",
  "proc-macro2",
  "quote",
- "syn 2.0.62",
+ "syn 2.0.64",
 ]
 
 [[package]]
@@ -1453,22 +1453,22 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
 [[package]]
 name = "serde"
-version = "1.0.201"
+version = "1.0.202"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c"
+checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.201"
+version = "1.0.202"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865"
+checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.62",
+ "syn 2.0.64",
 ]
 
 [[package]]
@@ -1518,7 +1518,7 @@ dependencies = [
  "darling",
  "proc-macro2",
  "quote",
- "syn 2.0.62",
+ "syn 2.0.64",
 ]
 
 [[package]]
@@ -1605,7 +1605,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2ff9eaf853dec4c8802325d8b6d3dffa86cc707fd7a1a4cdbf416e13b061787a"
 dependencies = [
  "quote",
- "syn 2.0.62",
+ "syn 2.0.64",
 ]
 
 [[package]]
@@ -1639,7 +1639,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "rustversion",
- "syn 2.0.62",
+ "syn 2.0.64",
 ]
 
 [[package]]
@@ -1655,9 +1655,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.62"
+version = "2.0.64"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f660c3bfcefb88c538776b6685a0c472e3128b51e74d48793dc2a488196e8eb"
+checksum = "7ad3dee41f36859875573074334c200d1add8e4a87bb37113ebd31d926b7b11f"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1698,7 +1698,7 @@ dependencies = [
  "cfg-if",
  "proc-macro2",
  "quote",
- "syn 2.0.62",
+ "syn 2.0.64",
 ]
 
 [[package]]
@@ -1709,28 +1709,28 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.62",
+ "syn 2.0.64",
  "test-case-core",
 ]
 
 [[package]]
 name = "thiserror"
-version = "1.0.60"
+version = "1.0.61"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"
+checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.60"
+version = "1.0.61"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"
+checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.62",
+ "syn 2.0.64",
 ]
 
 [[package]]
@@ -1816,7 +1816,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.62",
+ "syn 2.0.64",
 ]
 
 [[package]]
@@ -1885,7 +1885,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.62",
+ "syn 2.0.64",
 ]
 
 [[package]]
@@ -1956,55 +1956,94 @@ name = "trippy"
 version = "0.11.0-dev"
 dependencies = [
  "anyhow",
- "arrayvec",
- "bitflags",
- "caps",
  "chrono",
  "clap",
  "clap_complete",
  "clap_mangen",
  "comfy-table",
- "crossbeam",
  "crossterm",
  "csv",
  "derive_more",
- "dns-lookup",
  "encoding_rs_io",
  "etcetera",
- "hex-literal",
- "hickory-resolver",
  "humantime",
  "indexmap 2.2.6",
- "ipnetwork",
  "itertools",
  "maxminddb",
- "mockall",
- "nix",
  "parking_lot",
- "paste",
  "petgraph",
  "pretty_assertions",
- "rand",
  "ratatui",
  "serde",
  "serde_json",
  "serde_with",
  "serde_yaml",
- "socket2",
  "strum",
  "test-case",
  "thiserror",
- "tokio",
- "tokio-util",
  "toml",
  "tracing",
  "tracing-chrome",
  "tracing-subscriber",
+ "trippy-core",
+ "trippy-dns",
+ "trippy-privilege",
+]
+
+[[package]]
+name = "trippy-core"
+version = "0.11.0-dev"
+dependencies = [
+ "anyhow",
+ "arrayvec",
+ "bitflags",
+ "derive_more",
+ "hex-literal",
+ "ipnetwork",
+ "itertools",
+ "mockall",
+ "nix",
+ "paste",
+ "rand",
+ "serde",
+ "serde_yaml",
+ "socket2",
+ "test-case",
+ "thiserror",
+ "tokio",
+ "tokio-util",
+ "tracing",
+ "tracing-subscriber",
  "tun2",
  "widestring",
  "windows-sys 0.52.0",
 ]
 
+[[package]]
+name = "trippy-dns"
+version = "0.11.0-dev"
+dependencies = [
+ "anyhow",
+ "crossbeam",
+ "dns-lookup",
+ "hickory-resolver",
+ "itertools",
+ "parking_lot",
+ "thiserror",
+]
+
+[[package]]
+name = "trippy-privilege"
+version = "0.11.0-dev"
+dependencies = [
+ "anyhow",
+ "caps",
+ "nix",
+ "paste",
+ "thiserror",
+ "windows-sys 0.52.0",
+]
+
 [[package]]
 name = "tun2"
 version = "1.3.0"
@@ -2119,7 +2158,7 @@ dependencies = [
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.62",
+ "syn 2.0.64",
  "wasm-bindgen-shared",
 ]
 
@@ -2141,7 +2180,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.62",
+ "syn 2.0.64",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
@@ -2393,5 +2432,5 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.62",
+ "syn 2.0.64",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index e21e0a78d..f8dd38cb2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,107 +1,25 @@
-[package]
-name = "trippy"
+[workspace]
+resolver = "2"
+members = ["crates/trippy", "crates/trippy-core", "crates/trippy-privilege", "crates/trippy-dns"]
+
+[workspace.package]
 version = "0.11.0-dev"
 authors = ["FujiApple <fujiapple852@gmail.com>"]
-description = "A network diagnostic tool"
 documentation = "https://github.com/fujiapple852/trippy"
 homepage = "https://github.com/fujiapple852/trippy"
 repository = "https://github.com/fujiapple852/trippy"
-keywords = ["cli", "tui", "traceroute", "ping", "icmp"]
-categories = ["command-line-utilities", "network-programming"]
 readme = "README.md"
 license = "Apache-2.0"
 edition = "2021"
 rust-version = "1.75"
-exclude = ["assets/legacy", "assets/*/*.png"]
-
-[[bin]]
-bench = false
-path = "src/main.rs"
-name = "trip"
 
-[dependencies]
-
-# Library dependencies
-arrayvec = { version = "0.7.4", default-features = false }
-derive_more = { version = "0.99.17", default-features = false, features = [ "mul", "add", "add_assign" ] }
-socket2 = { version = "0.5.7", default-features = false }
+[workspace.dependencies]
 thiserror = "1.0.60"
-tracing = { version = "0.1.40", default-features = false }
-
-# TUI dependencies
 anyhow = "1.0.83"
-dns-lookup = "2.0.4"
-hickory-resolver = "0.24.1"
-crossbeam = "0.8.4"
-clap = { version = "4.4.0",  default-features = false, features = [ "cargo", "derive", "wrap_help", "usage", "unstable-styles" ] }
-clap_complete = "4.4.9"
-humantime = "2.1.0"
-parking_lot = "0.12.2"
-ratatui = "0.26.2"
-crossterm = { version = "0.27.0",  default-features = false, features = [ "events", "windows" ] }
-chrono = { version = "0.4.38", default-features = false, features = [ "clock" ] }
 itertools = "0.12.1"
 serde = { version = "1.0.201", default-features = false }
-serde_json = { version = "1.0.117", default-features = false }
-comfy-table = { version = "7.1.0", default-features = false }
-strum = { version = "0.26.2", default-features = false, features = [ "std", "derive" ] }
-etcetera = "0.8.0"
-toml = { version = "0.8.13", default-features = false, features = [ "parse" ] }
-indexmap = { version = "2.2.6", default-features = false }
-maxminddb = "0.24.0"
-tracing-subscriber = { version = "0.3.18", default-features = false, features = [ "json", "env-filter" ] }
-tracing-chrome = "0.7.2"
-petgraph = "0.6.5"
-csv = "1.3.0"
-serde_with = "3.8.1"
-encoding_rs_io = "0.1.7"
-bitflags = "2.5.0"
-clap_mangen = "0.2.20"
-
-# Library dependencies (Linux)
-[target.'cfg(target_os = "linux")'.dependencies]
-caps = "0.5.5"
-
-# Library dependencies (Unix)
-[target.'cfg(unix)'.dependencies]
-nix = { version = "0.28.0", default-features = false, features = [ "user", "poll", "net" ] }
-
-# Library dependencies (Windows)
-[target.'cfg(windows)'.dependencies]
-paste = "1.0.15"
-widestring = "1.0.2"
-windows-sys = { version = "0.52.0", features = [
-    "Win32_Foundation",
-    "Win32_Networking_WinSock",
-    "Win32_System_IO",
-    "Win32_NetworkManagement_IpHelper",
-    "Win32_NetworkManagement_Ndis",
-    "Win32_System_IO",
-    "Win32_System_Threading",
-    "Win32_Security",
-] }
-
-[dev-dependencies]
-hex-literal = "0.4.1"
-pretty_assertions = "1.4.0"
-rand = "0.8.5"
-test-case = "3.3.1"
 serde_yaml = "=0.9.33"
-tokio = { version = "1.37.0", features = [ "full" ] }
-tokio-util = "0.7.11"
-ipnetwork = "0.20.0"
-mockall = "0.12.1"
-
-# see https://github.com/meh/rust-tun/pull/74
-[target.'cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))'.dev-dependencies]
-tun2 = { version = "1.3.0", features = [ "async" ] }
-
-[features]
-# Enable simulation integration tests
-sim-tests = []
-
-# cargo-generate-rpm dependencies
-[package.metadata.generate-rpm]
-assets = [
-  { source = "target/release/trip", dest = "/usr/bin/trip", mode = "755" },
-]
+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.2"
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index d010bf8eb..5f7420b6c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,15 +1,32 @@
-FROM rust:1.74 as build-env
+FROM rust:1.75 as build-env
 RUN rustup target add x86_64-unknown-linux-musl
 WORKDIR /app
 COPY Cargo.toml /app
 COPY Cargo.lock /app
-RUN mkdir /app/src
-RUN echo "fn main() {}" > /app/src/main.rs
+RUN mkdir -p /app/crates/trippy/src
+RUN mkdir -p /app/crates/trippy-core/src
+RUN mkdir -p /app/crates/trippy-dns/src
+RUN mkdir -p /app/crates/trippy-privilege/src
+COPY crates/trippy/Cargo.toml /app/crates/trippy/Cargo.toml
+COPY crates/trippy-core/Cargo.toml /app/crates/trippy-core/Cargo.toml
+COPY crates/trippy-dns/Cargo.toml /app/crates/trippy-dns/Cargo.toml
+COPY crates/trippy-privilege/Cargo.toml /app/crates/trippy-privilege/Cargo.toml
+
+# dummy build to cache dependencies
+RUN echo "fn main() {}" > /app/crates/trippy/src/main.rs
+RUN touch /app/crates/trippy-core/src/lib.rs
+RUN touch /app/crates/trippy-dns/src/lib.rs
+RUN touch /app/crates/trippy-privilege/src/lib.rs
 RUN cargo build --release --target=x86_64-unknown-linux-musl
-COPY src /app/src
+
+# copy the actual application code and build
+COPY crates/trippy/src /app/crates/trippy/src
+COPY crates/trippy-core/src /app/crates/trippy-core/src
+COPY crates/trippy-dns/src /app/crates/trippy-dns/src
+COPY crates/trippy-privilege/src /app/crates/trippy-privilege/src
 COPY trippy-config-sample.toml /app
 COPY README.md /app
-COPY LICENSE /app
+RUN cargo clean --release --target=x86_64-unknown-linux-musl -p trippy-core -p trippy-dns -p trippy-privilege
 RUN cargo build --release --target=x86_64-unknown-linux-musl
 
 FROM alpine
diff --git a/README.md b/README.md
index a1379439b..46653d6a5 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
 Trippy combines the functionality of traceroute and ping and is designed to assist with the analysis of networking
 issues.
 
-<img src="assets/0.8.0/trippy.gif" alt="trippy"/>
+<img src="https://raw.githubusercontent.com/fujiapple852/trippy/master/assets/0.8.0/trippy.gif" alt="trippy"/>
 
 ## Navigation
 
diff --git a/crates/README.md b/crates/README.md
new file mode 100644
index 000000000..9cd8070d4
--- /dev/null
+++ b/crates/README.md
@@ -0,0 +1,39 @@
+## Crates
+
+The following is a list of the crates defined by Trippy and their purposes:
+
+### `trippy`
+
+A binary crate for the Trippy application. This is the crate you would use if you wish to install and run Trippy as a
+standalone tool.
+
+```shell
+cargo install --locked trippy
+```
+
+### `trippy-core`
+
+A library crate providing the core Trippy tracing functionality. This crate is used by the Trippy application and is
+the crate you would use if you wish to provide the Trippy tracing functionality in your own application.
+
+```shell
+cargo add trippy-core
+```
+
+### `trippy-dns`
+
+A library crate for performing forward and reverse lazy DNS resolution. This crate is designed to be used by the Trippy
+application but may also be useful for other applications that need to perform forward and reverse lazy DNS resolution.
+
+```shell
+cargo add trippy-dns
+```
+
+### `trippy-privilege`
+
+A library crate for discovering platform privileges. This crate is designed to be used by the Trippy application but
+may also be useful for other applications.
+
+```shell
+cargo add trippy-privilege
+```
\ No newline at end of file
diff --git a/crates/trippy-core/Cargo.toml b/crates/trippy-core/Cargo.toml
new file mode 100644
index 000000000..754c4c208
--- /dev/null
+++ b/crates/trippy-core/Cargo.toml
@@ -0,0 +1,61 @@
+[package]
+name = "trippy-core"
+description = "A network tracing library"
+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]
+derive_more.workspace = true
+thiserror.workspace = true
+tracing.workspace = true
+itertools.workspace = true
+arrayvec = { version = "0.7.4", default-features = false }
+socket2 = { version = "0.5.7", features = [ "all" ] }
+bitflags = "2.5.0"
+
+[target.'cfg(unix)'.dependencies]
+nix = { version = "0.28.0", default-features = false, features = [ "user", "poll", "net" ] }
+
+[target.'cfg(windows)'.dependencies]
+paste = "1.0.15"
+widestring = "1.0.2"
+windows-sys = { version = "0.52.0", features = [
+    "Win32_Foundation",
+    "Win32_Networking_WinSock",
+    "Win32_System_IO",
+    "Win32_NetworkManagement_IpHelper",
+    "Win32_NetworkManagement_Ndis",
+    "Win32_System_IO",
+    "Win32_System_Threading",
+    "Win32_Security",
+] }
+
+[dev-dependencies]
+anyhow.workspace = true
+serde.workspace = true
+test-case.workspace = true
+serde_yaml.workspace = true
+hex-literal = "0.4.1"
+rand = "0.8.5"
+tokio = { version = "1.37.0", features = [ "full" ] }
+tokio-util = "0.7.11"
+ipnetwork = "0.20.0"
+mockall = "0.12.1"
+tracing-subscriber = "0.3.18"
+
+# see https://github.com/meh/rust-tun/pull/74
+[target.'cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))'.dev-dependencies]
+tun2 = { version = "1.3.0", features = [ "async" ] }
+
+[features]
+# Enable simulation integration tests
+sim-tests = []
\ No newline at end of file
diff --git a/src/tracing/builder.rs b/crates/trippy-core/src/builder.rs
similarity index 97%
rename from src/tracing/builder.rs
rename to crates/trippy-core/src/builder.rs
index 3a563829f..1e3a4e88a 100644
--- a/src/tracing/builder.rs
+++ b/crates/trippy-core/src/builder.rs
@@ -1,6 +1,6 @@
-use crate::tracing::error::TraceResult;
-use crate::tracing::net::PlatformImpl;
-use crate::tracing::{
+use crate::error::TraceResult;
+use crate::net::PlatformImpl;
+use crate::{
     ChannelConfig, Config, IcmpExtensionParseMode, MaxInflight, MaxRounds, MultipathStrategy,
     PacketSize, PayloadPattern, PortDirection, PrivilegeMode, Protocol, Sequence, SocketImpl,
     SourceAddr, TimeToLive, TraceId, Tracer, TracerChannel, TracerRound, TypeOfService,
@@ -19,7 +19,7 @@ use std::time::Duration;
 /// # Examples:
 ///
 /// ```no_run
-/// use trippy::tracing::{Builder, MultipathStrategy, Port, PortDirection, PrivilegeMode, Protocol};
+/// use trippy_core::{Builder, MultipathStrategy, Port, PortDirection, PrivilegeMode, Protocol};
 ///
 /// let addr = std::net::IpAddr::from([1, 2, 3, 4]);
 /// Builder::new(addr, |round| println!("{:?}", round))
diff --git a/src/tracing/config.rs b/crates/trippy-core/src/config.rs
similarity index 98%
rename from src/tracing/config.rs
rename to crates/trippy-core/src/config.rs
index f92a4dffd..44c8f7188 100644
--- a/src/tracing/config.rs
+++ b/crates/trippy-core/src/config.rs
@@ -1,6 +1,6 @@
-use crate::tracing::constants::{MAX_INITIAL_SEQUENCE, MAX_TTL};
-use crate::tracing::error::{TraceResult, TracerError};
-use crate::tracing::types::{
+use crate::constants::{MAX_INITIAL_SEQUENCE, MAX_TTL};
+use crate::error::{TraceResult, TracerError};
+use crate::types::{
     MaxInflight, MaxRounds, PacketSize, PayloadPattern, Port, Sequence, TimeToLive, TraceId,
     TypeOfService,
 };
@@ -9,9 +9,10 @@ use std::net::{IpAddr, Ipv4Addr};
 use std::num::NonZeroUsize;
 use std::time::Duration;
 
+/// Default values for configuration.
 pub mod defaults {
-    use crate::tracing::config::IcmpExtensionParseMode;
-    use crate::tracing::{MultipathStrategy, PrivilegeMode, Protocol};
+    use crate::config::IcmpExtensionParseMode;
+    use crate::{MultipathStrategy, PrivilegeMode, Protocol};
     use std::time::Duration;
 
     /// The default value for `unprivileged`.
diff --git a/src/tracing/constants.rs b/crates/trippy-core/src/constants.rs
similarity index 100%
rename from src/tracing/constants.rs
rename to crates/trippy-core/src/constants.rs
diff --git a/src/tracing/error.rs b/crates/trippy-core/src/error.rs
similarity index 98%
rename from src/tracing/error.rs
rename to crates/trippy-core/src/error.rs
index 0b2cbfdea..cc6e74009 100644
--- a/src/tracing/error.rs
+++ b/crates/trippy-core/src/error.rs
@@ -1,4 +1,4 @@
-use crate::tracing::packet::error::PacketError;
+use crate::packet::error::PacketError;
 use std::fmt::{Display, Formatter};
 use std::io;
 use std::io::ErrorKind;
diff --git a/crates/trippy-core/src/lib.rs b/crates/trippy-core/src/lib.rs
new file mode 100644
index 000000000..b27e2c227
--- /dev/null
+++ b/crates/trippy-core/src/lib.rs
@@ -0,0 +1,85 @@
+//! Trippy - A network tracing library.
+//!
+//! This crate provides the core network tracing facility used by the
+//! standalone [Trippy](https://trippy.cli.rs) application.
+//!
+//! Note: the public API is not stable and is highly likely to change
+//! in the future.
+//!
+//! # Example
+//!
+//! The following example builds and runs a tracer with default configuration
+//! and prints out the tracing data for each round:
+//!
+//! ```no_run
+//! # fn main() -> anyhow::Result<()> {
+//! # use std::net::IpAddr;
+//! # use std::str::FromStr;
+//! use trippy_core::Builder;
+//!
+//! let addr = IpAddr::from_str("1.1.1.1")?;
+//! Builder::new(addr, |round| println!("{:?}", round)).start()?;
+//! # Ok(())
+//! # }
+//! ```
+//!
+//! The following example traces using the UDP protocol with the Dublin ECMP
+//! strategy with fixed src and dest ports.  It also operates in unprivileged
+//! mode (only supported on some platforms):
+//!
+//! ```no_run
+//! # fn main() -> anyhow::Result<()> {
+//! # use std::net::IpAddr;
+//! # use std::str::FromStr;
+//! use trippy_core::{Builder, MultipathStrategy, Port, PortDirection, PrivilegeMode, Protocol};
+//!
+//! let addr = IpAddr::from_str("1.1.1.1")?;
+//! Builder::new(addr, |round| println!("{:?}", round))
+//!     .privilege_mode(PrivilegeMode::Unprivileged)
+//!     .protocol(Protocol::Udp)
+//!     .multipath_strategy(MultipathStrategy::Dublin)
+//!     .port_direction(PortDirection::FixedBoth(Port(33000), Port(3500)))
+//!     .start()?;
+//! # Ok(())
+//! # }
+//! ```
+#![warn(clippy::all, clippy::pedantic, clippy::nursery, rust_2018_idioms)]
+#![allow(
+    clippy::module_name_repetitions,
+    clippy::struct_field_names,
+    clippy::option_if_let_else,
+    clippy::missing_const_for_fn,
+    clippy::cast_possible_truncation,
+    clippy::missing_errors_doc
+)]
+#![deny(unsafe_code)]
+
+mod builder;
+mod config;
+mod constants;
+mod error;
+mod net;
+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,
+    MultipathStrategy, PortDirection, PrivilegeMode, Protocol,
+};
+pub use net::channel::TracerChannel;
+pub use net::source::SourceAddr;
+pub use net::{PlatformImpl, SocketImpl};
+pub use probe::{
+    Extension, Extensions, IcmpPacketType, MplsLabelStack, MplsLabelStackMember, Probe,
+    ProbeComplete, ProbeState, UnknownExtension,
+};
+pub use tracer::{CompletionReason, Tracer, TracerRound};
+pub use types::{
+    Flags, MaxInflight, MaxRounds, PacketSize, PayloadPattern, Port, Round, Sequence, TimeToLive,
+    TraceId, TypeOfService,
+};
diff --git a/src/tracing/net.rs b/crates/trippy-core/src/net.rs
similarity index 90%
rename from src/tracing/net.rs
rename to crates/trippy-core/src/net.rs
index 19cb30524..bbab01282 100644
--- a/src/tracing/net.rs
+++ b/crates/trippy-core/src/net.rs
@@ -1,5 +1,5 @@
-use crate::tracing::error::TraceResult;
-use crate::tracing::probe::{Probe, ProbeResponse};
+use crate::error::TraceResult;
+use crate::probe::{Probe, ProbeResponse};
 
 /// Common types and helper functions.
 mod common;
diff --git a/src/tracing/net/channel.rs b/crates/trippy-core/src/net/channel.rs
similarity index 95%
rename from src/tracing/net/channel.rs
rename to crates/trippy-core/src/net/channel.rs
index a08e49af2..f6675f713 100644
--- a/src/tracing/net/channel.rs
+++ b/crates/trippy-core/src/net/channel.rs
@@ -1,10 +1,10 @@
-use crate::tracing::config::IcmpExtensionParseMode;
-use crate::tracing::error::{TraceResult, TracerError};
-use crate::tracing::net::socket::Socket;
-use crate::tracing::net::{ipv4, ipv6, platform, Network};
-use crate::tracing::probe::{Probe, ProbeResponse};
-use crate::tracing::types::{PacketSize, PayloadPattern, TypeOfService};
-use crate::tracing::{ChannelConfig, Port, PrivilegeMode, Protocol, Sequence};
+use crate::config::IcmpExtensionParseMode;
+use crate::error::{TraceResult, TracerError};
+use crate::net::socket::Socket;
+use crate::net::{ipv4, ipv6, platform, Network};
+use crate::probe::{Probe, ProbeResponse};
+use crate::types::{PacketSize, PayloadPattern, TypeOfService};
+use crate::{ChannelConfig, Port, PrivilegeMode, Protocol, Sequence};
 use arrayvec::ArrayVec;
 use std::net::IpAddr;
 use std::time::{Duration, SystemTime};
diff --git a/src/tracing/net/common.rs b/crates/trippy-core/src/net/common.rs
similarity index 92%
rename from src/tracing/net/common.rs
rename to crates/trippy-core/src/net/common.rs
index 6405f7685..ae3f94004 100644
--- a/src/tracing/net/common.rs
+++ b/crates/trippy-core/src/net/common.rs
@@ -1,5 +1,5 @@
-use crate::tracing::error::{IoResult, TraceResult, TracerError};
-use crate::tracing::net::platform::in_progress_error;
+use crate::error::{IoResult, TraceResult, TracerError};
+use crate::net::platform::in_progress_error;
 use std::io::ErrorKind;
 use std::net::SocketAddr;
 
@@ -25,9 +25,9 @@ pub fn process_result(addr: SocketAddr, res: IoResult<()>) -> TraceResult<()> {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::tracing::error::{IoError, IoOperation};
+    use crate::error::{IoError, IoOperation};
+    use std::io;
     use std::net::{Ipv4Addr, SocketAddrV4};
-    use tokio::io;
 
     const ADDR: SocketAddr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0));
 
diff --git a/src/tracing/net/extension.rs b/crates/trippy-core/src/net/extension.rs
similarity index 87%
rename from src/tracing/net/extension.rs
rename to crates/trippy-core/src/net/extension.rs
index cefdeea83..314852482 100644
--- a/src/tracing/net/extension.rs
+++ b/crates/trippy-core/src/net/extension.rs
@@ -1,12 +1,10 @@
-use crate::tracing::error::TracerError;
-use crate::tracing::packet::icmp_extension::extension_header::ExtensionHeaderPacket;
-use crate::tracing::packet::icmp_extension::extension_object::{ClassNum, ExtensionObjectPacket};
-use crate::tracing::packet::icmp_extension::extension_structure::ExtensionsPacket;
-use crate::tracing::packet::icmp_extension::mpls_label_stack::MplsLabelStackPacket;
-use crate::tracing::packet::icmp_extension::mpls_label_stack_member::MplsLabelStackMemberPacket;
-use crate::tracing::probe::{
-    Extension, Extensions, MplsLabelStack, MplsLabelStackMember, UnknownExtension,
-};
+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};
 
 /// The supported ICMP extension version number.
 const ICMP_EXTENSION_VERSION: u8 = 2;
diff --git a/src/tracing/net/ipv4.rs b/crates/trippy-core/src/net/ipv4.rs
similarity index 97%
rename from src/tracing/net/ipv4.rs
rename to crates/trippy-core/src/net/ipv4.rs
index 239236b64..511a4a808 100644
--- a/src/tracing/net/ipv4.rs
+++ b/crates/trippy-core/src/net/ipv4.rs
@@ -1,25 +1,25 @@
-use crate::tracing::config::IcmpExtensionParseMode;
-use crate::tracing::error::{TraceResult, TracerError};
-use crate::tracing::net::channel::MAX_PACKET_SIZE;
-use crate::tracing::net::common::process_result;
-use crate::tracing::net::platform;
-use crate::tracing::net::socket::{Socket, SocketError};
-use crate::tracing::packet::checksum::{icmp_ipv4_checksum, udp_ipv4_checksum};
-use crate::tracing::packet::icmpv4::destination_unreachable::DestinationUnreachablePacket;
-use crate::tracing::packet::icmpv4::echo_reply::EchoReplyPacket;
-use crate::tracing::packet::icmpv4::echo_request::EchoRequestPacket;
-use crate::tracing::packet::icmpv4::time_exceeded::TimeExceededPacket;
-use crate::tracing::packet::icmpv4::{IcmpCode, IcmpPacket, IcmpTimeExceededCode, IcmpType};
-use crate::tracing::packet::ipv4::Ipv4Packet;
-use crate::tracing::packet::tcp::TcpPacket;
-use crate::tracing::packet::udp::UdpPacket;
-use crate::tracing::packet::IpProtocol;
-use crate::tracing::probe::{
+use crate::config::IcmpExtensionParseMode;
+use crate::error::{TraceResult, TracerError};
+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,
 };
-use crate::tracing::types::{PacketSize, PayloadPattern, Sequence, TraceId, TypeOfService};
-use crate::tracing::{Flags, Port, PrivilegeMode, Protocol};
+use crate::types::{PacketSize, PayloadPattern, Sequence, TraceId, TypeOfService};
+use crate::{Flags, Port, PrivilegeMode, Protocol};
 use std::io::ErrorKind;
 use std::net::{IpAddr, Ipv4Addr, SocketAddr};
 use std::time::SystemTime;
@@ -504,10 +504,10 @@ fn extract_tcp_packet(ipv4: &Ipv4Packet<'_>) -> TraceResult<(u16, u16)> {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::error::IoResult;
     use crate::mocket_read;
-    use crate::tracing::error::IoResult;
-    use crate::tracing::net::socket::MockSocket;
-    use crate::tracing::{Flags, Port, Round, TimeToLive};
+    use crate::net::socket::MockSocket;
+    use crate::{Flags, Port, Round, TimeToLive};
     use mockall::predicate;
     use std::str::FromStr;
     use std::sync::Mutex;
diff --git a/src/tracing/net/ipv6.rs b/crates/trippy-core/src/net/ipv6.rs
similarity index 98%
rename from src/tracing/net/ipv6.rs
rename to crates/trippy-core/src/net/ipv6.rs
index d411dffef..92a788a6a 100644
--- a/src/tracing/net/ipv6.rs
+++ b/crates/trippy-core/src/net/ipv6.rs
@@ -1,24 +1,24 @@
-use crate::tracing::config::IcmpExtensionParseMode;
-use crate::tracing::error::{TraceResult, TracerError};
-use crate::tracing::net::channel::MAX_PACKET_SIZE;
-use crate::tracing::net::common::process_result;
-use crate::tracing::net::socket::{Socket, SocketError};
-use crate::tracing::packet::checksum::{icmp_ipv6_checksum, udp_ipv6_checksum};
-use crate::tracing::packet::icmpv6::destination_unreachable::DestinationUnreachablePacket;
-use crate::tracing::packet::icmpv6::echo_reply::EchoReplyPacket;
-use crate::tracing::packet::icmpv6::echo_request::EchoRequestPacket;
-use crate::tracing::packet::icmpv6::time_exceeded::TimeExceededPacket;
-use crate::tracing::packet::icmpv6::{IcmpCode, IcmpPacket, IcmpTimeExceededCode, IcmpType};
-use crate::tracing::packet::ipv6::Ipv6Packet;
-use crate::tracing::packet::tcp::TcpPacket;
-use crate::tracing::packet::udp::UdpPacket;
-use crate::tracing::packet::IpProtocol;
-use crate::tracing::probe::{
+use crate::config::IcmpExtensionParseMode;
+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,
 };
-use crate::tracing::types::{PacketSize, PayloadPattern, Sequence, TraceId};
-use crate::tracing::{Flags, Port, PrivilegeMode, Protocol};
+use crate::types::{PacketSize, PayloadPattern, Sequence, TraceId};
+use crate::{Flags, Port, PrivilegeMode, Protocol};
 use std::io::ErrorKind;
 use std::net::{IpAddr, Ipv6Addr, SocketAddr};
 use std::time::SystemTime;
@@ -472,10 +472,10 @@ fn udp_payload_has_magic_prefix(ipv6: &Ipv6Packet<'_>) -> TraceResult<bool> {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::error::IoResult;
     use crate::mocket_recv_from;
-    use crate::tracing::error::IoResult;
-    use crate::tracing::net::socket::MockSocket;
-    use crate::tracing::{Flags, Port, Round, TimeToLive};
+    use crate::net::socket::MockSocket;
+    use crate::{Flags, Port, Round, TimeToLive};
     use mockall::predicate;
     use std::str::FromStr;
     use std::sync::Mutex;
diff --git a/src/tracing/net/platform.rs b/crates/trippy-core/src/net/platform.rs
similarity index 95%
rename from src/tracing/net/platform.rs
rename to crates/trippy-core/src/net/platform.rs
index 7050d2462..8fc74cdad 100644
--- a/src/tracing/net/platform.rs
+++ b/crates/trippy-core/src/net/platform.rs
@@ -6,7 +6,7 @@ use std::net::IpAddr;
 #[cfg(unix)]
 mod unix;
 
-use crate::tracing::error::TraceResult;
+use crate::error::TraceResult;
 #[cfg(unix)]
 pub use unix::*;
 
diff --git a/src/tracing/net/platform/byte_order.rs b/crates/trippy-core/src/net/platform/byte_order.rs
similarity index 96%
rename from src/tracing/net/platform/byte_order.rs
rename to crates/trippy-core/src/net/platform/byte_order.rs
index 75a43e6dd..88fa0cef3 100644
--- a/src/tracing/net/platform/byte_order.rs
+++ b/crates/trippy-core/src/net/platform/byte_order.rs
@@ -1,5 +1,5 @@
-use crate::tracing::error::TraceResult;
-use crate::tracing::net::platform::{Platform, PlatformImpl};
+use crate::error::TraceResult;
+use crate::net::platform::{Platform, PlatformImpl};
 use std::net::IpAddr;
 
 /// The byte order to encode the `total_length`, `flags` and `fragment_offset` fields of the IPv4
diff --git a/src/tracing/net/platform/unix.rs b/crates/trippy-core/src/net/platform/unix.rs
similarity index 97%
rename from src/tracing/net/platform/unix.rs
rename to crates/trippy-core/src/net/platform/unix.rs
index 9d639b705..5352648f5 100644
--- a/src/tracing/net/platform/unix.rs
+++ b/crates/trippy-core/src/net/platform/unix.rs
@@ -1,5 +1,5 @@
-use crate::tracing::error::TraceResult;
-use crate::tracing::net::platform::{Ipv4ByteOrder, Platform};
+use crate::error::TraceResult;
+use crate::net::platform::{Ipv4ByteOrder, Platform};
 use std::net::IpAddr;
 
 pub struct PlatformImpl;
@@ -17,10 +17,10 @@ impl Platform for PlatformImpl {
 }
 
 mod address {
-    use crate::tracing::error::{TraceResult, TracerError};
-    use crate::tracing::net::platform::Ipv4ByteOrder;
-    use crate::tracing::net::socket::Socket;
-    use crate::tracing::SocketImpl;
+    use crate::error::{TraceResult, TracerError};
+    use crate::net::platform::Ipv4ByteOrder;
+    use crate::net::socket::Socket;
+    use crate::SocketImpl;
     use nix::sys::socket::{AddressFamily, SockaddrLike};
     use std::net::{IpAddr, SocketAddr};
     use tracing::instrument;
@@ -72,7 +72,7 @@ mod address {
     #[cfg(not(target_os = "linux"))]
     #[instrument(ret)]
     fn test_send_local_ip4_packet(src_addr: Ipv4Addr, total_length: u16) -> TraceResult<()> {
-        use crate::tracing::packet;
+        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)?;
@@ -149,9 +149,9 @@ mod address {
 }
 
 mod socket {
-    use crate::tracing::error::{IoError, IoOperation};
-    use crate::tracing::error::{IoResult, TraceResult};
-    use crate::tracing::net::socket::{Socket, SocketError};
+    use crate::error::{IoError, IoOperation};
+    use crate::error::{IoResult, TraceResult};
+    use crate::net::socket::{Socket, SocketError};
     use itertools::Itertools;
     use nix::{
         sys::select::FdSet,
diff --git a/src/tracing/net/platform/windows.rs b/crates/trippy-core/src/net/platform/windows.rs
similarity index 98%
rename from src/tracing/net/platform/windows.rs
rename to crates/trippy-core/src/net/platform/windows.rs
index bf3d1a31b..c7436c0f3 100644
--- a/src/tracing/net/platform/windows.rs
+++ b/crates/trippy-core/src/net/platform/windows.rs
@@ -1,9 +1,9 @@
 use super::byte_order::Ipv4ByteOrder;
-use crate::tracing::error::{IoError, IoOperation, IoResult, TraceResult, TracerError};
-use crate::tracing::net::channel::MAX_PACKET_SIZE;
-use crate::tracing::net::platform::windows::adapter::Adapters;
-use crate::tracing::net::platform::Platform;
-use crate::tracing::net::socket::{Socket, SocketError};
+use crate::error::{IoError, IoOperation, IoResult, TraceResult, TracerError};
+use crate::net::channel::MAX_PACKET_SIZE;
+use crate::net::platform::windows::adapter::Adapters;
+use crate::net::platform::Platform;
+use crate::net::socket::{Socket, SocketError};
 use itertools::Itertools;
 use socket2::{Domain, Protocol, SockAddr, Type};
 use std::ffi::c_void;
@@ -735,8 +735,8 @@ fn lookup_interface_addr(adapters: &Adapters, name: &str) -> TraceResult<IpAddr>
 }
 
 mod adapter {
-    use crate::tracing::error::{TraceResult, TracerError};
-    use crate::tracing::net::platform::windows::sockaddrptr_to_ipaddr;
+    use crate::error::{TraceResult, TracerError};
+    use crate::net::platform::windows::sockaddrptr_to_ipaddr;
     use std::io::Error;
     use std::marker::PhantomData;
     use std::net::IpAddr;
diff --git a/src/tracing/net/socket.rs b/crates/trippy-core/src/net/socket.rs
similarity index 98%
rename from src/tracing/net/socket.rs
rename to crates/trippy-core/src/net/socket.rs
index 4d5f61489..b3d381d40 100644
--- a/src/tracing/net/socket.rs
+++ b/crates/trippy-core/src/net/socket.rs
@@ -1,4 +1,4 @@
-use crate::tracing::error::IoResult as Result;
+use crate::error::IoResult as Result;
 use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
 use std::time::Duration;
 
diff --git a/src/tracing/net/source.rs b/crates/trippy-core/src/net/source.rs
similarity index 94%
rename from src/tracing/net/source.rs
rename to crates/trippy-core/src/net/source.rs
index 3322ac873..fe9d3b678 100644
--- a/src/tracing/net/source.rs
+++ b/crates/trippy-core/src/net/source.rs
@@ -1,9 +1,9 @@
-use crate::tracing::error::TraceResult;
-use crate::tracing::error::TracerError::InvalidSourceAddr;
-use crate::tracing::net::platform::Platform;
-use crate::tracing::net::socket::Socket;
-use crate::tracing::types::Port;
-use crate::tracing::PortDirection;
+use crate::error::TraceResult;
+use crate::error::TracerError::InvalidSourceAddr;
+use crate::net::platform::Platform;
+use crate::net::socket::Socket;
+use crate::types::Port;
+use crate::PortDirection;
 use std::net::{IpAddr, SocketAddr};
 
 /// The port used for local address discovery if not dest port is available.
@@ -46,9 +46,9 @@ impl SourceAddr {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::tracing::error::{IoError, TracerError};
-    use crate::tracing::net::platform::MockPlatform;
-    use crate::tracing::net::socket::MockSocket;
+    use crate::error::{IoError, TracerError};
+    use crate::net::platform::MockPlatform;
+    use crate::net::socket::MockSocket;
     use mockall::predicate;
     use std::io::Error;
     use std::str::FromStr;
diff --git a/src/tracing/packet.rs b/crates/trippy-core/src/packet.rs
similarity index 100%
rename from src/tracing/packet.rs
rename to crates/trippy-core/src/packet.rs
diff --git a/src/tracing/packet/buffer.rs b/crates/trippy-core/src/packet/buffer.rs
similarity index 100%
rename from src/tracing/packet/buffer.rs
rename to crates/trippy-core/src/packet/buffer.rs
diff --git a/src/tracing/packet/checksum.rs b/crates/trippy-core/src/packet/checksum.rs
similarity index 99%
rename from src/tracing/packet/checksum.rs
rename to crates/trippy-core/src/packet/checksum.rs
index 40cca2919..be99fdcad 100644
--- a/src/tracing/packet/checksum.rs
+++ b/crates/trippy-core/src/packet/checksum.rs
@@ -4,7 +4,7 @@
 //!
 //! [`libpnet`]: https://github.com/libpnet/libpnet
 
-use crate::tracing::packet::IpProtocol;
+use crate::packet::IpProtocol;
 use std::net::{Ipv4Addr, Ipv6Addr};
 
 /// Calculate the checksum for an `Ipv4` header.
diff --git a/src/tracing/packet/error.rs b/crates/trippy-core/src/packet/error.rs
similarity index 100%
rename from src/tracing/packet/error.rs
rename to crates/trippy-core/src/packet/error.rs
diff --git a/src/tracing/packet/icmp_extension.rs b/crates/trippy-core/src/packet/icmp_extension.rs
similarity index 95%
rename from src/tracing/packet/icmp_extension.rs
rename to crates/trippy-core/src/packet/icmp_extension.rs
index 148de7ab9..69f39a189 100644
--- a/src/tracing/packet/icmp_extension.rs
+++ b/crates/trippy-core/src/packet/icmp_extension.rs
@@ -1,7 +1,7 @@
 pub mod extension_structure {
-    use crate::tracing::packet::buffer::Buffer;
-    use crate::tracing::packet::error::{PacketError, PacketResult};
-    use crate::tracing::packet::icmp_extension::extension_object::ExtensionObjectPacket;
+    use crate::packet::buffer::Buffer;
+    use crate::packet::error::{PacketError, PacketResult};
+    use crate::packet::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::tracing::packet::icmp_extension::extension_header::ExtensionHeaderPacket;
-        use crate::tracing::packet::icmp_extension::extension_object::{
+        use crate::packet::icmp_extension::extension_header::ExtensionHeaderPacket;
+        use crate::packet::icmp_extension::extension_object::{
             ClassNum, ClassSubType, ExtensionObjectPacket,
         };
 
@@ -152,8 +152,8 @@ pub mod extension_structure {
 }
 
 pub mod extension_header {
-    use crate::tracing::packet::buffer::Buffer;
-    use crate::tracing::packet::error::{PacketError, PacketResult};
+    use crate::packet::buffer::Buffer;
+    use crate::packet::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::tracing::packet::buffer::Buffer;
-    use crate::tracing::packet::error::{PacketError, PacketResult};
-    use crate::tracing::packet::fmt_payload;
+    use crate::packet::buffer::Buffer;
+    use crate::packet::error::{PacketError, PacketResult};
+    use crate::packet::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::tracing::packet::buffer::Buffer;
-    use crate::tracing::packet::error::{PacketError, PacketResult};
-    use crate::tracing::packet::icmp_extension::mpls_label_stack_member::MplsLabelStackMemberPacket;
+    use crate::packet::buffer::Buffer;
+    use crate::packet::error::{PacketError, PacketResult};
+    use crate::packet::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::tracing::packet::buffer::Buffer;
-    use crate::tracing::packet::error::{PacketError, PacketResult};
+    use crate::packet::buffer::Buffer;
+    use crate::packet::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::tracing::packet::icmp_extension::extension_header::ExtensionHeaderPacket;
+    use crate::packet::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::tracing::packet::icmp_extension::extension_header::ExtensionHeaderPacket;
-        use crate::tracing::packet::icmp_extension::extension_object::{
+        use crate::packet::icmp_extension::extension_header::ExtensionHeaderPacket;
+        use crate::packet::icmp_extension::extension_object::{
             ClassNum, ClassSubType, ExtensionObjectPacket,
         };
-        use crate::tracing::packet::icmp_extension::extension_structure::ExtensionsPacket;
-        use crate::tracing::packet::icmp_extension::mpls_label_stack::MplsLabelStackPacket;
-        use crate::tracing::packet::icmp_extension::mpls_label_stack_member::MplsLabelStackMemberPacket;
+        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;
 
         #[test]
         fn test_split_empty_payload() {
@@ -959,10 +959,10 @@ pub mod extension_splitter {
 
         mod ipv4 {
             use super::*;
-            use crate::tracing::packet::icmpv4::echo_request::EchoRequestPacket;
-            use crate::tracing::packet::icmpv4::time_exceeded::TimeExceededPacket;
-            use crate::tracing::packet::icmpv4::{IcmpCode, IcmpType};
-            use crate::tracing::packet::ipv4::Ipv4Packet;
+            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 std::net::Ipv4Addr;
 
             // This ICMP TimeExceeded packet which contains single `MPLS` extension
@@ -1136,17 +1136,17 @@ pub mod extension_splitter {
         }
 
         mod ipv6 {
-            use crate::tracing::packet::icmp_extension::extension_header::ExtensionHeaderPacket;
-            use crate::tracing::packet::icmp_extension::extension_object::{
+            use crate::packet::icmp_extension::extension_header::ExtensionHeaderPacket;
+            use crate::packet::icmp_extension::extension_object::{
                 ClassNum, ClassSubType, ExtensionObjectPacket,
             };
-            use crate::tracing::packet::icmp_extension::extension_structure::ExtensionsPacket;
-            use crate::tracing::packet::icmp_extension::mpls_label_stack::MplsLabelStackPacket;
-            use crate::tracing::packet::icmp_extension::mpls_label_stack_member::MplsLabelStackMemberPacket;
-            use crate::tracing::packet::icmpv6::echo_request::EchoRequestPacket;
-            use crate::tracing::packet::icmpv6::time_exceeded::TimeExceededPacket;
-            use crate::tracing::packet::icmpv6::{IcmpCode, IcmpType};
-            use crate::tracing::packet::ipv6::Ipv6Packet;
+            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;
 
             // Real IPv6 example with an rfc4884 length of 10 (10 * 8 = 80
             // octets).
diff --git a/src/tracing/packet/icmpv4.rs b/crates/trippy-core/src/packet/icmpv4.rs
similarity index 97%
rename from src/tracing/packet/icmpv4.rs
rename to crates/trippy-core/src/packet/icmpv4.rs
index 1c1fd37fe..4d766a53f 100644
--- a/src/tracing/packet/icmpv4.rs
+++ b/crates/trippy-core/src/packet/icmpv4.rs
@@ -1,5 +1,5 @@
-use crate::tracing::packet::buffer::Buffer;
-use crate::tracing::packet::error::{PacketError, PacketResult};
+use crate::packet::buffer::Buffer;
+use crate::packet::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::tracing::packet::buffer::Buffer;
-    use crate::tracing::packet::error::{PacketError, PacketResult};
-    use crate::tracing::packet::fmt_payload;
-    use crate::tracing::packet::icmpv4::{IcmpCode, IcmpType};
+    use crate::packet::buffer::Buffer;
+    use crate::packet::error::{PacketError, PacketResult};
+    use crate::packet::fmt_payload;
+    use crate::packet::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::tracing::packet::buffer::Buffer;
-    use crate::tracing::packet::error::{PacketError, PacketResult};
-    use crate::tracing::packet::fmt_payload;
-    use crate::tracing::packet::icmpv4::{IcmpCode, IcmpType};
+    use crate::packet::buffer::Buffer;
+    use crate::packet::error::{PacketError, PacketResult};
+    use crate::packet::fmt_payload;
+    use crate::packet::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::tracing::packet::buffer::Buffer;
-    use crate::tracing::packet::error::{PacketError, PacketResult};
-    use crate::tracing::packet::fmt_payload;
-    use crate::tracing::packet::icmp_extension::extension_splitter::split;
-    use crate::tracing::packet::icmpv4::{IcmpCode, IcmpType};
+    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 std::fmt::{Debug, Formatter};
 
     const TYPE_OFFSET: usize = 0;
@@ -1011,11 +1011,11 @@ pub mod time_exceeded {
 }
 
 pub mod destination_unreachable {
-    use crate::tracing::packet::buffer::Buffer;
-    use crate::tracing::packet::error::{PacketError, PacketResult};
-    use crate::tracing::packet::fmt_payload;
-    use crate::tracing::packet::icmp_extension::extension_splitter::split;
-    use crate::tracing::packet::icmpv4::{IcmpCode, IcmpType};
+    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 std::fmt::{Debug, Formatter};
 
     const TYPE_OFFSET: usize = 0;
diff --git a/src/tracing/packet/icmpv6.rs b/crates/trippy-core/src/packet/icmpv6.rs
similarity index 97%
rename from src/tracing/packet/icmpv6.rs
rename to crates/trippy-core/src/packet/icmpv6.rs
index 2ec00bfeb..70feb23e8 100644
--- a/src/tracing/packet/icmpv6.rs
+++ b/crates/trippy-core/src/packet/icmpv6.rs
@@ -1,5 +1,5 @@
-use crate::tracing::packet::buffer::Buffer;
-use crate::tracing::packet::error::{PacketError, PacketResult};
+use crate::packet::buffer::Buffer;
+use crate::packet::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::tracing::packet::buffer::Buffer;
-    use crate::tracing::packet::error::{PacketError, PacketResult};
-    use crate::tracing::packet::fmt_payload;
-    use crate::tracing::packet::icmpv6::{IcmpCode, IcmpType};
+    use crate::packet::buffer::Buffer;
+    use crate::packet::error::{PacketError, PacketResult};
+    use crate::packet::fmt_payload;
+    use crate::packet::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::tracing::packet::buffer::Buffer;
-    use crate::tracing::packet::error::{PacketError, PacketResult};
-    use crate::tracing::packet::fmt_payload;
-    use crate::tracing::packet::icmpv6::{IcmpCode, IcmpType};
+    use crate::packet::buffer::Buffer;
+    use crate::packet::error::{PacketError, PacketResult};
+    use crate::packet::fmt_payload;
+    use crate::packet::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::tracing::packet::buffer::Buffer;
-    use crate::tracing::packet::error::{PacketError, PacketResult};
-    use crate::tracing::packet::fmt_payload;
-    use crate::tracing::packet::icmp_extension::extension_splitter::split;
-    use crate::tracing::packet::icmpv6::{IcmpCode, IcmpType};
+    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 std::fmt::{Debug, Formatter};
 
     const TYPE_OFFSET: usize = 0;
@@ -1008,11 +1008,11 @@ pub mod time_exceeded {
 }
 
 pub mod destination_unreachable {
-    use crate::tracing::packet::buffer::Buffer;
-    use crate::tracing::packet::error::{PacketError, PacketResult};
-    use crate::tracing::packet::fmt_payload;
-    use crate::tracing::packet::icmp_extension::extension_splitter::split;
-    use crate::tracing::packet::icmpv6::{IcmpCode, IcmpType};
+    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 std::fmt::{Debug, Formatter};
 
     const TYPE_OFFSET: usize = 0;
diff --git a/src/tracing/packet/ipv4.rs b/crates/trippy-core/src/packet/ipv4.rs
similarity index 99%
rename from src/tracing/packet/ipv4.rs
rename to crates/trippy-core/src/packet/ipv4.rs
index 34b2e5731..223f902b6 100644
--- a/src/tracing/packet/ipv4.rs
+++ b/crates/trippy-core/src/packet/ipv4.rs
@@ -1,6 +1,6 @@
-use crate::tracing::packet::buffer::Buffer;
-use crate::tracing::packet::error::{PacketError, PacketResult};
-use crate::tracing::packet::{fmt_payload, IpProtocol};
+use crate::packet::buffer::Buffer;
+use crate::packet::error::{PacketError, PacketResult};
+use crate::packet::{fmt_payload, IpProtocol};
 use std::fmt::{Debug, Formatter};
 use std::net::Ipv4Addr;
 
diff --git a/src/tracing/packet/ipv6.rs b/crates/trippy-core/src/packet/ipv6.rs
similarity index 98%
rename from src/tracing/packet/ipv6.rs
rename to crates/trippy-core/src/packet/ipv6.rs
index 7b48350d4..337b98156 100644
--- a/src/tracing/packet/ipv6.rs
+++ b/crates/trippy-core/src/packet/ipv6.rs
@@ -1,6 +1,6 @@
-use crate::tracing::packet::buffer::Buffer;
-use crate::tracing::packet::error::{PacketError, PacketResult};
-use crate::tracing::packet::{fmt_payload, IpProtocol};
+use crate::packet::buffer::Buffer;
+use crate::packet::error::{PacketError, PacketResult};
+use crate::packet::{fmt_payload, IpProtocol};
 use std::fmt::{Debug, Formatter};
 use std::net::Ipv6Addr;
 
diff --git a/src/tracing/packet/tcp.rs b/crates/trippy-core/src/packet/tcp.rs
similarity index 99%
rename from src/tracing/packet/tcp.rs
rename to crates/trippy-core/src/packet/tcp.rs
index f2d5f49ae..48d723bd0 100644
--- a/src/tracing/packet/tcp.rs
+++ b/crates/trippy-core/src/packet/tcp.rs
@@ -1,6 +1,6 @@
-use crate::tracing::packet::buffer::Buffer;
-use crate::tracing::packet::error::{PacketError, PacketResult};
-use crate::tracing::packet::fmt_payload;
+use crate::packet::buffer::Buffer;
+use crate::packet::error::{PacketError, PacketResult};
+use crate::packet::fmt_payload;
 use std::fmt::{Debug, Formatter};
 
 const SOURCE_PORT_OFFSET: usize = 0;
diff --git a/src/tracing/packet/udp.rs b/crates/trippy-core/src/packet/udp.rs
similarity index 98%
rename from src/tracing/packet/udp.rs
rename to crates/trippy-core/src/packet/udp.rs
index 86d7e22d6..187ad9f8d 100644
--- a/src/tracing/packet/udp.rs
+++ b/crates/trippy-core/src/packet/udp.rs
@@ -1,6 +1,6 @@
-use crate::tracing::packet::buffer::Buffer;
-use crate::tracing::packet::error::{PacketError, PacketResult};
-use crate::tracing::packet::fmt_payload;
+use crate::packet::buffer::Buffer;
+use crate::packet::error::{PacketError, PacketResult};
+use crate::packet::fmt_payload;
 use std::fmt::{Debug, Formatter};
 
 const SOURCE_PORT_OFFSET: usize = 0;
diff --git a/src/tracing/probe.rs b/crates/trippy-core/src/probe.rs
similarity index 99%
rename from src/tracing/probe.rs
rename to crates/trippy-core/src/probe.rs
index c406e2b87..d69d1b019 100644
--- a/src/tracing/probe.rs
+++ b/crates/trippy-core/src/probe.rs
@@ -1,4 +1,4 @@
-use crate::tracing::types::{Flags, Port, Round, Sequence, TimeToLive, TraceId};
+use crate::types::{Flags, Port, Round, Sequence, TimeToLive, TraceId};
 use std::net::IpAddr;
 use std::time::SystemTime;
 
diff --git a/src/tracing/tracer.rs b/crates/trippy-core/src/tracer.rs
similarity index 98%
rename from src/tracing/tracer.rs
rename to crates/trippy-core/src/tracer.rs
index 2ee688eb2..2d6643920 100644
--- a/src/tracing/tracer.rs
+++ b/crates/trippy-core/src/tracer.rs
@@ -1,13 +1,13 @@
 use self::state::TracerState;
-use crate::tracing::error::{TraceResult, TracerError};
-use crate::tracing::net::Network;
-use crate::tracing::probe::{
+use crate::error::{TraceResult, TracerError};
+use crate::net::Network;
+use crate::probe::{
     ProbeResponse, ProbeResponseData, ProbeResponseSeq, ProbeResponseSeqIcmp, ProbeResponseSeqTcp,
     ProbeResponseSeqUdp, ProbeState,
 };
-use crate::tracing::types::{Sequence, TimeToLive, TraceId};
-use crate::tracing::Config;
-use crate::tracing::{MultipathStrategy, PortDirection, Protocol};
+use crate::types::{Sequence, TimeToLive, TraceId};
+use crate::Config;
+use crate::{MultipathStrategy, PortDirection, Protocol};
 use std::net::IpAddr;
 use std::time::{Duration, SystemTime};
 use tracing::instrument;
@@ -345,9 +345,9 @@ impl<F: Fn(&TracerRound<'_>)> Tracer<F> {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::tracing::net::MockNetwork;
-    use crate::tracing::probe::IcmpPacketCode;
-    use crate::tracing::{MaxRounds, Port};
+    use crate::net::MockNetwork;
+    use crate::probe::IcmpPacketCode;
+    use crate::{MaxRounds, Port};
     use std::net::Ipv4Addr;
     use std::num::NonZeroUsize;
 
@@ -416,10 +416,10 @@ mod tests {
 /// This is contained within a submodule to ensure that mutations are only performed via methods on
 /// the `TracerState` struct.
 mod state {
-    use crate::tracing::constants::MAX_SEQUENCE_PER_ROUND;
-    use crate::tracing::probe::{Extensions, IcmpPacketCode, IcmpPacketType, Probe, ProbeState};
-    use crate::tracing::types::{MaxRounds, Port, Round, Sequence, TimeToLive, TraceId};
-    use crate::tracing::{Config, Flags, MultipathStrategy, PortDirection, Protocol};
+    use crate::constants::MAX_SEQUENCE_PER_ROUND;
+    use crate::probe::{Extensions, IcmpPacketCode, IcmpPacketType, Probe, ProbeState};
+    use crate::types::{MaxRounds, Port, Round, Sequence, TimeToLive, TraceId};
+    use crate::{Config, Flags, MultipathStrategy, PortDirection, Protocol};
     use std::array::from_fn;
     use std::net::IpAddr;
     use std::time::SystemTime;
@@ -907,8 +907,8 @@ mod state {
     #[cfg(test)]
     mod tests {
         use super::*;
-        use crate::tracing::probe::IcmpPacketType;
-        use crate::tracing::types::MaxInflight;
+        use crate::probe::IcmpPacketType;
+        use crate::types::MaxInflight;
         use rand::Rng;
         use std::net::{IpAddr, Ipv4Addr};
         use std::time::Duration;
diff --git a/src/tracing/types.rs b/crates/trippy-core/src/types.rs
similarity index 100%
rename from src/tracing/types.rs
rename to crates/trippy-core/src/types.rs
diff --git a/crates/trippy-dns/Cargo.toml b/crates/trippy-dns/Cargo.toml
new file mode 100644
index 000000000..65ea95acd
--- /dev/null
+++ b/crates/trippy-dns/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "trippy-dns"
+description = "A lazy DNS resolver 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]
+thiserror.workspace = true
+itertools.workspace = true
+crossbeam = "0.8.4"
+dns-lookup = "2.0.4"
+hickory-resolver = "0.24.1"
+parking_lot.workspace = true
+
+[dev-dependencies]
+anyhow.workspace = true
\ No newline at end of file
diff --git a/src/dns/lazy_resolver.rs b/crates/trippy-dns/src/lazy_resolver.rs
similarity index 88%
rename from src/dns/lazy_resolver.rs
rename to crates/trippy-dns/src/lazy_resolver.rs
index 366c36522..95991c822 100644
--- a/src/dns/lazy_resolver.rs
+++ b/crates/trippy-dns/src/lazy_resolver.rs
@@ -1,4 +1,4 @@
-use crate::dns::resolver::{DnsEntry, ResolvedIpAddrs, Resolver, Result};
+use crate::resolver::{DnsEntry, ResolvedIpAddrs, Resolver, Result};
 use std::fmt::{Display, Formatter};
 use std::net::IpAddr;
 use std::rc::Rc;
@@ -118,14 +118,12 @@ impl Resolver for DnsResolver {
 
 /// Private impl of resolver.
 mod inner {
-    use crate::dns::lazy_resolver::{Config, IpAddrFamily, ResolveMethod};
-    use crate::dns::resolver::{
-        AsInfo, DnsEntry, Error, Resolved, ResolvedIpAddrs, Result, Unresolved,
-    };
-    use anyhow::anyhow;
+    use super::{Config, IpAddrFamily, ResolveMethod};
+    use crate::resolver::{AsInfo, DnsEntry, Error, Resolved, ResolvedIpAddrs, Result, Unresolved};
     use crossbeam::channel::{bounded, Receiver, Sender};
     use hickory_resolver::config::{LookupIpStrategy, ResolverConfig, ResolverOpts};
-    use hickory_resolver::error::ResolveErrorKind;
+    use hickory_resolver::error::{ResolveError, ResolveErrorKind};
+    use hickory_resolver::proto::error::ProtoError;
     use hickory_resolver::proto::rr::RecordType;
     use hickory_resolver::{Name, Resolver};
     use itertools::{Either, Itertools};
@@ -388,7 +386,7 @@ mod inner {
     }
 
     /// Lookup up `AsInfo` for an `IpAddr` address.
-    fn lookup_asinfo(resolver: &Arc<Resolver>, addr: IpAddr) -> anyhow::Result<AsInfo> {
+    fn lookup_asinfo(resolver: &Arc<Resolver>, addr: IpAddr) -> Result<AsInfo> {
         let origin_query_txt = match addr {
             IpAddr::V4(addr) => query_asn_ipv4(resolver, addr)?,
             IpAddr::V6(addr) => query_asn_ipv6(resolver, addr)?,
@@ -407,23 +405,25 @@ mod inner {
     }
 
     /// Perform the `origin` query.
-    fn query_asn_ipv4(resolver: &Arc<Resolver>, addr: Ipv4Addr) -> anyhow::Result<String> {
+    fn query_asn_ipv4(resolver: &Arc<Resolver>, addr: Ipv4Addr) -> Result<String> {
         let query = format!(
             "{}.origin.asn.cymru.com.",
             addr.octets().iter().rev().join(".")
         );
-        let name = Name::from_str(query.as_str())?;
-        let response = resolver.lookup(name, RecordType::TXT)?;
+        let name = Name::from_str(query.as_str()).map_err(proto_error)?;
+        let response = resolver
+            .lookup(name, RecordType::TXT)
+            .map_err(resolve_error)?;
         let data = response
             .iter()
             .next()
-            .ok_or_else(|| anyhow!("asn origin query"))?;
-        let bytes = data.as_txt().ok_or_else(|| anyhow!("asn origin query"))?;
+            .ok_or_else(|| Error::QueryAsnOriginFailed)?;
+        let bytes = data.as_txt().ok_or_else(|| Error::QueryAsnOriginFailed)?;
         Ok(bytes.to_string())
     }
 
     /// Perform the `origin` query.
-    fn query_asn_ipv6(resolver: &Arc<Resolver>, addr: Ipv6Addr) -> anyhow::Result<String> {
+    fn query_asn_ipv6(resolver: &Arc<Resolver>, addr: Ipv6Addr) -> Result<String> {
         let query = format!(
             "{:x}.origin6.asn.cymru.com.",
             addr.octets()
@@ -432,23 +432,30 @@ mod inner {
                 .flat_map(|o| [o & 0x0F, (o & 0xF0) >> 4])
                 .format(".")
         );
-        let name = Name::from_str(query.as_str())?;
-        let response = resolver.lookup(name, RecordType::TXT)?;
+        let name = Name::from_str(query.as_str()).map_err(proto_error)?;
+        let response = resolver
+            .lookup(name, RecordType::TXT)
+            .map_err(resolve_error)?;
         let data = response
             .iter()
             .next()
-            .ok_or_else(|| anyhow!("asn origin6 query"))?;
-        let bytes = data.as_txt().ok_or_else(|| anyhow!("asn origin6 query"))?;
+            .ok_or_else(|| Error::QueryAsnOriginFailed)?;
+        let bytes = data.as_txt().ok_or_else(|| Error::QueryAsnOriginFailed)?;
         Ok(bytes.to_string())
     }
 
     /// Perform the `asn` query.
-    fn query_asn_name(resolver: &Arc<Resolver>, asn: &str) -> anyhow::Result<String> {
+    fn query_asn_name(resolver: &Arc<Resolver>, asn: &str) -> Result<String> {
         let query = format!("AS{asn}.asn.cymru.com.");
-        let name = Name::from_str(query.as_str())?;
-        let response = resolver.lookup(name, RecordType::TXT)?;
-        let data = response.iter().next().ok_or_else(|| anyhow!("asn query"))?;
-        let bytes = data.as_txt().ok_or_else(|| anyhow!("asn query"))?;
+        let name = Name::from_str(query.as_str()).map_err(proto_error)?;
+        let response = resolver
+            .lookup(name, RecordType::TXT)
+            .map_err(resolve_error)?;
+        let data = response
+            .iter()
+            .next()
+            .ok_or_else(|| Error::QueryAsnFailed)?;
+        let bytes = data.as_txt().ok_or_else(|| Error::QueryAsnFailed)?;
         Ok(bytes.to_string())
     }
 
@@ -459,12 +466,11 @@ mod inner {
     ///      `12301 | 81.0.100.0/22 | HU | ripencc | 2001-12-06`
     ///
     /// From this we extract all fields.
-    fn parse_origin_query_txt(origin_query_txt: &str) -> anyhow::Result<AsInfo> {
+    fn parse_origin_query_txt(origin_query_txt: &str) -> Result<AsInfo> {
         if origin_query_txt.chars().filter(|c| *c == '|').count() != 4 {
-            return Err(anyhow!(
-                "failed to parse AS origin txt: {}",
-                origin_query_txt
-            ));
+            return Err(Error::ParseOriginQueryFailed(String::from(
+                origin_query_txt,
+            )));
         }
         let mut split = origin_query_txt.split('|');
         let asn = split.next().unwrap_or_default().trim().to_string();
@@ -489,11 +495,21 @@ mod inner {
     ///      `12301 | HU | ripencc | 1999-02-25 | INVITECH, HU`
     ///
     /// From this we extract the 4th field (name, `INVITECH, HU` in this example)
-    fn parse_asn_query_txt(asn_query_txt: &str) -> anyhow::Result<String> {
+    fn parse_asn_query_txt(asn_query_txt: &str) -> Result<String> {
         if asn_query_txt.chars().filter(|c| *c == '|').count() != 4 {
-            return Err(anyhow!("failed to parse AS origin txt: {}", asn_query_txt));
+            return Err(Error::ParseAsnQueryFailed(String::from(asn_query_txt)));
         }
         let mut split = asn_query_txt.split('|');
         Ok(split.nth(4).unwrap_or_default().trim().to_string())
     }
+
+    /// Convert a `ResolveError` to an `Error::LookupFailed`.
+    fn resolve_error(err: ResolveError) -> Error {
+        Error::LookupFailed(Box::new(err))
+    }
+
+    /// Convert a `ProtoError` to an `Error::LookupFailed`.
+    fn proto_error(err: ProtoError) -> Error {
+        Error::LookupFailed(Box::new(err))
+    }
 }
diff --git a/crates/trippy-dns/src/lib.rs b/crates/trippy-dns/src/lib.rs
new file mode 100644
index 000000000..5fb7d5a1d
--- /dev/null
+++ b/crates/trippy-dns/src/lib.rs
@@ -0,0 +1,83 @@
+//! This crate provides a cheaply cloneable, non-blocking, caching, forward
+//! and reverse DNS resolver which support the ability to lookup Autonomous
+//! System (AS) information.
+//!
+//! # Example
+//!
+//! The following example perform a reverse DNS lookup and loop until it is
+//! resolved or fails.  The lookup uses the Cloudflare 1.1.1.1 public DNS
+//! service.
+//!
+//! Note that only a single reverse DNS lookup is performed (lazily) regardless
+//! of how often the lookup is performed, unless the previous lookup failed
+//! with `DnsEntry::Timeout(_)`.
+//!
+//! ```no_run
+//! # fn main() -> anyhow::Result<()> {
+//! # use std::net::IpAddr;
+//! # use std::str::FromStr;
+//! # use std::thread::sleep;
+//! # use std::time::Duration;
+//! use trippy_dns::{
+//!     Config, DnsEntry, DnsResolver, IpAddrFamily, ResolveMethod, Resolved, Resolver, Unresolved,
+//! };
+//!
+//! let config = Config::new(
+//!     ResolveMethod::Cloudflare,
+//!     IpAddrFamily::Ipv4Only,
+//!     Duration::from_secs(5),
+//! );
+//! let resolver = DnsResolver::start(config)?;
+//! let addr = IpAddr::from_str("1.1.1.1")?;
+//! loop {
+//!     let entry = resolver.lazy_reverse_lookup_with_asinfo(addr);
+//!     match entry {
+//!         DnsEntry::Pending(ip) => {
+//!             println!("lookup of {ip} is pending, sleeping for 1 sec");
+//!             sleep(Duration::from_secs(1));
+//!         }
+//!         DnsEntry::Resolved(Resolved::Normal(ip, addrs)) => {
+//!             println!("lookup of {ip} resolved to {addrs:?}");
+//!             return Ok(());
+//!         }
+//!         DnsEntry::Resolved(Resolved::WithAsInfo(ip, addrs, as_info)) => {
+//!             println!("lookup of {ip} resolved to {addrs:?} with AS information {as_info:?}");
+//!             return Ok(());
+//!         }
+//!         DnsEntry::NotFound(Unresolved::Normal(ip)) => {
+//!             println!("lookup of {ip} did not match any records");
+//!             return Ok(());
+//!         }
+//!         DnsEntry::NotFound(Unresolved::WithAsInfo(ip, as_info)) => {
+//!             println!(
+//!                 "lookup of {ip} did not match any records with AS information {as_info:?}"
+//!             );
+//!             return Ok(());
+//!         }
+//!         DnsEntry::Timeout(ip) => {
+//!             println!("lookup of {ip} timed out");
+//!             return Ok(());
+//!         }
+//!         DnsEntry::Failed(ip) => {
+//!             println!("lookup of {ip} failed");
+//!             return Ok(());
+//!         }
+//!     }
+//! }
+//! # Ok(())
+//! # }
+//! ```
+#![warn(clippy::all, clippy::pedantic, clippy::nursery, rust_2018_idioms)]
+#![allow(
+    clippy::module_name_repetitions,
+    clippy::missing_const_for_fn,
+    clippy::missing_errors_doc,
+    clippy::option_if_let_else
+)]
+#![forbid(unsafe_code)]
+
+mod lazy_resolver;
+mod resolver;
+
+pub use lazy_resolver::{Config, DnsResolver, IpAddrFamily, ResolveMethod};
+pub use resolver::{AsInfo, DnsEntry, Error, Resolved, Resolver, Result, Unresolved};
diff --git a/src/dns/resolver.rs b/crates/trippy-dns/src/resolver.rs
similarity index 94%
rename from src/dns/resolver.rs
rename to crates/trippy-dns/src/resolver.rs
index b0acc36f4..020579180 100644
--- a/src/dns/resolver.rs
+++ b/crates/trippy-dns/src/resolver.rs
@@ -49,6 +49,14 @@ pub type Result<T> = std::result::Result<T, Error>;
 pub enum Error {
     #[error("DNS lookup failed")]
     LookupFailed(Box<dyn std::error::Error>),
+    #[error("ASN origin query failed")]
+    QueryAsnOriginFailed,
+    #[error("ASN query failed")]
+    QueryAsnFailed,
+    #[error("origin query txt parse failed: {0}")]
+    ParseOriginQueryFailed(String),
+    #[error("asn query txt parse failed: {0}")]
+    ParseAsnQueryFailed(String),
 }
 
 /// The output of a successful DNS lookup.
diff --git a/crates/trippy-privilege/Cargo.toml b/crates/trippy-privilege/Cargo.toml
new file mode 100644
index 000000000..121f72743
--- /dev/null
+++ b/crates/trippy-privilege/Cargo.toml
@@ -0,0 +1,33 @@
+[package]
+name = "trippy-privilege"
+description = "Discover platform privileges"
+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]
+thiserror.workspace = true
+
+[target.'cfg(target_os = "linux")'.dependencies]
+caps = "0.5.5"
+
+[target.'cfg(unix)'.dependencies]
+nix = { version = "0.28.0", default-features = false, features = [ "user" ] }
+
+[target.'cfg(windows)'.dependencies]
+windows-sys = { version = "0.52.0", features = [
+    "Win32_Foundation",
+    "Win32_System_Threading",
+] }
+paste = "1.0.15"
+
+[dev-dependencies]
+anyhow.workspace = true
\ No newline at end of file
diff --git a/src/platform.rs b/crates/trippy-privilege/src/lib.rs
similarity index 54%
rename from src/platform.rs
rename to crates/trippy-privilege/src/lib.rs
index c47456094..ed3b7f479 100644
--- a/src/platform.rs
+++ b/crates/trippy-privilege/src/lib.rs
@@ -1,37 +1,146 @@
-/// Runtime information about the platform and environment.
+//! Discover platform privileges.
+//!
+//! A cross-platform library to discover and manage platform privileges needed
+//! for sending ICMP packets via RAW and `IPPROTO_ICMP` sockets.
+//!
+//! [`Privilege::acquire_privileges`]:
+//!
+//! - On Linux we check if `CAP_NET_RAW` is in the permitted set and if so raise
+//! it to the effective set
+//! - On other Unix platforms this is a no-op
+//! - On Windows this is a no-op
+//!
+//! [`Privilege::has_privileges`] (obtained via [`Privilege::discover`]):
+//!
+//! - On Linux we check if `CAP_NET_RAW` is in the effective set
+//! - On other Unix platforms we check that the effective user is root
+//! - On Windows we check if the current process has an elevated token
+//!
+//! [`Privilege::needs_privileges`] (obtained via [`Privilege::discover`]):
+//!
+//! - On macOS we do not always need privileges to send ICMP packets as we
+//! can use `IPPROTO_ICMP` sockets with the `IP_HDRINCL` socket option.
+//! - On Linux we always need privileges to send ICMP packets even though it
+//! supports the `IPPROTO_ICMP` socket type but not the `IP_HDRINCL` socket option
+//! - On Windows we always need privileges to send ICMP packets
+//!
+//! [`Privilege::drop_privileges`]:
+//!
+//! - On Linux we clear the effective set
+//! - On other Unix platforms this is a no-op
+//! - On Windows this is a no-op
+//!
+//! # Examples
+//!
+//! Acquire the required privileges if we can:
+//!
+//! ```rust
+//! # fn main() -> anyhow::Result<()> {
+//! # use trippy_privilege::Privilege;
+//! Privilege::acquire_privileges()?;
+//! # Ok(())
+//! # }
+//! ```
+//!
+//! Discover the current privileges:
+//!
+//! ```rust
+//! # fn main() -> anyhow::Result<()> {
+//! # use trippy_privilege::Privilege;
+//! let privilege = Privilege::discover()?;
+//! if privilege.has_privileges() {
+//!     println!("You have the required privileges for raw sockets");
+//! } else {
+//!     println!("You do not have the required privileges for raw sockets");
+//! }
+//! if privilege.needs_privileges() {
+//!     println!("You always need privileges to send ICMP packets.");
+//! } else {
+//!     println!("You do not always need privileges to send ICMP packets.");
+//! }
+//! # Ok(())
+//! # }
+//! ```
+//!
+//! Drop all privileges:
+//!
+//! ```rust
+//! # fn main() -> anyhow::Result<()> {
+//! # use trippy_privilege::Privilege;
+//! Privilege::drop_privileges()?;
+//! # Ok(())
+//! # }
+//! ```
+#![warn(clippy::all, clippy::pedantic, clippy::nursery, rust_2018_idioms)]
+#![allow(clippy::missing_const_for_fn, clippy::missing_errors_doc)]
+#![deny(unsafe_code)]
+
+/// A privilege error result.
+pub type Result<T> = std::result::Result<T, Error>;
+
+/// A privilege error.
+#[derive(thiserror::Error, Debug)]
+pub enum Error {
+    #[cfg(target_os = "linux")]
+    #[error("caps error: {0}")]
+    CapsError(#[from] caps::errors::CapsError),
+    #[cfg(windows)]
+    #[error("OpenProcessToken failed")]
+    OpenProcessTokenError,
+    #[cfg(windows)]
+    #[error("GetTokenInformation failed")]
+    GetTokenInformationError,
+}
+
+/// Runtime platform privilege information.
 #[derive(Debug)]
-pub struct Platform {
-    /// The platform process id.
-    pub pid: u16,
-    /// Are we running with the privileges required for raw sockets?
-    pub has_privileges: bool,
-    /// Does our platform always need privileges for `ICMP`?
-    ///
-    /// Specifically, each platform requires privileges unless it supports the `IPPROTO_ICMP` socket type which _also_
-    /// allows the `IP_HDRINCL` socket option to be set.
-    pub needs_privileges: bool,
+pub struct Privilege {
+    has_privileges: bool,
+    needs_privileges: bool,
 }
 
-impl Platform {
-    /// Discover information about the platform and environment.
-    pub fn discover() -> anyhow::Result<Self> {
-        let pid = u16::try_from(std::process::id() % u32::from(u16::MAX))?;
-        let has_privileges = Self::has_privileges()?;
-        let needs_privileges = Self::needs_privileges();
+impl Privilege {
+    /// Discover information about the platform privileges.
+    pub fn discover() -> Result<Self> {
+        let has_privileges = Self::check_has_privileges()?;
+        let needs_privileges = Self::check_needs_privileges();
         Ok(Self {
-            pid,
             has_privileges,
             needs_privileges,
         })
     }
 
+    /// Create a new Privilege instance.
+    #[must_use]
+    pub fn new(has_privileges: bool, needs_privileges: bool) -> Self {
+        Self {
+            has_privileges,
+            needs_privileges,
+        }
+    }
+
+    /// Are we running with the privileges required for raw sockets?
+    #[must_use]
+    pub fn has_privileges(&self) -> bool {
+        self.has_privileges
+    }
+
+    /// Does our platform always need privileges for `ICMP`?
+    ///
+    /// Specifically, each platform requires privileges unless it supports the `IPPROTO_ICMP` socket type which _also_
+    /// allows the `IP_HDRINCL` socket option to be set.
+    #[must_use]
+    pub fn needs_privileges(&self) -> bool {
+        self.needs_privileges
+    }
+
     // Linux
 
     #[cfg(target_os = "linux")]
     /// Acquire privileges, if possible.
     ///
     /// Check if `CAP_NET_RAW` is in the permitted set and if so raise it to the effective set.
-    pub fn acquire_privileges() -> anyhow::Result<()> {
+    pub fn acquire_privileges() -> Result<()> {
         if caps::has_cap(None, caps::CapSet::Permitted, caps::Capability::CAP_NET_RAW)? {
             caps::raise(None, caps::CapSet::Effective, caps::Capability::CAP_NET_RAW)?;
         }
@@ -42,7 +151,7 @@ impl Platform {
     /// Do we have the required privileges?
     ///
     /// Check if `CAP_NET_RAW` is in the effective set.
-    pub fn has_privileges() -> anyhow::Result<bool> {
+    fn check_has_privileges() -> Result<bool> {
         Ok(caps::has_cap(
             None,
             caps::CapSet::Effective,
@@ -54,7 +163,7 @@ impl Platform {
     /// Drop all privileges.
     ///
     /// Clears the effective set.
-    pub fn drop_privileges() -> anyhow::Result<()> {
+    pub fn drop_privileges() -> Result<()> {
         caps::clear(None, caps::CapSet::Effective)?;
         Ok(())
     }
@@ -66,7 +175,7 @@ impl Platform {
     /// Acquire privileges, if possible.
     ///
     /// This is a no-op on non-Linux unix systems.
-    pub fn acquire_privileges() -> anyhow::Result<()> {
+    pub fn acquire_privileges() -> Result<()> {
         Ok(())
     }
 
@@ -75,7 +184,7 @@ impl Platform {
     /// Do we have the required privileges?
     ///
     /// Checks if the effective user is root.
-    pub fn has_privileges() -> anyhow::Result<bool> {
+    fn check_has_privileges() -> Result<bool> {
         Ok(nix::unistd::Uid::effective().is_root())
     }
 
@@ -84,7 +193,7 @@ impl Platform {
     /// Drop all privileges.
     ///
     /// This is a no-op on non-Linux unix systems.
-    pub fn drop_privileges() -> anyhow::Result<()> {
+    pub fn drop_privileges() -> Result<()> {
         Ok(())
     }
 
@@ -97,7 +206,7 @@ impl Platform {
     /// option and is therefore not supported.  This may be supported in the future.
     ///
     /// `NetBSD`, `OpenBSD` and `FreeBSD` do not support `IPPROTO_ICMP`.
-    fn needs_privileges() -> bool {
+    fn check_needs_privileges() -> bool {
         true
     }
 
@@ -107,7 +216,7 @@ impl Platform {
     /// Does the platform always require privileges?
     ///
     /// `macOS` supports both privileged and unprivileged modes.
-    fn needs_privileges() -> bool {
+    fn check_needs_privileges() -> bool {
         false
     }
 
@@ -118,7 +227,7 @@ impl Platform {
     /// Acquire privileges, if possible.
     ///
     /// This is a no-op on `Windows`.
-    pub fn acquire_privileges() -> anyhow::Result<()> {
+    pub fn acquire_privileges() -> Result<()> {
         Ok(())
     }
 
@@ -127,7 +236,7 @@ impl Platform {
     /// Do we have the required privileges?
     ///
     /// Check if the current process has an elevated token.
-    pub fn has_privileges() -> anyhow::Result<bool> {
+    fn check_has_privileges() -> Result<bool> {
         macro_rules! syscall {
             ($p: path, $fn: ident ( $($arg: expr),* $(,)* ) ) => {{
                 #[allow(unsafe_code)]
@@ -142,7 +251,7 @@ impl Platform {
 
         impl Privileged {
             /// Create a new `ElevationChecker` for the current process.
-            pub fn current_process() -> anyhow::Result<Self> {
+            pub fn current_process() -> Result<Self> {
                 use windows_sys::Win32::Security::TOKEN_QUERY;
                 let mut handle: windows_sys::Win32::Foundation::HANDLE = 0;
                 let current_process = syscall!(System::Threading, GetCurrentProcess());
@@ -151,18 +260,19 @@ impl Platform {
                     OpenProcessToken(current_process, TOKEN_QUERY, std::ptr::addr_of_mut!(handle))
                 );
                 if res == 0 {
-                    Err(anyhow::anyhow!("OpenProcessToken failed"))
+                    Err(Error::OpenProcessTokenError)
                 } else {
                     Ok(Self { handle })
                 }
             }
 
             /// Check if the current process has elevated privileged.
-            pub fn is_elevated(&self) -> anyhow::Result<bool> {
+            pub fn is_elevated(&self) -> Result<bool> {
                 use windows_sys::Win32::Security::TokenElevation;
                 use windows_sys::Win32::Security::TOKEN_ELEVATION;
                 let mut elevation = TOKEN_ELEVATION { TokenIsElevated: 0 };
-                let size = std::mem::size_of::<TOKEN_ELEVATION>();
+                #[allow(clippy::cast_possible_truncation)]
+                let size = std::mem::size_of::<TOKEN_ELEVATION>() as u32;
                 let mut ret_size = 0u32;
                 let ret = syscall!(
                     Security,
@@ -170,12 +280,12 @@ impl Platform {
                         self.handle,
                         TokenElevation,
                         std::ptr::addr_of_mut!(elevation).cast(),
-                        size as u32,
+                        size,
                         std::ptr::addr_of_mut!(ret_size),
                     )
                 );
                 if ret == 0 {
-                    Err(anyhow::anyhow!("GetTokenInformation failed"))
+                    Err(Error::GetTokenInformationError)
                 } else {
                     Ok(elevation.TokenIsElevated != 0)
                 }
@@ -197,7 +307,7 @@ impl Platform {
     /// Drop all capabilities.
     ///
     /// This is a no-op on `Windows`.
-    pub fn drop_privileges() -> anyhow::Result<()> {
+    pub fn drop_privileges() -> Result<()> {
         Ok(())
     }
 
@@ -205,16 +315,7 @@ impl Platform {
     /// Does the platform always require privileges?
     ///
     /// Privileges are always required on `Windows`.
-    fn needs_privileges() -> bool {
+    fn check_needs_privileges() -> bool {
         true
     }
-
-    #[cfg(test)]
-    pub fn dummy_for_test() -> Self {
-        Self {
-            pid: 0,
-            has_privileges: true,
-            needs_privileges: false,
-        }
-    }
 }
diff --git a/crates/trippy/Cargo.toml b/crates/trippy/Cargo.toml
new file mode 100644
index 000000000..ea59d836c
--- /dev/null
+++ b/crates/trippy/Cargo.toml
@@ -0,0 +1,61 @@
+[package]
+name = "trippy"
+description = "A network diagnostic tool"
+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 = ["cli", "tui", "traceroute", "ping", "icmp"]
+categories = ["command-line-utilities", "network-programming"]
+
+[[bin]]
+bench = false
+path = "src/main.rs"
+name = "trip"
+
+[dependencies]
+trippy-core = { version = "0.11.0-dev", path = "../trippy-core" }
+trippy-privilege = { version = "0.11.0-dev", path = "../trippy-privilege" }
+trippy-dns = { version = "0.11.0-dev", path = "../trippy-dns" }
+thiserror.workspace = true
+anyhow.workspace = true
+itertools.workspace = true
+tracing.workspace = true
+serde.workspace = true
+derive_more.workspace = true
+parking_lot.workspace = true
+clap = { version = "4.4.0",  default-features = false, features = [ "cargo", "derive", "wrap_help", "usage", "unstable-styles" ] }
+clap_complete = "4.4.9"
+humantime = "2.1.0"
+ratatui = "0.26.2"
+crossterm = { version = "0.27.0",  default-features = false, features = [ "events", "windows" ] }
+chrono = { version = "0.4.38", default-features = false, features = [ "clock" ] }
+serde_json = { version = "1.0.117", default-features = false }
+comfy-table = { version = "7.1.0", default-features = false }
+strum = { version = "0.26.2", default-features = false, features = [ "std", "derive" ] }
+etcetera = "0.8.0"
+toml = { version = "0.8.13", default-features = false, features = [ "parse" ] }
+indexmap = { version = "2.2.6", default-features = false }
+maxminddb = "0.24.0"
+tracing-subscriber = { version = "0.3.18", default-features = false, features = [ "json", "env-filter" ] }
+tracing-chrome = "0.7.2"
+petgraph = "0.6.5"
+csv = "1.3.0"
+serde_with = "3.8.1"
+encoding_rs_io = "0.1.7"
+clap_mangen = "0.2.20"
+
+[dev-dependencies]
+test-case.workspace = true
+serde_yaml.workspace = true
+pretty_assertions = "1.4.0"
+
+[package.metadata.generate-rpm]
+assets = [
+  { source = "target/release/trip", dest = "/usr/bin/trip", mode = "755" },
+]
diff --git a/src/backend.rs b/crates/trippy/src/backend.rs
similarity index 91%
rename from src/backend.rs
rename to crates/trippy/src/backend.rs
index 73b3e3858..4647eb25f 100644
--- a/src/backend.rs
+++ b/crates/trippy/src/backend.rs
@@ -1,10 +1,10 @@
-use crate::platform::Platform;
 use parking_lot::RwLock;
 use std::fmt::Debug;
 use std::sync::Arc;
 use trace::Trace;
 use tracing::instrument;
-use trippy::tracing::{ChannelConfig, Config, SocketImpl, Tracer, TracerChannel};
+use trippy_core::{ChannelConfig, Config, SocketImpl, Tracer, TracerChannel};
+use trippy_privilege::Privilege;
 
 pub mod flows;
 pub mod trace;
@@ -48,7 +48,7 @@ impl Backend {
                 td.write().set_error(Some(err.to_string()));
                 err
             })?;
-        Platform::drop_privileges()?;
+        Privilege::drop_privileges()?;
         let tracer = Tracer::new(&self.tracer_config, move |round| {
             self.trace.write().update_from_round(round);
         });
diff --git a/src/backend/flows.rs b/crates/trippy/src/backend/flows.rs
similarity index 100%
rename from src/backend/flows.rs
rename to crates/trippy/src/backend/flows.rs
diff --git a/src/backend/trace.rs b/crates/trippy/src/backend/trace.rs
similarity index 99%
rename from src/backend/trace.rs
rename to crates/trippy/src/backend/trace.rs
index ae473e988..48583791b 100644
--- a/src/backend/trace.rs
+++ b/crates/trippy/src/backend/trace.rs
@@ -5,7 +5,7 @@ use std::collections::HashMap;
 use std::iter::once;
 use std::net::IpAddr;
 use std::time::Duration;
-use trippy::tracing::{Extensions, IcmpPacketType, ProbeState, Round, TimeToLive, TracerRound};
+use trippy_core::{Extensions, IcmpPacketType, ProbeState, Round, TimeToLive, TracerRound};
 
 /// The state of all hops in a trace.
 #[derive(Debug, Clone)]
@@ -488,7 +488,7 @@ mod tests {
     use std::str::FromStr;
     use std::time::SystemTime;
     use test_case::test_case;
-    use trippy::tracing::{
+    use trippy_core::{
         CompletionReason, Flags, IcmpPacketType, Port, Probe, ProbeComplete, ProbeState, Sequence,
         TimeToLive, TraceId,
     };
diff --git a/src/config.rs b/crates/trippy/src/config.rs
similarity index 98%
rename from src/config.rs
rename to crates/trippy/src/config.rs
index 87b7666a8..8b17e7bd1 100644
--- a/src/config.rs
+++ b/crates/trippy/src/config.rs
@@ -1,4 +1,3 @@
-use crate::platform::Platform;
 use anyhow::anyhow;
 use clap::ValueEnum;
 use clap_complete::Shell;
@@ -10,10 +9,10 @@ use std::collections::HashMap;
 use std::net::IpAddr;
 use std::str::FromStr;
 use std::time::Duration;
-use trippy::dns::{IpAddrFamily, ResolveMethod};
-use trippy::tracing::{
+use trippy_core::{
     defaults, IcmpExtensionParseMode, MultipathStrategy, PortDirection, PrivilegeMode, Protocol,
 };
+use trippy_dns::{IpAddrFamily, ResolveMethod};
 
 mod binding;
 mod cmd;
@@ -27,6 +26,7 @@ pub use cmd::Args;
 pub use columns::{TuiColumn, TuiColumns};
 pub use constants::MAX_HOPS;
 pub use theme::{TuiColor, TuiTheme, TuiThemeItem};
+use trippy_privilege::Privilege;
 
 /// The tool mode.
 #[derive(Debug, Copy, Clone, Eq, PartialEq, ValueEnum, Deserialize)]
@@ -260,7 +260,7 @@ pub enum TrippyAction {
 }
 
 impl TrippyAction {
-    pub fn from(args: Args, platform: &Platform) -> anyhow::Result<Self> {
+    pub fn from(args: Args, privilege: &Privilege, pid: u16) -> anyhow::Result<Self> {
         Ok(if args.print_tui_theme_items {
             Self::PrintTuiThemeItems
         } else if args.print_tui_binding_commands {
@@ -272,7 +272,7 @@ impl TrippyAction {
         } else if args.generate_man {
             Self::PrintManPage
         } else {
-            Self::Trippy(TrippyConfig::from(args, platform)?)
+            Self::Trippy(TrippyConfig::from(args, privilege, pid)?)
         })
     }
 }
@@ -328,7 +328,7 @@ pub struct TrippyConfig {
 }
 
 impl TrippyConfig {
-    pub fn from(args: Args, platform: &Platform) -> anyhow::Result<Self> {
+    pub fn from(args: Args, privilege: &Privilege, pid: u16) -> anyhow::Result<Self> {
         let cfg_file = if let Some(cfg) = &args.config_file {
             file::read_config_file(cfg)?
         } else if let Some(cfg) = file::read_default_config_file()? {
@@ -336,7 +336,7 @@ impl TrippyConfig {
         } else {
             ConfigFile::default()
         };
-        Self::build_config(args, cfg_file, platform)
+        Self::build_config(args, cfg_file, privilege, pid)
     }
 
     /// The maximum number of flows allowed.
@@ -350,12 +350,14 @@ impl TrippyConfig {
     }
 
     #[allow(clippy::too_many_lines)]
-    fn build_config(args: Args, cfg_file: ConfigFile, platform: &Platform) -> anyhow::Result<Self> {
-        let &Platform {
-            pid,
-            has_privileges,
-            needs_privileges,
-        } = platform;
+    fn build_config(
+        args: Args,
+        cfg_file: ConfigFile,
+        privilege: &Privilege,
+        pid: u16,
+    ) -> anyhow::Result<Self> {
+        let has_privileges = privilege.has_privileges();
+        let needs_privileges = privilege.needs_privileges();
         let cfg_file_trace = cfg_file.trippy.unwrap_or_default();
         let cfg_file_strategy = cfg_file.strategy.unwrap_or_default();
         let cfg_file_tui_bindings = cfg_file.bindings.unwrap_or_default();
@@ -1117,14 +1119,14 @@ mod tests {
     use crossterm::event::KeyCode;
     use std::net::{Ipv4Addr, Ipv6Addr};
     use test_case::test_case;
-    use trippy::tracing::Port;
+    use trippy_core::Port;
 
     #[test]
     fn test_config_default() {
         let args = args(&["trip", "example.com"]).unwrap();
         let cfg_file = ConfigFile::default();
-        let platform = Platform::dummy_for_test();
-        let config = TrippyConfig::build_config(args, cfg_file, &platform).unwrap();
+        let platform = dummy_platform();
+        let config = TrippyConfig::build_config(args, cfg_file, &platform, 0).unwrap();
         let expected = TrippyConfig {
             targets: vec![String::from("example.com")],
             ..TrippyConfig::default()
@@ -1136,9 +1138,9 @@ mod tests {
     fn test_config_sample() {
         let args = args(&["trip", "example.com"]).unwrap();
         let cfg_file: ConfigFile =
-            toml::from_str(include_str!("../trippy-config-sample.toml")).unwrap();
-        let platform = Platform::dummy_for_test();
-        let config = TrippyConfig::build_config(args, cfg_file, &platform).unwrap();
+            toml::from_str(include_str!("../../../trippy-config-sample.toml")).unwrap();
+        let platform = dummy_platform();
+        let config = TrippyConfig::build_config(args, cfg_file, &platform, 0).unwrap();
         let expected = TrippyConfig {
             targets: vec![String::from("example.com")],
             ..TrippyConfig::default()
@@ -1620,14 +1622,14 @@ mod tests {
     }
 
     fn parse_action(cmd: &str) -> anyhow::Result<TrippyAction> {
-        TrippyAction::from(parse(cmd)?, &Platform::dummy_for_test())
+        TrippyAction::from(parse(cmd)?, &dummy_platform(), 0)
     }
 
     fn parse_config(cmd: &str) -> anyhow::Result<TrippyConfig> {
         let args = parse(cmd)?;
         let cfg_file = ConfigFile::default();
-        let platform = Platform::dummy_for_test();
-        TrippyConfig::build_config(args, cfg_file, &platform)
+        let platform = dummy_platform();
+        TrippyConfig::build_config(args, cfg_file, &platform, 0)
     }
 
     fn parse_config_with_privileges(
@@ -1637,12 +1639,8 @@ mod tests {
     ) -> anyhow::Result<TrippyConfig> {
         let args = parse(cmd)?;
         let cfg_file = ConfigFile::default();
-        let platform = Platform {
-            pid: 0,
-            has_privileges,
-            needs_privileges,
-        };
-        TrippyConfig::build_config(args, cfg_file, &platform)
+        let privilege = Privilege::new(has_privileges, needs_privileges);
+        TrippyConfig::build_config(args, cfg_file, &privilege, 0)
     }
 
     fn parse(cmd: &str) -> anyhow::Result<Args> {
@@ -1686,6 +1684,10 @@ mod tests {
         ])
     }
 
+    fn dummy_platform() -> Privilege {
+        Privilege::new(true, false)
+    }
+
     fn args(args: &[&str]) -> anyhow::Result<Args> {
         use clap::Parser;
         Ok(Args::try_parse_from(
diff --git a/src/config/binding.rs b/crates/trippy/src/config/binding.rs
similarity index 100%
rename from src/config/binding.rs
rename to crates/trippy/src/config/binding.rs
diff --git a/src/config/cmd.rs b/crates/trippy/src/config/cmd.rs
similarity index 100%
rename from src/config/cmd.rs
rename to crates/trippy/src/config/cmd.rs
diff --git a/src/config/columns.rs b/crates/trippy/src/config/columns.rs
similarity index 100%
rename from src/config/columns.rs
rename to crates/trippy/src/config/columns.rs
diff --git a/src/config/constants.rs b/crates/trippy/src/config/constants.rs
similarity index 100%
rename from src/config/constants.rs
rename to crates/trippy/src/config/constants.rs
diff --git a/src/config/file.rs b/crates/trippy/src/config/file.rs
similarity index 99%
rename from src/config/file.rs
rename to crates/trippy/src/config/file.rs
index a9fe30c03..7e4113e82 100644
--- a/src/config/file.rs
+++ b/crates/trippy/src/config/file.rs
@@ -12,7 +12,7 @@ use serde::Deserialize;
 use std::fs::File;
 use std::io::{BufReader, Read};
 use std::path::Path;
-use trippy::tracing::defaults;
+use trippy_core::defaults;
 
 const DEFAULT_CONFIG_FILE: &str = "trippy.toml";
 const DEFAULT_HIDDEN_CONFIG_FILE: &str = ".trippy.toml";
@@ -407,7 +407,7 @@ mod tests {
     #[allow(clippy::too_many_lines)]
     fn test_parse_config_sample() {
         let config: ConfigFile =
-            toml::from_str(include_str!("../../trippy-config-sample.toml")).unwrap();
+            toml::from_str(include_str!("../../../../trippy-config-sample.toml")).unwrap();
         pretty_assertions::assert_eq!(ConfigFile::default(), config);
     }
 }
diff --git a/src/config/theme.rs b/crates/trippy/src/config/theme.rs
similarity index 100%
rename from src/config/theme.rs
rename to crates/trippy/src/config/theme.rs
diff --git a/src/frontend.rs b/crates/trippy/src/frontend.rs
similarity index 99%
rename from src/frontend.rs
rename to crates/trippy/src/frontend.rs
index bb22c7ba8..64c58bed2 100644
--- a/src/frontend.rs
+++ b/crates/trippy/src/frontend.rs
@@ -14,7 +14,7 @@ use ratatui::{
     Terminal,
 };
 use std::io;
-use trippy::dns::DnsResolver;
+use trippy_dns::DnsResolver;
 use tui_app::TuiApp;
 
 mod binding;
diff --git a/src/frontend/binding.rs b/crates/trippy/src/frontend/binding.rs
similarity index 100%
rename from src/frontend/binding.rs
rename to crates/trippy/src/frontend/binding.rs
diff --git a/src/frontend/columns.rs b/crates/trippy/src/frontend/columns.rs
similarity index 100%
rename from src/frontend/columns.rs
rename to crates/trippy/src/frontend/columns.rs
diff --git a/src/frontend/config.rs b/crates/trippy/src/frontend/config.rs
similarity index 100%
rename from src/frontend/config.rs
rename to crates/trippy/src/frontend/config.rs
diff --git a/src/frontend/render.rs b/crates/trippy/src/frontend/render.rs
similarity index 100%
rename from src/frontend/render.rs
rename to crates/trippy/src/frontend/render.rs
diff --git a/src/frontend/render/app.rs b/crates/trippy/src/frontend/render/app.rs
similarity index 100%
rename from src/frontend/render/app.rs
rename to crates/trippy/src/frontend/render/app.rs
diff --git a/src/frontend/render/body.rs b/crates/trippy/src/frontend/render/body.rs
similarity index 100%
rename from src/frontend/render/body.rs
rename to crates/trippy/src/frontend/render/body.rs
diff --git a/src/frontend/render/bsod.rs b/crates/trippy/src/frontend/render/bsod.rs
similarity index 100%
rename from src/frontend/render/bsod.rs
rename to crates/trippy/src/frontend/render/bsod.rs
diff --git a/src/frontend/render/chart.rs b/crates/trippy/src/frontend/render/chart.rs
similarity index 100%
rename from src/frontend/render/chart.rs
rename to crates/trippy/src/frontend/render/chart.rs
diff --git a/src/frontend/render/flows.rs b/crates/trippy/src/frontend/render/flows.rs
similarity index 100%
rename from src/frontend/render/flows.rs
rename to crates/trippy/src/frontend/render/flows.rs
diff --git a/src/frontend/render/footer.rs b/crates/trippy/src/frontend/render/footer.rs
similarity index 100%
rename from src/frontend/render/footer.rs
rename to crates/trippy/src/frontend/render/footer.rs
diff --git a/src/frontend/render/header.rs b/crates/trippy/src/frontend/render/header.rs
similarity index 98%
rename from src/frontend/render/header.rs
rename to crates/trippy/src/frontend/render/header.rs
index edb9f9af8..611eaea7b 100644
--- a/src/frontend/render/header.rs
+++ b/crates/trippy/src/frontend/render/header.rs
@@ -8,8 +8,8 @@ use ratatui::widgets::{Block, BorderType, Borders, Paragraph};
 use ratatui::Frame;
 use std::net::IpAddr;
 use std::time::Duration;
-use trippy::dns::{ResolveMethod, Resolver};
-use trippy::tracing::{PortDirection, Protocol};
+use trippy_core::{PortDirection, Protocol};
+use trippy_dns::{ResolveMethod, Resolver};
 
 /// Render the title, config, target, clock and keyboard controls.
 #[allow(clippy::too_many_lines)]
diff --git a/src/frontend/render/help.rs b/crates/trippy/src/frontend/render/help.rs
similarity index 100%
rename from src/frontend/render/help.rs
rename to crates/trippy/src/frontend/render/help.rs
diff --git a/src/frontend/render/histogram.rs b/crates/trippy/src/frontend/render/histogram.rs
similarity index 100%
rename from src/frontend/render/histogram.rs
rename to crates/trippy/src/frontend/render/histogram.rs
diff --git a/src/frontend/render/history.rs b/crates/trippy/src/frontend/render/history.rs
similarity index 100%
rename from src/frontend/render/history.rs
rename to crates/trippy/src/frontend/render/history.rs
diff --git a/src/frontend/render/settings.rs b/crates/trippy/src/frontend/render/settings.rs
similarity index 99%
rename from src/frontend/render/settings.rs
rename to crates/trippy/src/frontend/render/settings.rs
index a18fc700f..6ea8ba181 100644
--- a/src/frontend/render/settings.rs
+++ b/crates/trippy/src/frontend/render/settings.rs
@@ -10,8 +10,8 @@ use ratatui::widgets::{
     Block, BorderType, Borders, Cell, Clear, Paragraph, Row, Table, Tabs, Wrap,
 };
 use ratatui::Frame;
-use trippy::dns::ResolveMethod;
-use trippy::tracing::PortDirection;
+use trippy_core::PortDirection;
+use trippy_dns::ResolveMethod;
 
 /// Render settings dialog.
 pub fn render(f: &mut Frame<'_>, app: &mut TuiApp) {
diff --git a/src/frontend/render/splash.rs b/crates/trippy/src/frontend/render/splash.rs
similarity index 100%
rename from src/frontend/render/splash.rs
rename to crates/trippy/src/frontend/render/splash.rs
diff --git a/src/frontend/render/table.rs b/crates/trippy/src/frontend/render/table.rs
similarity index 99%
rename from src/frontend/render/table.rs
rename to crates/trippy/src/frontend/render/table.rs
index 95ad7aa97..8976d60f7 100644
--- a/src/frontend/render/table.rs
+++ b/crates/trippy/src/frontend/render/table.rs
@@ -12,10 +12,8 @@ use ratatui::widgets::{Block, BorderType, Borders, Cell, Row, Table};
 use ratatui::Frame;
 use std::net::IpAddr;
 use std::rc::Rc;
-use trippy::dns::{AsInfo, DnsEntry, DnsResolver, Resolved, Resolver, Unresolved};
-use trippy::tracing::{
-    Extension, Extensions, IcmpPacketType, MplsLabelStackMember, UnknownExtension,
-};
+use trippy_core::{Extension, Extensions, IcmpPacketType, MplsLabelStackMember, UnknownExtension};
+use trippy_dns::{AsInfo, DnsEntry, DnsResolver, Resolved, Resolver, Unresolved};
 
 /// Render the table of data about the hops.
 ///
diff --git a/src/frontend/render/tabs.rs b/crates/trippy/src/frontend/render/tabs.rs
similarity index 100%
rename from src/frontend/render/tabs.rs
rename to crates/trippy/src/frontend/render/tabs.rs
diff --git a/src/frontend/render/util.rs b/crates/trippy/src/frontend/render/util.rs
similarity index 100%
rename from src/frontend/render/util.rs
rename to crates/trippy/src/frontend/render/util.rs
diff --git a/src/frontend/render/world.rs b/crates/trippy/src/frontend/render/world.rs
similarity index 100%
rename from src/frontend/render/world.rs
rename to crates/trippy/src/frontend/render/world.rs
diff --git a/src/frontend/theme.rs b/crates/trippy/src/frontend/theme.rs
similarity index 100%
rename from src/frontend/theme.rs
rename to crates/trippy/src/frontend/theme.rs
diff --git a/src/frontend/tui_app.rs b/crates/trippy/src/frontend/tui_app.rs
similarity index 99%
rename from src/frontend/tui_app.rs
rename to crates/trippy/src/frontend/tui_app.rs
index 1deefbf3f..5e53bc1d9 100644
--- a/src/frontend/tui_app.rs
+++ b/crates/trippy/src/frontend/tui_app.rs
@@ -8,7 +8,7 @@ use crate::TraceInfo;
 use itertools::Itertools;
 use ratatui::widgets::TableState;
 use std::time::SystemTime;
-use trippy::dns::{DnsResolver, ResolveMethod};
+use trippy_dns::{DnsResolver, ResolveMethod};
 
 pub struct TuiApp {
     pub selected_tracer_data: Trace,
diff --git a/src/geoip.rs b/crates/trippy/src/geoip.rs
similarity index 100%
rename from src/geoip.rs
rename to crates/trippy/src/geoip.rs
diff --git a/crates/trippy/src/lib.rs b/crates/trippy/src/lib.rs
new file mode 100644
index 000000000..e21f8d189
--- /dev/null
+++ b/crates/trippy/src/lib.rs
@@ -0,0 +1,2 @@
+#![allow(rustdoc::broken_intra_doc_links, rustdoc::bare_urls)]
+#![doc = include_str!("../../../README.md")]
diff --git a/src/main.rs b/crates/trippy/src/main.rs
similarity index 95%
rename from src/main.rs
rename to crates/trippy/src/main.rs
index 560e71098..c21133656 100644
--- a/src/main.rs
+++ b/crates/trippy/src/main.rs
@@ -13,14 +13,13 @@
     clippy::cognitive_complexity,
     clippy::option_option
 )]
-#![deny(unsafe_code)]
+#![forbid(unsafe_code)]
 
 use crate::backend::Backend;
 use crate::config::{
     LogFormat, LogSpanEvents, Mode, TrippyAction, TrippyConfig, TuiCommandItem, TuiThemeItem,
 };
 use crate::geoip::GeoIpLookup;
-use crate::platform::Platform;
 use anyhow::{anyhow, Error};
 use backend::trace::Trace;
 use clap::{CommandFactory, Parser};
@@ -37,26 +36,27 @@ use tracing_chrome::{ChromeLayerBuilder, FlushGuard};
 use tracing_subscriber::fmt::format::FmtSpan;
 use tracing_subscriber::layer::SubscriberExt;
 use tracing_subscriber::util::SubscriberInitExt;
-use trippy::dns::{DnsResolver, Resolver};
-use trippy::tracing::{
+use trippy_core::{
     ChannelConfig, Config, IcmpExtensionParseMode, MultipathStrategy, PlatformImpl, PortDirection,
     Protocol, SocketImpl,
 };
-use trippy::tracing::{PrivilegeMode, SourceAddr};
+use trippy_core::{PrivilegeMode, SourceAddr};
+use trippy_dns::{DnsResolver, Resolver};
+use trippy_privilege::Privilege;
 
 mod backend;
 mod config;
 mod frontend;
 mod geoip;
-mod platform;
 mod report;
 
 fn main() -> anyhow::Result<()> {
     let args = Args::parse();
-    Platform::acquire_privileges()?;
-    let platform = Platform::discover()?;
-    match TrippyAction::from(args, &platform)? {
-        TrippyAction::Trippy(cfg) => run_trippy(&cfg, &platform)?,
+    Privilege::acquire_privileges()?;
+    let privilege = Privilege::discover()?;
+    let pid = u16::try_from(process::id() % u32::from(u16::MAX))?;
+    match TrippyAction::from(args, &privilege, pid)? {
+        TrippyAction::Trippy(cfg) => run_trippy(&cfg, pid)?,
         TrippyAction::PrintTuiThemeItems => print_tui_theme_items(),
         TrippyAction::PrintTuiBindingCommands => print_tui_binding_commands(),
         TrippyAction::PrintConfigTemplate => print_config_template(),
@@ -66,7 +66,7 @@ fn main() -> anyhow::Result<()> {
     Ok(())
 }
 
-fn run_trippy(cfg: &TrippyConfig, platform: &Platform) -> anyhow::Result<()> {
+fn run_trippy(cfg: &TrippyConfig, pid: u16) -> anyhow::Result<()> {
     let _guard = configure_logging(cfg);
     let resolver = start_dns_resolver(cfg)?;
     let geoip_lookup = create_geoip_lookup(cfg)?;
@@ -78,8 +78,8 @@ fn run_trippy(cfg: &TrippyConfig, platform: &Platform) -> anyhow::Result<()> {
             cfg.addr_family,
         ));
     }
-    let traces = start_tracers(cfg, &addrs, platform.pid)?;
-    Platform::drop_privileges()?;
+    let traces = start_tracers(cfg, &addrs, pid)?;
+    Privilege::drop_privileges()?;
     run_frontend(cfg, resolver, geoip_lookup, traces)
 }
 
@@ -94,7 +94,7 @@ fn print_tui_binding_commands() {
 }
 
 fn print_config_template() {
-    println!("{}", include_str!("../trippy-config-sample.toml"));
+    println!("{}", include_str!("../../../trippy-config-sample.toml"));
     process::exit(0);
 }
 
@@ -110,7 +110,7 @@ fn print_man_page() -> anyhow::Result<()> {
 
 /// Start the DNS resolver.
 fn start_dns_resolver(cfg: &TrippyConfig) -> anyhow::Result<DnsResolver> {
-    Ok(DnsResolver::start(trippy::dns::Config::new(
+    Ok(DnsResolver::start(trippy_dns::Config::new(
         cfg.dns_resolve_method,
         cfg.addr_family,
         cfg.dns_timeout,
diff --git a/src/report.rs b/crates/trippy/src/report.rs
similarity index 100%
rename from src/report.rs
rename to crates/trippy/src/report.rs
diff --git a/src/report/csv.rs b/crates/trippy/src/report/csv.rs
similarity index 99%
rename from src/report/csv.rs
rename to crates/trippy/src/report/csv.rs
index bd9b87d0a..ebdf5fe7a 100644
--- a/src/report/csv.rs
+++ b/crates/trippy/src/report/csv.rs
@@ -4,7 +4,7 @@ use crate::{backend, TraceInfo};
 use itertools::Itertools;
 use serde::Serialize;
 use std::net::IpAddr;
-use trippy::dns::Resolver;
+use trippy_dns::Resolver;
 
 /// Generate a CSV report of trace data.
 pub fn report<R: Resolver>(
diff --git a/src/report/dot.rs b/crates/trippy/src/report/dot.rs
similarity index 100%
rename from src/report/dot.rs
rename to crates/trippy/src/report/dot.rs
diff --git a/src/report/flows.rs b/crates/trippy/src/report/flows.rs
similarity index 100%
rename from src/report/flows.rs
rename to crates/trippy/src/report/flows.rs
diff --git a/src/report/json.rs b/crates/trippy/src/report/json.rs
similarity index 96%
rename from src/report/json.rs
rename to crates/trippy/src/report/json.rs
index 83e164c96..f0bb3685d 100644
--- a/src/report/json.rs
+++ b/crates/trippy/src/report/json.rs
@@ -1,7 +1,7 @@
 use crate::backend::trace::Trace;
 use crate::report::types::{Hop, Host, Info, Report};
 use crate::TraceInfo;
-use trippy::dns::Resolver;
+use trippy_dns::Resolver;
 
 /// Generate a json report of trace data.
 pub fn report<R: Resolver>(
diff --git a/src/report/silent.rs b/crates/trippy/src/report/silent.rs
similarity index 100%
rename from src/report/silent.rs
rename to crates/trippy/src/report/silent.rs
diff --git a/src/report/stream.rs b/crates/trippy/src/report/stream.rs
similarity index 97%
rename from src/report/stream.rs
rename to crates/trippy/src/report/stream.rs
index 94775824f..6bb51facd 100644
--- a/src/report/stream.rs
+++ b/crates/trippy/src/report/stream.rs
@@ -3,7 +3,7 @@ use crate::report::types::Hop;
 use crate::TraceInfo;
 use anyhow::anyhow;
 use std::thread::sleep;
-use trippy::dns::Resolver;
+use trippy_dns::Resolver;
 
 /// Display a continuous stream of trace data.
 pub fn report<R: Resolver>(info: &TraceInfo, resolver: &R) -> anyhow::Result<()> {
diff --git a/src/report/table.rs b/crates/trippy/src/report/table.rs
similarity index 98%
rename from src/report/table.rs
rename to crates/trippy/src/report/table.rs
index 139f36f2a..0876a4762 100644
--- a/src/report/table.rs
+++ b/crates/trippy/src/report/table.rs
@@ -3,7 +3,7 @@ use crate::TraceInfo;
 use comfy_table::presets::{ASCII_MARKDOWN, UTF8_FULL};
 use comfy_table::{ContentArrangement, Table};
 use itertools::Itertools;
-use trippy::dns::Resolver;
+use trippy_dns::Resolver;
 
 /// Generate a markdown table report of trace data.
 pub fn report_md<R: Resolver>(
diff --git a/src/report/types.rs b/crates/trippy/src/report/types.rs
similarity index 87%
rename from src/report/types.rs
rename to crates/trippy/src/report/types.rs
index 0cb1a060d..33f349cab 100644
--- a/src/report/types.rs
+++ b/crates/trippy/src/report/types.rs
@@ -3,7 +3,7 @@ use itertools::Itertools;
 use serde::{Serialize, Serializer};
 use std::fmt::{Display, Formatter};
 use std::net::IpAddr;
-use trippy::dns::Resolver;
+use trippy_dns::Resolver;
 
 #[derive(Serialize)]
 pub struct Report {
@@ -109,8 +109,8 @@ pub struct Extensions {
     pub extensions: Vec<Extension>,
 }
 
-impl From<&trippy::tracing::Extensions> for Extensions {
-    fn from(value: &trippy::tracing::Extensions) -> Self {
+impl From<&trippy_core::Extensions> for Extensions {
+    fn from(value: &trippy_core::Extensions) -> Self {
         Self {
             extensions: value
                 .extensions
@@ -136,13 +136,13 @@ pub enum Extension {
     Mpls(MplsLabelStack),
 }
 
-impl From<trippy::tracing::Extension> for Extension {
-    fn from(value: trippy::tracing::Extension) -> Self {
+impl From<trippy_core::Extension> for Extension {
+    fn from(value: trippy_core::Extension) -> Self {
         match value {
-            trippy::tracing::Extension::Unknown(unknown) => {
+            trippy_core::Extension::Unknown(unknown) => {
                 Self::Unknown(UnknownExtension::from(unknown))
             }
-            trippy::tracing::Extension::Mpls(mpls) => Self::Mpls(MplsLabelStack::from(mpls)),
+            trippy_core::Extension::Mpls(mpls) => Self::Mpls(MplsLabelStack::from(mpls)),
         }
     }
 }
@@ -161,8 +161,8 @@ pub struct MplsLabelStack {
     pub members: Vec<MplsLabelStackMember>,
 }
 
-impl From<trippy::tracing::MplsLabelStack> for MplsLabelStack {
-    fn from(value: trippy::tracing::MplsLabelStack) -> Self {
+impl From<trippy_core::MplsLabelStack> for MplsLabelStack {
+    fn from(value: trippy_core::MplsLabelStack) -> Self {
         Self {
             members: value
                 .members
@@ -187,8 +187,8 @@ pub struct MplsLabelStackMember {
     pub ttl: u8,
 }
 
-impl From<trippy::tracing::MplsLabelStackMember> for MplsLabelStackMember {
-    fn from(value: trippy::tracing::MplsLabelStackMember) -> Self {
+impl From<trippy_core::MplsLabelStackMember> for MplsLabelStackMember {
+    fn from(value: trippy_core::MplsLabelStackMember) -> Self {
         Self {
             label: value.label,
             exp: value.exp,
@@ -211,8 +211,8 @@ pub struct UnknownExtension {
     pub bytes: Vec<u8>,
 }
 
-impl From<trippy::tracing::UnknownExtension> for UnknownExtension {
-    fn from(value: trippy::tracing::UnknownExtension) -> Self {
+impl From<trippy_core::UnknownExtension> for UnknownExtension {
+    fn from(value: trippy_core::UnknownExtension) -> Self {
         Self {
             class_num: value.class_num,
             class_subtype: value.class_subtype,
diff --git a/src/dns.rs b/src/dns.rs
deleted file mode 100644
index 8c59773e4..000000000
--- a/src/dns.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-//! A lazy DNS resolver.
-//!
-//! This module provides a cheaply cloneable, non-blocking, caching, forward
-//! and reverse DNS resolver which support the ability to lookup Autonomous
-//! System (AS) information.
-
-mod lazy_resolver;
-mod resolver;
-
-pub use lazy_resolver::{Config, DnsResolver, IpAddrFamily, ResolveMethod};
-pub use resolver::{AsInfo, DnsEntry, Error, Resolved, Resolver, Result, Unresolved};
diff --git a/src/lib.rs b/src/lib.rs
deleted file mode 100644
index 45d32763f..000000000
--- a/src/lib.rs
+++ /dev/null
@@ -1,15 +0,0 @@
-#![doc = include_str!("../README.md")]
-#![warn(clippy::all, clippy::pedantic, clippy::nursery, rust_2018_idioms)]
-#![allow(
-    clippy::module_name_repetitions,
-    clippy::struct_field_names,
-    clippy::option_if_let_else,
-    clippy::missing_const_for_fn,
-    clippy::cast_possible_truncation,
-    clippy::missing_errors_doc
-)]
-#![deny(unsafe_code)]
-
-pub mod tracing;
-
-pub mod dns;
diff --git a/src/tracing.rs b/src/tracing.rs
deleted file mode 100644
index 43b4ee333..000000000
--- a/src/tracing.rs
+++ /dev/null
@@ -1,29 +0,0 @@
-mod builder;
-mod config;
-mod constants;
-mod error;
-mod net;
-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,
-    MultipathStrategy, PortDirection, PrivilegeMode, Protocol,
-};
-pub use net::channel::TracerChannel;
-pub use net::source::SourceAddr;
-pub use net::{PlatformImpl, SocketImpl};
-pub use probe::{
-    Extension, Extensions, IcmpPacketType, MplsLabelStack, MplsLabelStackMember, Probe,
-    ProbeComplete, ProbeState, UnknownExtension,
-};
-pub use tracer::{CompletionReason, Tracer, TracerRound};
-pub use types::{
-    Flags, MaxInflight, MaxRounds, PacketSize, PayloadPattern, Port, Round, Sequence, TimeToLive,
-    TraceId, TypeOfService,
-};


Test patch:
diff --git a/tests/resources/simulation/ipv4_icmp.yaml b/crates/trippy-core/tests/resources/simulation/ipv4_icmp.yaml
similarity index 100%
rename from tests/resources/simulation/ipv4_icmp.yaml
rename to crates/trippy-core/tests/resources/simulation/ipv4_icmp.yaml
diff --git a/tests/resources/simulation/ipv4_icmp_gaps.yaml b/crates/trippy-core/tests/resources/simulation/ipv4_icmp_gaps.yaml
similarity index 100%
rename from tests/resources/simulation/ipv4_icmp_gaps.yaml
rename to crates/trippy-core/tests/resources/simulation/ipv4_icmp_gaps.yaml
diff --git a/tests/resources/simulation/ipv4_icmp_min.yaml b/crates/trippy-core/tests/resources/simulation/ipv4_icmp_min.yaml
similarity index 100%
rename from tests/resources/simulation/ipv4_icmp_min.yaml
rename to crates/trippy-core/tests/resources/simulation/ipv4_icmp_min.yaml
diff --git a/tests/resources/simulation/ipv4_icmp_ooo.yaml b/crates/trippy-core/tests/resources/simulation/ipv4_icmp_ooo.yaml
similarity index 100%
rename from tests/resources/simulation/ipv4_icmp_ooo.yaml
rename to crates/trippy-core/tests/resources/simulation/ipv4_icmp_ooo.yaml
diff --git a/tests/resources/simulation/ipv4_icmp_pattern.yaml b/crates/trippy-core/tests/resources/simulation/ipv4_icmp_pattern.yaml
similarity index 100%
rename from tests/resources/simulation/ipv4_icmp_pattern.yaml
rename to crates/trippy-core/tests/resources/simulation/ipv4_icmp_pattern.yaml
diff --git a/tests/resources/simulation/ipv4_icmp_quick.yaml b/crates/trippy-core/tests/resources/simulation/ipv4_icmp_quick.yaml
similarity index 100%
rename from tests/resources/simulation/ipv4_icmp_quick.yaml
rename to crates/trippy-core/tests/resources/simulation/ipv4_icmp_quick.yaml
diff --git a/tests/resources/simulation/ipv4_icmp_wrap.yaml b/crates/trippy-core/tests/resources/simulation/ipv4_icmp_wrap.yaml
similarity index 100%
rename from tests/resources/simulation/ipv4_icmp_wrap.yaml
rename to crates/trippy-core/tests/resources/simulation/ipv4_icmp_wrap.yaml
diff --git a/tests/resources/simulation/ipv4_tcp_fixed_dest.yaml b/crates/trippy-core/tests/resources/simulation/ipv4_tcp_fixed_dest.yaml
similarity index 100%
rename from tests/resources/simulation/ipv4_tcp_fixed_dest.yaml
rename to crates/trippy-core/tests/resources/simulation/ipv4_tcp_fixed_dest.yaml
diff --git a/tests/resources/simulation/ipv4_udp_classic_fixed_dest.yaml b/crates/trippy-core/tests/resources/simulation/ipv4_udp_classic_fixed_dest.yaml
similarity index 100%
rename from tests/resources/simulation/ipv4_udp_classic_fixed_dest.yaml
rename to crates/trippy-core/tests/resources/simulation/ipv4_udp_classic_fixed_dest.yaml
diff --git a/tests/resources/simulation/ipv4_udp_classic_fixed_src.yaml b/crates/trippy-core/tests/resources/simulation/ipv4_udp_classic_fixed_src.yaml
similarity index 100%
rename from tests/resources/simulation/ipv4_udp_classic_fixed_src.yaml
rename to crates/trippy-core/tests/resources/simulation/ipv4_udp_classic_fixed_src.yaml
diff --git a/tests/resources/simulation/ipv4_udp_classic_unprivileged.yaml b/crates/trippy-core/tests/resources/simulation/ipv4_udp_classic_unprivileged.yaml
similarity index 100%
rename from tests/resources/simulation/ipv4_udp_classic_unprivileged.yaml
rename to crates/trippy-core/tests/resources/simulation/ipv4_udp_classic_unprivileged.yaml
diff --git a/tests/resources/simulation/ipv4_udp_dublin_fixed_both.yaml b/crates/trippy-core/tests/resources/simulation/ipv4_udp_dublin_fixed_both.yaml
similarity index 100%
rename from tests/resources/simulation/ipv4_udp_dublin_fixed_both.yaml
rename to crates/trippy-core/tests/resources/simulation/ipv4_udp_dublin_fixed_both.yaml
diff --git a/tests/resources/simulation/ipv4_udp_paris_fixed_both.yaml b/crates/trippy-core/tests/resources/simulation/ipv4_udp_paris_fixed_both.yaml
similarity index 100%
rename from tests/resources/simulation/ipv4_udp_paris_fixed_both.yaml
rename to crates/trippy-core/tests/resources/simulation/ipv4_udp_paris_fixed_both.yaml
diff --git a/tests/resources/wintun.dll b/crates/trippy-core/tests/resources/wintun.dll
similarity index 100%
rename from tests/resources/wintun.dll
rename to crates/trippy-core/tests/resources/wintun.dll
diff --git a/tests/sim/main.rs b/crates/trippy-core/tests/sim/main.rs
similarity index 100%
rename from tests/sim/main.rs
rename to crates/trippy-core/tests/sim/main.rs
diff --git a/tests/sim/network.rs b/crates/trippy-core/tests/sim/network.rs
similarity index 94%
rename from tests/sim/network.rs
rename to crates/trippy-core/tests/sim/network.rs
index 09e787983..2624670f1 100644
--- a/tests/sim/network.rs
+++ b/crates/trippy-core/tests/sim/network.rs
@@ -8,18 +8,16 @@ use tokio::sync::Mutex;
 use tokio::task::JoinHandle;
 use tokio_util::sync::CancellationToken;
 use tracing::{debug, info};
-use trippy::tracing::packet::checksum::{
-    icmp_ipv4_checksum, ipv4_header_checksum, tcp_ipv4_checksum,
-};
-use trippy::tracing::packet::icmpv4::destination_unreachable::DestinationUnreachablePacket;
-use trippy::tracing::packet::icmpv4::echo_reply::EchoReplyPacket;
-use trippy::tracing::packet::icmpv4::echo_request::EchoRequestPacket;
-use trippy::tracing::packet::icmpv4::time_exceeded::TimeExceededPacket;
-use trippy::tracing::packet::icmpv4::{IcmpCode, IcmpType};
-use trippy::tracing::packet::ipv4::Ipv4Packet;
-use trippy::tracing::packet::tcp::TcpPacket;
-use trippy::tracing::packet::udp::UdpPacket;
-use trippy::tracing::packet::IpProtocol;
+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;
 
 const READ_TIMEOUT: Duration = Duration::from_millis(10);
 
diff --git a/tests/sim/simulation.rs b/crates/trippy-core/tests/sim/simulation.rs
similarity index 92%
rename from tests/sim/simulation.rs
rename to crates/trippy-core/tests/sim/simulation.rs
index b2e696d3f..c5d5445c3 100644
--- a/tests/sim/simulation.rs
+++ b/crates/trippy-core/tests/sim/simulation.rs
@@ -1,6 +1,6 @@
 use serde::Deserialize;
 use std::net::IpAddr;
-use trippy::tracing::Port;
+use trippy_core::Port;
 
 /// A simulated trace.
 #[derive(Debug, Deserialize)]
@@ -70,7 +70,7 @@ pub enum PrivilegeMode {
     Unprivileged,
 }
 
-impl From<PrivilegeMode> for trippy::tracing::PrivilegeMode {
+impl From<PrivilegeMode> for trippy_core::PrivilegeMode {
     fn from(value: PrivilegeMode) -> Self {
         match value {
             PrivilegeMode::Privileged => Self::Privileged,
@@ -86,7 +86,7 @@ pub enum Protocol {
     Tcp,
 }
 
-impl From<Protocol> for trippy::tracing::Protocol {
+impl From<Protocol> for trippy_core::Protocol {
     fn from(value: Protocol) -> Self {
         match value {
             Protocol::Icmp => Self::Icmp,
@@ -111,7 +111,7 @@ pub struct FixedBoth {
     pub dest: u16,
 }
 
-impl From<PortDirection> for trippy::tracing::PortDirection {
+impl From<PortDirection> for trippy_core::PortDirection {
     fn from(value: PortDirection) -> Self {
         match value {
             PortDirection::None => Self::None,
@@ -132,7 +132,7 @@ pub enum MultipathStrategy {
     Dublin,
 }
 
-impl From<MultipathStrategy> for trippy::tracing::MultipathStrategy {
+impl From<MultipathStrategy> for trippy_core::MultipathStrategy {
     fn from(value: MultipathStrategy) -> Self {
         match value {
             MultipathStrategy::Classic => Self::Classic,
diff --git a/tests/sim/tests.rs b/crates/trippy-core/tests/sim/tests.rs
similarity index 100%
rename from tests/sim/tests.rs
rename to crates/trippy-core/tests/sim/tests.rs
diff --git a/tests/sim/tracer.rs b/crates/trippy-core/tests/sim/tracer.rs
similarity index 99%
rename from tests/sim/tracer.rs
rename to crates/trippy-core/tests/sim/tracer.rs
index 738d06b73..b3c520fc8 100644
--- a/tests/sim/tracer.rs
+++ b/crates/trippy-core/tests/sim/tracer.rs
@@ -6,7 +6,7 @@ use std::thread;
 use std::time::Duration;
 use tokio_util::sync::CancellationToken;
 use tracing::info;
-use trippy::tracing::{
+use trippy_core::{
     defaults, Builder, CompletionReason, MaxRounds, MultipathStrategy, PacketSize, PayloadPattern,
     PortDirection, PrivilegeMode, ProbeState, Protocol, Sequence, TimeToLive, TraceId, TracerRound,
 };
diff --git a/tests/sim/tun_device.rs b/crates/trippy-core/tests/sim/tun_device.rs
similarity index 100%
rename from tests/sim/tun_device.rs
rename to crates/trippy-core/tests/sim/tun_device.rs
diff --git a/test_resources/backend/ipv4_3probes_3hops_completed.yaml b/crates/trippy/test_resources/backend/ipv4_3probes_3hops_completed.yaml
similarity index 100%
rename from test_resources/backend/ipv4_3probes_3hops_completed.yaml
rename to crates/trippy/test_resources/backend/ipv4_3probes_3hops_completed.yaml
diff --git a/test_resources/backend/ipv4_3probes_3hops_mixed_multi.yaml b/crates/trippy/test_resources/backend/ipv4_3probes_3hops_mixed_multi.yaml
similarity index 100%
rename from test_resources/backend/ipv4_3probes_3hops_mixed_multi.yaml
rename to crates/trippy/test_resources/backend/ipv4_3probes_3hops_mixed_multi.yaml
diff --git a/test_resources/backend/ipv4_4probes_0latency.yaml b/crates/trippy/test_resources/backend/ipv4_4probes_0latency.yaml
similarity index 100%
rename from test_resources/backend/ipv4_4probes_0latency.yaml
rename to crates/trippy/test_resources/backend/ipv4_4probes_0latency.yaml
diff --git a/test_resources/backend/ipv4_4probes_all_status.yaml b/crates/trippy/test_resources/backend/ipv4_4probes_all_status.yaml
similarity index 100%
rename from test_resources/backend/ipv4_4probes_all_status.yaml
rename to crates/trippy/test_resources/backend/ipv4_4probes_all_status.yaml
diff --git a/test_resources/config/completions_bash.txt b/crates/trippy/test_resources/config/completions_bash.txt
similarity index 100%
rename from test_resources/config/completions_bash.txt
rename to crates/trippy/test_resources/config/completions_bash.txt
diff --git a/test_resources/config/completions_elvish.txt b/crates/trippy/test_resources/config/completions_elvish.txt
similarity index 100%
rename from test_resources/config/completions_elvish.txt
rename to crates/trippy/test_resources/config/completions_elvish.txt
diff --git a/test_resources/config/completions_fish.txt b/crates/trippy/test_resources/config/completions_fish.txt
similarity index 100%
rename from test_resources/config/completions_fish.txt
rename to crates/trippy/test_resources/config/completions_fish.txt
diff --git a/test_resources/config/completions_powershell.txt b/crates/trippy/test_resources/config/completions_powershell.txt
similarity index 100%
rename from test_resources/config/completions_powershell.txt
rename to crates/trippy/test_resources/config/completions_powershell.txt
diff --git a/test_resources/config/completions_zsh.txt b/crates/trippy/test_resources/config/completions_zsh.txt
similarity index 100%
rename from test_resources/config/completions_zsh.txt
rename to crates/trippy/test_resources/config/completions_zsh.txt
diff --git a/test_resources/config/trip.1 b/crates/trippy/test_resources/config/trip.1
similarity index 100%
rename from test_resources/config/trip.1
rename to crates/trippy/test_resources/config/trip.1
diff --git a/test_resources/config/usage_long.txt b/crates/trippy/test_resources/config/usage_long.txt
similarity index 100%
rename from test_resources/config/usage_long.txt
rename to crates/trippy/test_resources/config/usage_long.txt
diff --git a/test_resources/config/usage_short.txt b/crates/trippy/test_resources/config/usage_short.txt
similarity index 100%
rename from test_resources/config/usage_short.txt
rename to crates/trippy/test_resources/config/usage_short.txt
diff --git a/test_resources/tui/tui_binding_commands.txt b/crates/trippy/test_resources/tui/tui_binding_commands.txt
similarity index 100%
rename from test_resources/tui/tui_binding_commands.txt
rename to crates/trippy/test_resources/tui/tui_binding_commands.txt
diff --git a/test_resources/tui/tui_theme_items.txt b/crates/trippy/test_resources/tui/tui_theme_items.txt
similarity index 100%
rename from test_resources/tui/tui_theme_items.txt
rename to crates/trippy/test_resources/tui/tui_theme_items.txt