From 32f496d3d04cb963dee134286bc1c256076126d0 Mon Sep 17 00:00:00 2001 From: pjht Date: Mon, 5 Dec 2022 12:13:32 -0600 Subject: [PATCH] Initial commit --- .gitignore | 1 + 2 | 49 ++ Cargo.lock | 686 ++++++++++++++++++++++ Cargo.toml | 13 + nstack-runner | 1 + runner/Cargo.lock | 47 ++ runner/Cargo.toml | 10 + runner/iptool/.github/workflows/build.yml | 17 + runner/iptool/.gitignore | 1 + runner/iptool/Cargo.lock | 215 +++++++ runner/iptool/Cargo.toml | 21 + runner/iptool/LICENSE | 8 + runner/iptool/README.md | 3 + runner/iptool/default.nix | 8 + runner/iptool/flake.lock | 25 + runner/iptool/flake.nix | 28 + runner/iptool/src/lib.rs | 133 +++++ runner/iptool/src/linux.rs | 369 ++++++++++++ runner/rust-toolchain.toml | 2 + runner/src/main.rs | 33 ++ src/arp.rs | 217 +++++++ src/ethernet.rs | 124 ++++ src/icmp.rs | 94 +++ src/iface.rs | 62 ++ src/ip.rs | 170 ++++++ src/main.rs | 67 +++ src/packet.rs | 306 ++++++++++ 27 files changed, 2710 insertions(+) create mode 100644 .gitignore create mode 100644 2 create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 120000 nstack-runner create mode 100644 runner/Cargo.lock create mode 100644 runner/Cargo.toml create mode 100644 runner/iptool/.github/workflows/build.yml create mode 100644 runner/iptool/.gitignore create mode 100644 runner/iptool/Cargo.lock create mode 100644 runner/iptool/Cargo.toml create mode 100644 runner/iptool/LICENSE create mode 100644 runner/iptool/README.md create mode 100644 runner/iptool/default.nix create mode 100644 runner/iptool/flake.lock create mode 100644 runner/iptool/flake.nix create mode 100644 runner/iptool/src/lib.rs create mode 100644 runner/iptool/src/linux.rs create mode 100644 runner/rust-toolchain.toml create mode 100644 runner/src/main.rs create mode 100644 src/arp.rs create mode 100644 src/ethernet.rs create mode 100644 src/icmp.rs create mode 100644 src/iface.rs create mode 100644 src/ip.rs create mode 100644 src/main.rs create mode 100644 src/packet.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/2 b/2 new file mode 100644 index 0000000..b217f3f --- /dev/null +++ b/2 @@ -0,0 +1,49 @@ +use std::net::Ipv4Addr; + +use tun_tap::Mode; + +use crate::ethernet::{EtherType, EthernetFrame, InvalidEtherType, MacAddr}; + +pub struct Iface { + iface: tun_tap::Iface, + mac: MacAddr, + ip: Option, +} + +impl Iface { + pub fn new() -> Iface { + Self { + iface: tun_tap::Iface::without_packet_info("tap0", Mode::Tap).unwrap(), + mac: MacAddr::new([0x9A, 0xB1, 0xB5, 0x22, 0x38, 0xAB]), + ip: None, + } + } + + pub fn recv(&self) -> Result { + let mut frame_buf = [0u8; 2048]; + let frame_len = self.iface.recv(&mut frame_buf).unwrap(); + EthernetFrame::parse(&frame_buf[0..frame_len]) + } + + pub fn mac_to_self(&self, mac: MacAddr) -> bool { + mac == self.mac || mac.is_broadcast() + } + + pub fn send(&self, dst: MacAddr, typ: EtherType, data: &[u8]) { + let frame = EthernetFrame::new(dst, self.mac, typ, data).serialize(); + println!("{}", frame); + self.iface.send(&frame).unwrap(); + } + + pub fn mac(&self) -> MacAddr { + self.mac + } + + pub fn ip(&self) -> Option { + self.ip + } + + pub fn set_ip(&mut self, ip: Ipv4Addr) { + self.ip = Some(ip); + } +} diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..316cea6 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,686 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +dependencies = [ + "byteorder", + "iovec", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "crossbeam-deque" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20ff29ded3204c5106278a81a38f4b482636ed4fa1e6cfbeef193291beb29ed" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "crossbeam-utils", + "lazy_static", + "maybe-uninit", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" +dependencies = [ + "cfg-if 0.1.10", + "crossbeam-utils", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "lazy_static", +] + +[[package]] +name = "derive-try-from-primitive" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302ccf094df1151173bb6f5a2282fcd2f45accd5eae1bdf82dcbfefbc501ad5c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "futures" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "internet-checksum" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6d6206008e25125b1f97fbe5d309eb7b85141cf9199d52dbd3729a1584dd16" + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "lock_api" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "memoffset" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mio" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio-uds" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" +dependencies = [ + "iovec", + "libc", + "mio", +] + +[[package]] +name = "miow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "mopa" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a785740271256c230f57462d3b83e52f998433a7062fc18f96d5999474a9f915" + +[[package]] +name = "net2" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "nstack-rs" +version = "0.1.0" +dependencies = [ + "derive-try-from-primitive", + "internet-checksum", + "mopa", + "rhexdump", + "tun-tap", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "parking_lot" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" +dependencies = [ + "lock_api", + "parking_lot_core", + "rustc_version", +] + +[[package]] +name = "parking_lot_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" +dependencies = [ + "cfg-if 0.1.10", + "cloudabi", + "libc", + "redox_syscall", + "rustc_version", + "smallvec", + "winapi 0.3.9", +] + +[[package]] +name = "proc-macro2" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "rhexdump" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e9af64574935e39f24d1c0313a997c8b880ca0e087c888bc6af8af31579847" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "scoped-tls" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "slab" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" + +[[package]] +name = "smallvec" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" +dependencies = [ + "maybe-uninit", +] + +[[package]] +name = "syn" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tokio" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6" +dependencies = [ + "bytes", + "futures", + "mio", + "num_cpus", + "tokio-codec", + "tokio-current-thread", + "tokio-executor", + "tokio-fs", + "tokio-io", + "tokio-reactor", + "tokio-sync", + "tokio-tcp", + "tokio-threadpool", + "tokio-timer", + "tokio-udp", + "tokio-uds", +] + +[[package]] +name = "tokio-codec" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b2998660ba0e70d18684de5d06b70b70a3a747469af9dea7618cc59e75976b" +dependencies = [ + "bytes", + "futures", + "tokio-io", +] + +[[package]] +name = "tokio-core" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87b1395334443abca552f63d4f61d0486f12377c2ba8b368e523f89e828cffd4" +dependencies = [ + "bytes", + "futures", + "iovec", + "log", + "mio", + "scoped-tls", + "tokio", + "tokio-executor", + "tokio-io", + "tokio-reactor", + "tokio-timer", +] + +[[package]] +name = "tokio-current-thread" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1de0e32a83f131e002238d7ccde18211c0a5397f60cbfffcb112868c2e0e20e" +dependencies = [ + "futures", + "tokio-executor", +] + +[[package]] +name = "tokio-executor" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671" +dependencies = [ + "crossbeam-utils", + "futures", +] + +[[package]] +name = "tokio-fs" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297a1206e0ca6302a0eed35b700d292b275256f596e2f3fea7729d5e629b6ff4" +dependencies = [ + "futures", + "tokio-io", + "tokio-threadpool", +] + +[[package]] +name = "tokio-io" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" +dependencies = [ + "bytes", + "futures", + "log", +] + +[[package]] +name = "tokio-reactor" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351" +dependencies = [ + "crossbeam-utils", + "futures", + "lazy_static", + "log", + "mio", + "num_cpus", + "parking_lot", + "slab", + "tokio-executor", + "tokio-io", + "tokio-sync", +] + +[[package]] +name = "tokio-sync" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfe50152bc8164fcc456dab7891fa9bf8beaf01c5ee7e1dd43a397c3cf87dee" +dependencies = [ + "fnv", + "futures", +] + +[[package]] +name = "tokio-tcp" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98df18ed66e3b72e742f185882a9e201892407957e45fbff8da17ae7a7c51f72" +dependencies = [ + "bytes", + "futures", + "iovec", + "mio", + "tokio-io", + "tokio-reactor", +] + +[[package]] +name = "tokio-threadpool" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df720b6581784c118f0eb4310796b12b1d242a7eb95f716a8367855325c25f89" +dependencies = [ + "crossbeam-deque", + "crossbeam-queue", + "crossbeam-utils", + "futures", + "lazy_static", + "log", + "num_cpus", + "slab", + "tokio-executor", +] + +[[package]] +name = "tokio-timer" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93044f2d313c95ff1cb7809ce9a7a05735b012288a888b62d4434fd58c94f296" +dependencies = [ + "crossbeam-utils", + "futures", + "slab", + "tokio-executor", +] + +[[package]] +name = "tokio-udp" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2a0b10e610b39c38b031a2fcab08e4b82f16ece36504988dcbd81dbba650d82" +dependencies = [ + "bytes", + "futures", + "log", + "mio", + "tokio-codec", + "tokio-io", + "tokio-reactor", +] + +[[package]] +name = "tokio-uds" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab57a4ac4111c8c9dbcf70779f6fc8bc35ae4b2454809febac840ad19bd7e4e0" +dependencies = [ + "bytes", + "futures", + "iovec", + "libc", + "log", + "mio", + "mio-uds", + "tokio-codec", + "tokio-io", + "tokio-reactor", +] + +[[package]] +name = "tun-tap" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53ccbe9cfffdaa7eefd36538bb26f228d4bd319a8aeca044d54377612a646bcd" +dependencies = [ + "cc", + "futures", + "libc", + "mio", + "tokio-core", +] + +[[package]] +name = "unicode-ident" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..33dd9f2 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "nstack-rs" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +derive-try-from-primitive = "1.0.0" +internet-checksum = "0.2.1" +mopa = "0.2.2" +rhexdump = "0.1.1" +tun-tap = "0.1.2" diff --git a/nstack-runner b/nstack-runner new file mode 120000 index 0000000..4f4c2b6 --- /dev/null +++ b/nstack-runner @@ -0,0 +1 @@ +runner/target/debug/runner \ No newline at end of file diff --git a/runner/Cargo.lock b/runner/Cargo.lock new file mode 100644 index 0000000..61f3d2c --- /dev/null +++ b/runner/Cargo.lock @@ -0,0 +1,47 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "capctl" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "526c6a8746a7cfb052c15d20259c4f5c021966affdc7c960c71ca640f824c801" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "iptool" +version = "0.1.0" +dependencies = [ + "libc", +] + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "runner" +version = "0.1.0" +dependencies = [ + "capctl", + "iptool", +] diff --git a/runner/Cargo.toml b/runner/Cargo.toml new file mode 100644 index 0000000..1fde40a --- /dev/null +++ b/runner/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "runner" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +capctl = "0.2.1" +iptool = { path = "iptool" } diff --git a/runner/iptool/.github/workflows/build.yml b/runner/iptool/.github/workflows/build.yml new file mode 100644 index 0000000..784bad9 --- /dev/null +++ b/runner/iptool/.github/workflows/build.yml @@ -0,0 +1,17 @@ +name: "Build" +on: + pull_request: + push: +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2.3.4 + - uses: cachix/install-nix-action@v13 + with: + nix_path: "" + - uses: cachix/cachix-action@v10 + with: + name: nyantec + authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' + - run: nix-build -A iptool diff --git a/runner/iptool/.gitignore b/runner/iptool/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/runner/iptool/.gitignore @@ -0,0 +1 @@ +/target diff --git a/runner/iptool/Cargo.lock b/runner/iptool/Cargo.lock new file mode 100644 index 0000000..d762195 --- /dev/null +++ b/runner/iptool/Cargo.lock @@ -0,0 +1,215 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "ipnetwork" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4088d739b183546b239688ddbc79891831df421773df95e236daf7867866d355" +dependencies = [ + "serde", +] + +[[package]] +name = "iptool" +version = "0.1.0" +dependencies = [ + "libc", + "pnet", +] + +[[package]] +name = "libc" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2f96d100e1cf1929e7719b7edb3b90ab5298072638fccd77be9ce942ecdfce" + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "pnet" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6d2a0409666964722368ef5fb74b9f93fac11c18bef3308693c16c6733f103" +dependencies = [ + "ipnetwork", + "pnet_base", + "pnet_datalink", + "pnet_packet", + "pnet_sys", + "pnet_transport", +] + +[[package]] +name = "pnet_base" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25488cd551a753dcaaa6fffc9f69a7610a412dd8954425bf7ffad5f7d1156fb8" + +[[package]] +name = "pnet_datalink" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d1f8ab1ef6c914cf51dc5dfe0be64088ea5f3b08bbf5a31abc70356d271198" +dependencies = [ + "ipnetwork", + "libc", + "pnet_base", + "pnet_sys", + "winapi", +] + +[[package]] +name = "pnet_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30490e0852e58402b8fae0d39897b08a24f493023a4d6cf56b2e30f31ed57548" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "pnet_macros_support" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4714e10f30cab023005adce048f2d30dd4ac4f093662abf2220855655ef8f90" +dependencies = [ + "pnet_base", +] + +[[package]] +name = "pnet_packet" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8588067671d03c9f4254b2e66fecb4d8b93b5d3e703195b84f311cd137e32130" +dependencies = [ + "glob", + "pnet_base", + "pnet_macros", + "pnet_macros_support", +] + +[[package]] +name = "pnet_sys" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9a3f32b0df45515befd19eed04616f6b56a488da92afc61164ef455e955f07f" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "pnet_transport" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "932b2916d693bcc5fa18443dc99142e0a6fd31a6ce75a511868f7174c17e2bce" +dependencies = [ + "libc", + "pnet_base", + "pnet_packet", + "pnet_sys", +] + +[[package]] +name = "proc-macro2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc3358ebc67bc8b7fa0c007f945b0b18226f78437d61bec735a9eb96b61ee70" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" + +[[package]] +name = "syn" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/runner/iptool/Cargo.toml b/runner/iptool/Cargo.toml new file mode 100644 index 0000000..11e13bb --- /dev/null +++ b/runner/iptool/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "iptool" +version = "0.1.0" +authors = ["Finn Behrens "] +edition = "2018" +repository = "https://github.com/nyantec/iptool" +license = "MirOS" +keywords = [ "linux", "network" ] +categories = [ "hardware-support" ] +description = "Rust linux iptool helpers for network interfaces" +homepage = "https://github.com/nyantec/iptool" +readme = "README.md" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = [] + +[dependencies] +libc = "0.2" + +pnet = { version = "0.28", optional = true } \ No newline at end of file diff --git a/runner/iptool/LICENSE b/runner/iptool/LICENSE new file mode 100644 index 0000000..d755898 --- /dev/null +++ b/runner/iptool/LICENSE @@ -0,0 +1,8 @@ +Copyright 2020 nyantec GmbH + +Authors: + Finn Behrens + +Provided that these terms and disclaimer and all copyright notices are retained or reproduced in an accompanying document, permission is granted to deal in this work without restriction, including unlimited rights to use, publicly perform, distribute, sell, modify, merge, give away, or sublicence. + +This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to the utmost extent permitted by applicable law, neither express nor implied; without malicious intent or gross negligence. In no event may a licensor, author or contributor be held liable for indirect, direct, other damage, loss, or other issues arising in any way out of dealing in the work, even if advised of the possibility of such damage or existence of a defect, except proven that it results out of said person's immediate fault when using the work as intended. diff --git a/runner/iptool/README.md b/runner/iptool/README.md new file mode 100644 index 0000000..0a8004d --- /dev/null +++ b/runner/iptool/README.md @@ -0,0 +1,3 @@ +# IpTool + +Rust linux iptool helpers for network interfaces. \ No newline at end of file diff --git a/runner/iptool/default.nix b/runner/iptool/default.nix new file mode 100644 index 0000000..6e54b59 --- /dev/null +++ b/runner/iptool/default.nix @@ -0,0 +1,8 @@ +(import ( + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/12c64ca55c1014cdc1b16ed5a804aa8576601ff2.tar.gz"; + sha256 = "0jm6nzb83wa6ai17ly9fzpqc40wg1viib8klq8lby54agpl213w5"; + } +) { + src = ./.; +}).defaultNix.packages.x86_64-linux diff --git a/runner/iptool/flake.lock b/runner/iptool/flake.lock new file mode 100644 index 0000000..d02fd15 --- /dev/null +++ b/runner/iptool/flake.lock @@ -0,0 +1,25 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1635150109, + "narHash": "sha256-KTtyOdjFY8AqQRKytamEuLgPJYRjQSDivyNlZtT/2Zs=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "8e18c70837aa01ade3718cd0fd35b649b3a2cf52", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/runner/iptool/flake.nix b/runner/iptool/flake.nix new file mode 100644 index 0000000..b256acd --- /dev/null +++ b/runner/iptool/flake.nix @@ -0,0 +1,28 @@ +{ + description = "iptool"; + + outputs = { self, nixpkgs }: let + version = self.shortRev or (toString self.lastModifiedDate); + overlay = final: prev: { + iptool = final.callPackage ( + { rustPlatform }: rustPlatform.buildRustPackage { + pname = "iptool"; + inherit version; + src = self; + cargoLock.lockFile = ./Cargo.lock; + cargoBuildFlags = [ "--all-features" ]; + } + ) {}; + }; + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ overlay ]; + }; + in { + inherit overlay; + packages.x86_64-linux = { + inherit (pkgs) iptool; + }; + defaultPackage.x86_64-linux = self.packages.x86_64-linux.iptool; + }; +} diff --git a/runner/iptool/src/lib.rs b/runner/iptool/src/lib.rs new file mode 100644 index 0000000..4acd086 --- /dev/null +++ b/runner/iptool/src/lib.rs @@ -0,0 +1,133 @@ +use std::io::{Error, Result}; + +#[cfg(target_family = "unix")] +use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; + +#[cfg(feature = "pnet")] +use pnet::datalink::MacAddr; + +#[cfg(target_os = "linux")] +mod linux; + +#[cfg(target_os = "linux")] +pub use linux::{Ifreq, SIOCGIFINDEX}; + +// TODO: macOS + +/// Only use it for a short amount of time, as it does not close it's ioctl socket +pub struct IpTool { + #[cfg(target_family = "unix")] + fd: RawFd, +} + +/* +TODO: is it already non send? +impl !Send for IpTool {} +impl !Sync for IpTool {} + */ + +impl AsRawFd for IpTool { + fn as_raw_fd(&self) -> RawFd { + self.fd + } +} + +impl FromRawFd for IpTool { + unsafe fn from_raw_fd(fd: RawFd) -> Self { + Self { fd } + } +} + +// Helper function +#[allow(dead_code)] // not used yet on non linux +pub(crate) fn copy_slice(dst: &mut [u8], src: &[u8]) -> usize { + let mut c = 0; + + for (d, s) in dst.iter_mut().zip(src.iter()) { + *d = *s; + c += 1; + } + + c +} + +pub fn parse_mac_addr(mac: &str) -> Result<[libc::c_char; 14]> { + let mut addr: [libc::c_char; 14] = [0; 14]; + let mac_vec: Vec<&str> = mac.split(':').collect(); + if mac_vec.len() != 6 { + // TODO: unlikly (https://doc.rust-lang.org/nightly/std/intrinsics/fn.unlikely.html) + return Err(Error::from_raw_os_error(libc::EINVAL)); + } + for x in 0..6 { + if let Ok(data) = u8::from_str_radix(mac_vec[x], 16) { + addr[x] = data as i8; + } else { + // TODO: unlikly (https://doc.rust-lang.org/nightly/std/intrinsics/fn.unlikely.html) + return Err(Error::from_raw_os_error(libc::EINVAL)); + } + } + + Ok(addr) +} + +// Ext traits +#[cfg(feature = "pnet")] +pub trait MacAddrLinxExt: From<[u8; 6]> { + fn from_interface(interface: &str) -> Result; +} + +#[cfg(feature = "pnet")] +impl MacAddrLinxExt for MacAddr { + fn from_interface(interface: &str) -> Result { + let tool = IpTool::new()?; + + let hwaddr = tool.get_mac_sa_data(interface)?; + //let hwaddr: [u8; 6] = hwaddr.try_into()?; + let hwaddr = unsafe { *(&hwaddr as *const _ as *const [u8; 6]) }; + + let hwaddr: [u8; 6] = hwaddr.into(); + + Ok(hwaddr.into()) + } +} + +#[cfg(target_os = "linux")] +pub use linux::IpAddrLinkExt; + +#[cold] +#[inline] +#[allow(dead_code)] // not used yet on non linux +/// Own (cold) function to optimize if statements +fn last_err() -> Error { + Error::last_os_error() +} + +#[cfg(test)] +mod test { + + #[test] + #[allow(overflowing_literals)] + fn parse_mac_addr() { + let addr = "5A:E6:60:8F:5F:DE"; + let mut addr_vec: [libc::c_char; 14] = [0; 14]; + addr_vec[0] = 0x5A; + addr_vec[1] = 0xE6; + addr_vec[2] = 0x60; + addr_vec[3] = 0x8F; + addr_vec[4] = 0x5F; + addr_vec[5] = 0xDE; + + assert_eq!(super::parse_mac_addr(addr).unwrap(), addr_vec); + + // not long enough address + super::parse_mac_addr("5A:3B:2D").unwrap_err(); + } + + #[cfg(feature = "pnet")] + #[test] + fn macaddr_from_interface() { + use super::MacAddrLinxExt; + + assert!(pnet::util::MacAddr::from_interface("lo").unwrap().is_zero()); + } +} diff --git a/runner/iptool/src/linux.rs b/runner/iptool/src/linux.rs new file mode 100644 index 0000000..20ebd6a --- /dev/null +++ b/runner/iptool/src/linux.rs @@ -0,0 +1,369 @@ +use std::io::{Error, Result}; +use std::net::{IpAddr, Ipv4Addr}; + +use libc::{ + c_int, c_short, c_uchar, close, in6_addr, ioctl, sockaddr, sockaddr_in, sockaddr_in6, socket, +}; + +use super::{copy_slice, last_err, parse_mac_addr, IpTool}; + +pub const SIOCGIFINDEX: u64 = 0x8933; + +// Helper traits +pub trait IpAddrLinkExt +where + Self: Sized, +{ + fn from_interface(dev: &str) -> Result; +} + +impl IpAddrLinkExt for Ipv4Addr { + fn from_interface(dev: &str) -> Result { + let iptool = IpTool::new()?; + + iptool.get_address(dev) + } +} + +impl IpTool { + pub fn new() -> Result { + let fd = Self::get_ctl_fd()?; + + Ok(Self { fd }) + } + + pub fn set_up(&self, dev: &str, up: bool) -> Result<()> { + let mut ifr = Ifreq::new(dev); + + ifr.ioctl(&self, libc::SIOCGIFFLAGS as _)?; + + let flag_val = libc::IFF_UP as i16; + + // SAFETY: union + unsafe { + ifr.ifr_ifru.ifru_flags = if up { + ifr.ifr_ifru.ifru_flags | flag_val + } else { + ifr.ifr_ifru.ifru_flags & (!flag_val) + }; + } + + ifr.ioctl(&self, libc::SIOCSIFFLAGS as _)?; + + if self.get_up(dev)? != up { + return Err(Error::from_raw_os_error(libc::ENOTRECOVERABLE)); + } + + Ok(()) + } + + pub fn get_up(&self, dev: &str) -> Result { + let mut ifr = Ifreq::new(dev); + + ifr.ioctl(&self, libc::SIOCGIFFLAGS as _)?; + + // unions + let flags: i16 = unsafe { ifr.ifr_ifru.ifru_flags }; + Ok((flags & libc::IFF_UP as i16) == 1) + } + + pub fn set_mtu(&self, dev: &str, mtu: u32) -> Result<()> { + let mut ifr = Ifreq::new(dev); + ifr.ifr_ifru.ifru_mtu = mtu as i32; + + ifr.ioctl(&self, libc::SIOCSIFMTU as _)?; + + Ok(()) + } + + pub fn get_mtu(&self, dev: &str) -> Result { + let mut ifr = Ifreq::new(dev); + + ifr.ioctl(&self, libc::SIOCGIFMTU as _)?; + + let mtu = unsafe { ifr.ifr_ifru.ifru_mtu as u32 }; + Ok(mtu) + } + + pub fn get_index(&self, dev: &str) -> Result { + let mut ifr = Ifreq::new(dev); + + ifr.ioctl(&self, SIOCGIFINDEX as _)?; + + Ok(unsafe { ifr.ifr_ifru.ifru_ivalue }) + } + + pub fn set_address(&self, dev: &str, address: &IpAddr, prefix_length: u32) -> Result<()> { + let index = self.get_index(dev)?; + match address { + IpAddr::V4(addr) => { + if prefix_length > 32 { + return Err(Error::from_raw_os_error(libc::EINVAL)); + } + + let mut ifr = Ifreq::new(dev); + ifr.ifr_ifru.ifru_addr_v4.sin_family = libc::AF_INET as _; + ifr.ifr_ifru.ifru_addr_v4.sin_addr.s_addr = u32::from_ne_bytes(addr.octets()); + + ifr.ioctl(&self, libc::SIOCSIFADDR as _)?; + + ifr.ifr_ifru.ifru_addr_v4.sin_addr.s_addr = u32::MAX >> (32 - prefix_length); + + ifr.ioctl(&self, libc::SIOCSIFNETMASK as _)?; + } + IpAddr::V6(addr) => { + let mut ifr = Ifreq6 { + prefix_length, + ifindex: index as _, + addr: in6_addr { + s6_addr: addr.octets(), + }, + }; + ifr.ioctl(&self, libc::SIOCSIFADDR as _)?; + } + } + + Ok(()) + //let mut ifr = Ifreq::new(dev); + /*match address { + IpAddr::V4(addr) => { + ifr.ifr_ifru.ifru_addr_v4.sin_family = libc::AF_INET as libc::sa_family_t; + ifr.ifr_ifru.ifru_addr_v4.sin_addr.s_addr = u32::from_ne_bytes(addr.octets()); + } + }*/ + + /*let res = unsafe { libc::ioctl(self.fd, libc::SIOCSIFADDR as _, &mut ifr) };*/ + } + + pub fn get_address(&self, dev: &str) -> Result { + let mut ifr = Ifreq::new(dev); + + ifr.ioctl(&self, libc::SIOCGIFADDR as _)?; + + // SAFETY: union + let addr: Ipv4Addr = unsafe { + if ifr.ifr_ifru.ifru_addr_v4.sin_family != libc::AF_INET as _ { + return Err(Error::from_raw_os_error(libc::ENOTRECOVERABLE)); + } + ifr.ifr_ifru.ifru_addr_v4.sin_addr.s_addr + } + .into(); + + Ok(addr) + } + + pub fn set_mac(&self, dev: &str, mac: &str) -> Result<()> { + self.set_mac_sa_data(dev, parse_mac_addr(mac)?) + } + pub fn set_mac_sa_data(&self, dev: &str, mac: [libc::c_char; 14]) -> Result<()> { + let mut ifr = Ifreq::new(dev); + ifr.ifr_ifru.ifru_hwaddr.sa_family = libc::ARPHRD_ETHER; + ifr.ifr_ifru.ifru_hwaddr.sa_data = mac; + + ifr.ioctl(&self, libc::SIOCSIFHWADDR as _) + } + + pub fn get_mac_sa_data(&self, dev: &str) -> Result<[libc::c_char; 14]> { + let mut ifr = Ifreq::new(dev); + ifr.ifr_ifru.ifru_hwaddr.sa_family = libc::ARPHRD_ETHER; + + ifr.ioctl(&self, libc::SIOCGIFHWADDR as _)?; + + let sa_data = unsafe { ifr.ifr_ifru.ifru_hwaddr.sa_data }; + Ok(sa_data) + } + // TODO: get_mac -> String + + /// Get the hardware type of the given interface + pub fn get_arptype(&self, dev: &str) -> Result { + let mut ifr = Ifreq::new(dev); + + ifr.ioctl(&self, libc::SIOCGIFHWADDR as _)?; + + let arptype = unsafe { ifr.ifr_ifru.ifru_hwaddr.sa_family }; + return Ok(arptype); + } + + fn get_ctl_fd() -> Result { + let fd = unsafe { socket(libc::PF_INET, libc::SOCK_DGRAM, 0) }; + if fd >= 0 { + return Ok(fd); + } + let error = std::io::Error::last_os_error(); + let fd = unsafe { socket(libc::PF_PACKET, libc::SOCK_DGRAM, 0) }; + if fd >= 0 { + return Ok(fd); + } + let fd = unsafe { socket(libc::PF_INET6, libc::SOCK_DGRAM, 0) }; + if fd >= 0 { + return Ok(fd); + } + Err(error) + } +} + +impl Drop for IpTool { + fn drop(&mut self) { + unsafe { close(self.fd) }; + } +} + +#[repr(C)] +union IfrIfru { + ifru_addr: sockaddr, + ifru_hwaddr: sockaddr, + ifru_addr_v4: sockaddr_in, + ifru_addr_v6: sockaddr_in6, + ifru_dstaddr: sockaddr, + ifru_broadaddr: sockaddr, + ifru_flags: c_short, + ifru_metric: c_int, + ifru_ivalue: c_int, + ifru_mtu: c_int, + ifru_phys: c_int, + ifru_media: c_int, + ifru_intval: c_int, + //ifru_data: caddr_t, + //ifru_devmtu: ifdevmtu, + //ifru_kpi: ifkpi, + ifru_wake_flags: u32, + ifru_route_refcnt: u32, + ifru_cap: [c_int; 2], + ifru_functional_type: u32, +} + +trait IoctlReq { + fn ioctl(&mut self, iptool: &IpTool, request: libc::c_ulong) -> Result<()> { + let res = unsafe { ioctl(iptool.fd, request as _, self) }; + if res < 0 { + Err(last_err()) + } else { + Ok(()) + } + } +} + +#[repr(C)] +pub struct Ifreq { + ifr_name: [c_uchar; libc::IFNAMSIZ], + ifr_ifru: IfrIfru, +} + +impl Ifreq { + pub fn new(dev: &str) -> Self { + //let mut ifr_name = [0; libc::IF_NAMESIZE]; + + //ifr_name[..dev.len()].copy_from_slice(dev.as_bytes().as_ref()); + + let s: [u8; core::mem::size_of::()] = [0; core::mem::size_of::()]; + let mut s: Self = unsafe { core::mem::transmute(s) }; + + copy_slice(&mut s.ifr_name, dev.as_bytes()); + + s + /*Self { + ifr_name, + ifr_ifru: IfrIfru { ifru_flags: 0 }, + }*/ + } +} + +impl IoctlReq for Ifreq {} + +#[repr(C)] +pub struct Ifreq6 { + addr: in6_addr, + prefix_length: u32, + ifindex: libc::c_uint, +} + +impl IoctlReq for Ifreq6 {} + +#[cfg(test)] +mod test { + use super::IpTool; + use std::net::{IpAddr, Ipv4Addr}; + + const TEST_INTERFACE: &str = "loop1"; + #[test] + #[ignore] + fn down() { + let ip_tool = IpTool::new().unwrap(); + + ip_tool.set_up(TEST_INTERFACE, false).unwrap(); + } + + #[test] + #[ignore] + fn up() { + let ip_tool = IpTool::new().unwrap(); + + ip_tool.set_up(TEST_INTERFACE, true).unwrap(); + } + + #[test] + #[ignore] + fn sleep_down_and_up() { + let ip_tool = IpTool::new().unwrap(); + + ip_tool.set_up(TEST_INTERFACE, false).unwrap(); + + std::thread::sleep(std::time::Duration::from_secs(5)); + + ip_tool.set_up(TEST_INTERFACE, true).unwrap(); + } + + #[test] + #[ignore] + fn mtu() { + let ip_tool = IpTool::new().unwrap(); + + ip_tool.set_mtu(TEST_INTERFACE, 1420).unwrap(); + + assert_eq!(ip_tool.get_mtu(TEST_INTERFACE).unwrap(), 1420); + } + + #[test] + #[ignore] + fn mac() { + let ip_tool = IpTool::new().unwrap(); + let mac = "5A:E6:60:8F:5F:DE"; + + ip_tool.set_mac(TEST_INTERFACE, mac).unwrap(); + + let sa_data = ip_tool.get_mac_sa_data(TEST_INTERFACE).unwrap(); + assert_eq!(sa_data, super::parse_mac_addr(mac).unwrap()); + } + + #[test] + #[ignore] + fn set_ipv4() { + let ip_tool = IpTool::new().unwrap(); + let address: Ipv4Addr = "10.23.42.1".parse().unwrap(); + + ip_tool + .set_address(TEST_INTERFACE, &IpAddr::V4(address), 24) + .unwrap(); + + let addr_on_link = ip_tool.get_address(TEST_INTERFACE).unwrap(); + assert_eq!(address, addr_on_link); + } + + #[test] + #[ignore] + fn ipv4addr_link_ext() { + use super::IpAddrLinkExt; + let address: Ipv4Addr = "10.23.42.1".parse().unwrap(); + + let addr_if = Ipv4Addr::from_interface(TEST_INTERFACE).unwrap(); + + assert_eq!(address, addr_if); + } + + #[test] + fn get_arptype() { + let iptool = IpTool::new().unwrap(); + let arptype = iptool.get_arptype("lo").unwrap(); + assert_eq!(arptype, libc::ARPHRD_LOOPBACK); + } +} diff --git a/runner/rust-toolchain.toml b/runner/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/runner/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/runner/src/main.rs b/runner/src/main.rs new file mode 100644 index 0000000..4bd1752 --- /dev/null +++ b/runner/src/main.rs @@ -0,0 +1,33 @@ +#![feature(io_error_uncategorized)] + +use iptool::IpTool; +use std::{io, net::Ipv4Addr, process::Command, str::FromStr}; + +use capctl::{Cap, FileCaps}; + +fn main() { + Command::new("cargo") + .arg("build") + .spawn() + .unwrap() + .wait() + .unwrap(); + let mut caps = FileCaps::empty(); + caps.effective = true; + caps.permitted.add(Cap::NET_ADMIN); + caps.permitted.add(Cap::NET_RAW); + caps.set_for_file("target/debug/nstack-rs").unwrap(); + let mut nstack = Command::new("target/debug/nstack-rs").spawn().unwrap(); + let iptool = IpTool::new().unwrap(); + while let Err(x) = + iptool.set_address("tap0", &(Ipv4Addr::from_str("10.0.0.2").unwrap().into()), 8) + { + if x.kind() == io::ErrorKind::Uncategorized { + continue; + } else { + Err::<(), _>(x).unwrap(); + } + } + iptool.set_up("tap0", true).unwrap(); + nstack.wait().unwrap(); +} diff --git a/src/arp.rs b/src/arp.rs new file mode 100644 index 0000000..a26cd60 --- /dev/null +++ b/src/arp.rs @@ -0,0 +1,217 @@ +use std::{ + cell::RefCell, + collections::{hash_map::Entry, HashMap}, + fmt::Display, + net::Ipv4Addr, +}; + +use derive_try_from_primitive::TryFromPrimitive; + +use crate::{ + ethernet::{EthernetHeader, MacAddr}, + iface::Iface, + packet::{Header, HeaderParse, Packet}, +}; + +#[derive(Copy, Clone, Debug)] +pub enum ArpParseError { + HardwareType(u16), + ProtocolType(u16), + ProtocolAddressLen(u8), + HardwareAddressLen(u8), + Opcode(u16), +} + +#[derive(Copy, Clone, Debug, TryFromPrimitive, PartialEq, Eq)] +#[repr(u16)] +pub enum ArpOpcode { + Request = 1, + Reply = 2, +} + +#[derive(Copy, Clone, Debug)] +pub struct ArpMessage { + op: ArpOpcode, + sha: MacAddr, + spa: Ipv4Addr, + tha: MacAddr, + tpa: Ipv4Addr, +} + +impl Display for ArpMessage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.op { + ArpOpcode::Request => f.write_fmt(format_args!( + "ARP request from {} ({}) for {}", + self.spa, self.sha, self.tpa, + )), + ArpOpcode::Reply => f.write_fmt(format_args!( + "ARP response for {} ({}) to {} ({})", + self.spa, self.sha, self.tpa, self.tha, + )), + } + } +} + +impl ArpMessage { + pub fn new(op: ArpOpcode, sha: MacAddr, spa: Ipv4Addr, tha: MacAddr, tpa: Ipv4Addr) -> Self { + Self { + op, + sha, + spa, + tha, + tpa, + } + } + + #[allow(dead_code)] + pub fn op(&self) -> ArpOpcode { + self.op + } + + #[allow(dead_code)] + pub fn sha(&self) -> MacAddr { + self.sha + } + + #[allow(dead_code)] + pub fn spa(&self) -> Ipv4Addr { + self.spa + } + + #[allow(dead_code)] + pub fn tha(&self) -> MacAddr { + self.tha + } + + #[allow(dead_code)] + pub fn tpa(&self) -> Ipv4Addr { + self.tpa + } +} + +impl Header for ArpMessage { + fn serialize_stage1(&self, buf: &mut Vec) { + buf.extend_from_slice(&1u16.to_be_bytes()); + buf.extend_from_slice(&0x800u16.to_be_bytes()); + buf.push(6); + buf.push(4); + buf.extend_from_slice(&(self.op as u16).to_be_bytes()); + buf.extend_from_slice(self.sha.octets()); + buf.extend_from_slice(&self.spa.octets()); + buf.extend_from_slice(self.tha.octets()); + buf.extend_from_slice(&self.tpa.octets()); + } +} + +impl HeaderParse for ArpMessage { + type ParseError = ArpParseError; + fn parse(message: &[u8]) -> Result<(Self, &[u8]), ArpParseError> { + let hrd = u16::from_be_bytes((&message[0..2]).try_into().unwrap()); + if hrd != 1 { + return Err(ArpParseError::HardwareType(hrd)); + } + let pro = u16::from_be_bytes((&message[2..4]).try_into().unwrap()); + if pro != 0x800 { + return Err(ArpParseError::ProtocolType(pro)); + } + let hln = u8::from_be_bytes((&message[4..5]).try_into().unwrap()); + if hln != 6 { + return Err(ArpParseError::HardwareAddressLen(hln)); + } + let pln = u8::from_be_bytes((&message[5..6]).try_into().unwrap()); + if pln != 4 { + return Err(ArpParseError::ProtocolAddressLen(pln)); + } + let op = ArpOpcode::try_from(u16::from_be_bytes((&message[6..8]).try_into().unwrap())) + .map_err(ArpParseError::Opcode)?; + let sha = MacAddr::new((&message[8..14]).try_into().unwrap()); + let spa = Ipv4Addr::from(<[u8; 4]>::try_from(&message[14..18]).unwrap()); + let tha = MacAddr::new((&message[18..24]).try_into().unwrap()); + let tpa = Ipv4Addr::from(<[u8; 4]>::try_from(&message[24..28]).unwrap()); + Ok(( + Self { + op, + sha, + spa, + tha, + tpa, + }, + &[], + )) + } +} + +type ArpCallback<'a> = Box; + +pub struct ArpClient<'a> { + iface: &'a Iface, + cache: RefCell>, + waiting: RefCell>>>, +} + +impl<'a> ArpClient<'a> { + pub fn new(iface: &'a Iface) -> Self { + Self { + iface, + cache: RefCell::new(HashMap::new()), + waiting: RefCell::new(HashMap::new()), + } + } + + pub fn handle_message(&self, message: Packet) { + match message.l3().op() { + ArpOpcode::Request => { + if let Some(ip) = self.iface.ip() { + if message.l3().tpa() == ip { + self.iface.send( + message.l3().sha(), + Packet::new(Vec::new()).set_l3(ArpMessage::new( + ArpOpcode::Reply, + self.iface.mac(), + ip, + message.l3().sha(), + message.l3().spa(), + )), + ); + } + } + } + ArpOpcode::Reply => { + self.cache + .borrow_mut() + .insert(message.l3().spa(), message.l3().sha()); + if let Some(callback_vec) = self.waiting.borrow_mut().remove(&message.l3().spa()) { + for callback in callback_vec { + callback(message.l3().sha()); + } + } + } + } + } + + pub fn get_mac(&self, ip: Ipv4Addr, callback: Box) { + if let Some(&mac) = self.cache.borrow().get(&ip) { + callback(mac); + } else { + match self.waiting.borrow_mut().entry(ip) { + Entry::Vacant(e) => { + e.insert(vec![callback]); + self.iface.send( + MacAddr::broadcast(), + Packet::new(Vec::new()).set_l3(ArpMessage::new( + ArpOpcode::Request, + self.iface.mac(), + self.iface.ip().unwrap_or(Ipv4Addr::new(0, 0, 0, 0)), + MacAddr::broadcast(), + ip, + )), + ); + } + Entry::Occupied(mut e) => { + e.get_mut().push(callback); + } + } + } + } +} diff --git a/src/ethernet.rs b/src/ethernet.rs new file mode 100644 index 0000000..f22f610 --- /dev/null +++ b/src/ethernet.rs @@ -0,0 +1,124 @@ +use std::fmt::Display; + +use derive_try_from_primitive::TryFromPrimitive; + +use crate::{ + arp::ArpMessage, + from_header_type_impl, + ip::IpHeader, + packet::{Header, HeaderParse}, +}; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct MacAddr([u8; 6]); + +impl Display for MacAddr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for byte in &self.0[0..5] { + f.write_fmt(format_args!("{:0>2x}:", byte))?; + } + f.write_fmt(format_args!("{:0>2x}", self.0[5]))?; + Ok(()) + } +} + +impl MacAddr { + pub fn new(mac: [u8; 6]) -> MacAddr { + Self(mac) + } + + pub fn is_broadcast(&self) -> bool { + self == &Self::broadcast() + } + + pub fn octets(&self) -> &[u8] { + &self.0 + } + + pub fn broadcast() -> MacAddr { + Self([0xFFu8; 6]) + } +} + +#[derive(TryFromPrimitive)] +#[repr(u16)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum EtherType { + Ipv4 = 0x800, + Arp = 0x806, +} + +impl Display for EtherType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Ipv4 => f.write_str("IPv4"), + Self::Arp => f.write_str("ARP"), + } + } +} + +from_header_type_impl!(EtherType, IpHeader => Self::Ipv4, ArpMessage => Self::Arp); + +#[derive(Copy, Clone, Debug)] +pub struct InvalidEtherType(pub u16); + +#[derive(Debug)] +pub struct EthernetHeader { + dst: MacAddr, + src: MacAddr, + typ: EtherType, +} + +impl Display for EthernetHeader { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!( + "{} from {} to {}", + self.typ, self.src, self.dst, + )) + } +} + +impl EthernetHeader { + #[allow(dead_code)] + pub fn new(dst: MacAddr, src: MacAddr, typ: EtherType) -> Self { + Self { dst, src, typ } + } + + #[allow(dead_code)] + pub fn dst(&self) -> MacAddr { + self.dst + } + + #[allow(dead_code)] + pub fn src(&self) -> MacAddr { + self.src + } + + #[allow(dead_code)] + pub fn typ(&self) -> EtherType { + self.typ + } +} + +impl Header for EthernetHeader { + fn serialize_stage1(&self, buf: &mut Vec) { + buf.extend_from_slice(self.dst.octets()); + buf.extend_from_slice(self.src.octets()); + buf.extend_from_slice(&(self.typ as u16).to_be_bytes()); + } +} + +impl HeaderParse for EthernetHeader { + type ParseError = InvalidEtherType; + fn parse(frame: &[u8]) -> Result<(Self, &[u8]), InvalidEtherType> { + Ok(( + Self { + dst: MacAddr(frame[0..6].try_into().unwrap()), + src: MacAddr(frame[6..12].try_into().unwrap()), + typ: EtherType::try_from(u16::from_be_bytes(frame[12..14].try_into().unwrap())) + .map_err(InvalidEtherType)?, + }, + &frame[14..], + )) + } +} diff --git a/src/icmp.rs b/src/icmp.rs new file mode 100644 index 0000000..46f115a --- /dev/null +++ b/src/icmp.rs @@ -0,0 +1,94 @@ +use crate::packet::{Header, HeaderParse}; +use derive_try_from_primitive::TryFromPrimitive; +use internet_checksum::Checksum; +use std::fmt::Display; + +#[derive(Debug, Copy, Clone)] +pub struct InvalidType(pub u8); + +#[derive(Debug, Copy, Clone, TryFromPrimitive)] +#[repr(u8)] +enum IcmpHeaderType { + EchoRequest = 8, + EchoReply = 0, +} + +#[derive(Debug)] +pub enum IcmpHeader { + EchoRequest { id: u16, seq: u16 }, + EchoReply { id: u16, seq: u16 }, +} + +impl IcmpHeader { + fn typ(&self) -> IcmpHeaderType { + match self { + Self::EchoRequest { .. } => IcmpHeaderType::EchoRequest, + Self::EchoReply { .. } => IcmpHeaderType::EchoReply, + } + } +} + +impl Display for IcmpHeader { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::EchoRequest { id, seq } => f.write_fmt(format_args!( + "ICMP Echo Request with ID {} and seq number {}", + id, seq, + )), + Self::EchoReply { id, seq } => f.write_fmt(format_args!( + "ICMP Echo Reply with ID {} and seq number {}.", + id, seq, + )), + } + } +} + +impl Header for IcmpHeader { + fn serialize_stage1(&self, buf: &mut Vec) { + buf.extend_from_slice(&[self.typ() as u8, 0]); + buf.extend_from_slice(&[0, 0]); // Filled with checksum in stage 2 + match self { + Self::EchoRequest { id, seq } => { + buf.extend_from_slice(&id.to_be_bytes()); + buf.extend_from_slice(&seq.to_be_bytes()); + } + Self::EchoReply { id, seq } => { + buf.extend_from_slice(&id.to_be_bytes()); + buf.extend_from_slice(&seq.to_be_bytes()); + } + } + } + + fn serialize_stage2(hdr: &mut [u8], data: &[u8]) { + let mut checksum = Checksum::new(); + checksum.add_bytes(hdr); + checksum.add_bytes(data); + hdr[2..4].copy_from_slice(&checksum.checksum()); + } +} + +impl HeaderParse for IcmpHeader { + type ParseError = InvalidType; + fn parse(message: &[u8]) -> Result<(Self, &[u8]), InvalidType> { + let typ = IcmpHeaderType::try_from(message[0]).map_err(InvalidType)?; + let _code = message[1]; + let rest = &message[4..]; + match typ { + IcmpHeaderType::EchoRequest => Ok(( + Self::EchoRequest { + id: u16::from_be_bytes(rest[0..2].try_into().unwrap()), + seq: u16::from_be_bytes(rest[2..4].try_into().unwrap()), + }, + &rest[4..], + )), + + IcmpHeaderType::EchoReply => Ok(( + Self::EchoReply { + id: u16::from_be_bytes(rest[0..2].try_into().unwrap()), + seq: u16::from_be_bytes(rest[2..4].try_into().unwrap()), + }, + &rest[4..], + )), + } + } +} diff --git a/src/iface.rs b/src/iface.rs new file mode 100644 index 0000000..8ee98a5 --- /dev/null +++ b/src/iface.rs @@ -0,0 +1,62 @@ +use std::{cell::Cell, net::Ipv4Addr}; + +use tun_tap::Mode; + +use crate::{ + ethernet::{EtherType, EthernetHeader, InvalidEtherType, MacAddr}, + packet::{FromHeaderType, Header, NoHeader, OptionalHeader, Packet}, +}; + +pub struct Iface { + iface: tun_tap::Iface, + mac: MacAddr, + ip: Cell>, +} + +impl Iface { + pub fn new() -> Iface { + Self { + iface: tun_tap::Iface::without_packet_info("tap0", Mode::Tap).unwrap(), + mac: MacAddr::new([0x9A, 0xB1, 0xB5, 0x22, 0x38, 0xAB]), + ip: Cell::new(None), + } + } + + pub fn recv(&self) -> Result, InvalidEtherType> { + let mut frame_buf = [0u8; 2048]; + let frame_len = self.iface.recv(&mut frame_buf).unwrap(); + let packet = Packet::new(frame_buf[0..frame_len].to_vec()); + packet.parse::().map_err(|(_pkt, err)| err) + } + + pub fn mac_to_self(&self, mac: MacAddr) -> bool { + mac == self.mac || mac.is_broadcast() + } + + pub fn send(&self, dst: MacAddr, packet: Packet) + where + L3: Header, + L4: OptionalHeader, + L7: OptionalHeader, + EtherType: FromHeaderType, + { + let eth_header = EthernetHeader::new(dst, self.mac, EtherType::from_hdr_type()); + let packet = packet.set_l2(eth_header); + println!("------------------"); + println!("{}", packet); + println!("------------------"); + self.iface.send(&packet.serialize()).unwrap(); + } + + pub fn mac(&self) -> MacAddr { + self.mac + } + + pub fn ip(&self) -> Option { + self.ip.get() + } + + pub fn set_ip(&self, ip: Ipv4Addr) { + self.ip.set(Some(ip)) + } +} diff --git a/src/ip.rs b/src/ip.rs new file mode 100644 index 0000000..ab1bf22 --- /dev/null +++ b/src/ip.rs @@ -0,0 +1,170 @@ +use std::{fmt::Display, net::Ipv4Addr, rc::Rc}; + +use derive_try_from_primitive::TryFromPrimitive; + +use crate::{ + arp::ArpClient, + from_header_type_impl, + icmp::IcmpHeader, + iface::Iface, + packet::{FromHeaderType, Header, HeaderParse, NoHeader, OptionalHeader, Packet}, +}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ParseError { + FragmentsUnsupported, + OptionsUnsupported, + InvalidVersion, + InvaliidProtocol(u8), +} + +#[repr(u8)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive)] +pub enum IpProtocolType { + Icmp = 1, + Tcp = 6, + Udp = 17, +} + +impl Display for IpProtocolType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Icmp => f.write_str("ICMP"), + Self::Tcp => f.write_str("TCP"), + Self::Udp => f.write_str("UDP"), + } + } +} + +from_header_type_impl!(IpProtocolType, IcmpHeader => Self::Icmp); + +#[derive(Debug)] +pub struct IpHeader { + ttl: u8, + protocol: IpProtocolType, + src: Ipv4Addr, + dst: Ipv4Addr, +} + +impl Display for IpHeader { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!( + "IP datagram from {} to {} ({})", + self.src, self.dst, self.protocol, + )) + } +} + +impl IpHeader { + pub fn new(protocol: IpProtocolType, src: Ipv4Addr, dst: Ipv4Addr) -> Self { + Self { + ttl: 255, + protocol, + src, + dst, + } + } + + #[allow(dead_code)] + pub fn ttl(&self) -> u8 { + self.ttl + } + + #[allow(dead_code)] + pub fn protocol(&self) -> IpProtocolType { + self.protocol + } + + #[allow(dead_code)] + pub fn dst(&self) -> Ipv4Addr { + self.dst + } + + #[allow(dead_code)] + pub fn src(&self) -> Ipv4Addr { + self.src + } +} + +impl Header for IpHeader { + fn serialize_stage1(&self, buf: &mut Vec) { + buf.extend_from_slice(&[0x45, 0]); + buf.extend_from_slice(&[0, 0]); // Filled with length in stage 2 + buf.extend_from_slice(&[0, 0]); + buf.extend_from_slice(&[0, 0]); + buf.push(self.ttl); + buf.push(self.protocol as u8); + buf.extend_from_slice(&[0, 0]); // Filled with checksum in stage 2 + buf.extend_from_slice(&self.src.octets()); + buf.extend_from_slice(&self.dst.octets()); + } + + fn serialize_stage2(hdr: &mut [u8], data: &[u8]) { + hdr[2..4].copy_from_slice(&(20u16 + data.len() as u16).to_be_bytes()); + let checksum = internet_checksum::checksum(hdr); + hdr[10..12].copy_from_slice(&checksum); + } +} + +impl HeaderParse for IpHeader { + type ParseError = ParseError; + fn parse(datagram: &[u8]) -> Result<(Self, &[u8]), ParseError> { + if datagram[0] & 0xf0 != 0x40 { + return Err(ParseError::InvalidVersion); + } + if datagram[0] & 0xf != 0x5 { + return Err(ParseError::OptionsUnsupported); + } + if datagram[6] & 0x20 != 0x0 { + return Err(ParseError::FragmentsUnsupported); + } + let ttl = datagram[8]; + let protocol = + IpProtocolType::try_from(datagram[9]).map_err(ParseError::InvaliidProtocol)?; + let src = Ipv4Addr::from(<[u8; 4]>::try_from(&datagram[12..16]).unwrap()); + let dst = Ipv4Addr::from(<[u8; 4]>::try_from(&datagram[16..20]).unwrap()); + Ok(( + Self { + ttl, + protocol, + src, + dst, + }, + &datagram[20..], + )) + } +} + +pub struct IpSender<'a, 'b> { + iface: Rc, + arp_client: &'a ArpClient<'b>, +} + +impl<'a, 'b> IpSender<'a, 'b> { + pub fn new(iface: Rc, arp_client: &'a ArpClient<'b>) -> Self { + Self { iface, arp_client } + } + + pub fn send(&self, dst: Ipv4Addr, packet: Packet) + where + L4: Header, + L7: OptionalHeader, + IpProtocolType: FromHeaderType, + { + let header = IpHeader::new( + IpProtocolType::from_hdr_type(), + self.iface.ip().unwrap(), + dst, + ); + let packet = packet.set_l3(header); + let iface = Rc::downgrade(&self.iface); + self.arp_client.get_mac( + dst, + Box::new(move |mac| { + if let Some(iface) = iface.upgrade() { + iface.send(mac, packet); + } + }), + ); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..c36b898 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,67 @@ +mod arp; +mod ethernet; +mod icmp; +mod iface; +mod ip; +mod packet; + +use std::{net::Ipv4Addr, rc::Rc}; + +use arp::ArpClient; +use iface::Iface; +use ip::{IpHeader, IpSender}; + +use crate::{ + arp::ArpMessage, + ethernet::{EtherType, InvalidEtherType}, + icmp::IcmpHeader, + ip::IpProtocolType, + packet::Packet, +}; + +fn main() { + let iface = Rc::new(Iface::new()); + iface.set_ip(Ipv4Addr::new(10, 0, 0, 1)); + let arp_client = ArpClient::new(&iface); + let ip_sender = IpSender::new(iface.clone(), &arp_client); + loop { + let packet = match iface.recv() { + Ok(x) => x, + Err(InvalidEtherType(_typ)) => { + continue; + } + }; + if !iface.mac_to_self(packet.l2().dst()) { + continue; + } + match packet.l2().typ() { + EtherType::Arp => { + let packet = packet.parse::().unwrap(); + println!("------------------"); + println!("{}", packet); + println!("------------------"); + arp_client.handle_message(packet); + } + EtherType::Ipv4 => { + let packet = packet.parse::().unwrap(); + #[allow(clippy::single_match)] + match packet.l3().protocol() { + IpProtocolType::Icmp => { + let packet = packet.parse::().unwrap(); + println!("------------------"); + println!("{}", packet); + println!("------------------"); + if let &IcmpHeader::EchoRequest { id, seq } = packet.l4() { + ip_sender.send( + packet.l3().src(), + Packet::new(packet.body().to_vec()) + .set_l4(IcmpHeader::EchoReply { id, seq }), + ); + } + } + _ => (), + } + } + }; + } +} diff --git a/src/packet.rs b/src/packet.rs new file mode 100644 index 0000000..ee6b861 --- /dev/null +++ b/src/packet.rs @@ -0,0 +1,306 @@ +#![allow(clippy::transmute_ptr_to_ref)] // Shut up a warning generated by mopafy! + +use std::fmt::{Debug, Display}; + +pub trait FromHeaderType { + fn from_hdr_type() -> Self; +} + +#[macro_export] +macro_rules! from_header_type_impl { + ($t: ty, $($ht: ty => $ex: expr),+) => { + $( + impl $crate::packet::FromHeaderType<$ht> for $t { + fn from_hdr_type() -> Self { + $ex + } + } + )+ + } +} + +pub trait Header: Display + Debug { + fn serialize_stage1(&self, buf: &mut Vec); + fn serialize_stage2(_hdr: &mut [u8], _data: &[u8]) {} +} + +pub trait HeaderParse: Header { + type ParseError; + fn parse(message: &[u8]) -> Result<(Self, &[u8]), Self::ParseError> + where + Self: Sized; +} + +pub trait OptionalHeader: Display + Debug { + fn is_some(&self) -> bool; + fn serialize_stage1(&self, buf: &mut Vec); + fn serialize_stage2(_hdr: &mut [u8], _data: &[u8]) {} +} + +impl OptionalHeader for T { + fn is_some(&self) -> bool { + true + } + fn serialize_stage1(&self, buf: &mut Vec) { + ::serialize_stage1(self, buf) + } + + fn serialize_stage2(hdr: &mut [u8], data: &[u8]) { + ::serialize_stage2(hdr, data) + } +} + +#[derive(Debug)] +pub struct NoHeader; + +impl Display for NoHeader { + fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Ok(()) + } +} + +impl OptionalHeader for NoHeader { + fn is_some(&self) -> bool { + false + } + fn serialize_stage1(&self, _buf: &mut Vec) {} +} + +#[derive(Debug)] +pub struct Packet< + L2: OptionalHeader = NoHeader, + L3: OptionalHeader = NoHeader, + L4: OptionalHeader = NoHeader, + L7: OptionalHeader = NoHeader, +> { + l2: L2, + l3: L3, + l4: L4, + l7: L7, + body: Vec, +} + +impl Display + for Packet +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut printed = false; + if self.l2.is_some() { + f.write_fmt(format_args!("{}", self.l2))?; + printed = true; + } + if self.l3.is_some() { + if printed { + f.write_str("\n")?; + } + f.write_fmt(format_args!("{}", self.l3))?; + printed = true; + } + if self.l4.is_some() { + if printed { + f.write_str("\n")?; + } + f.write_fmt(format_args!("{}", self.l4))?; + printed = true; + } + if self.l7.is_some() { + if printed { + f.write_str("\n")?; + } + f.write_fmt(format_args!("{}", self.l7))?; + printed = true; + } + if !self.body.is_empty() { + if printed { + f.write_str("\n")?; + } + f.write_fmt(format_args!("{}", rhexdump::hexdump(&self.body)))?; + } + Ok(()) + } +} + +impl Packet { + pub fn new(body: Vec) -> Self { + Self { + l2: NoHeader, + l3: NoHeader, + l4: NoHeader, + l7: NoHeader, + body, + } + } +} +impl + Packet +{ + #[allow(dead_code)] + pub fn body(&self) -> &[u8] { + self.body.as_ref() + } + + #[allow(dead_code)] + pub fn set_l2(self, l2: T) -> Packet { + Packet { + l2, + l3: self.l3, + l4: self.l4, + l7: self.l7, + body: self.body, + } + } + + #[allow(dead_code)] + pub fn set_l3(self, l3: T) -> Packet { + Packet { + l2: self.l2, + l3, + l4: self.l4, + l7: self.l7, + body: self.body, + } + } + + #[allow(dead_code)] + pub fn set_l4(self, l4: T) -> Packet { + Packet { + l2: self.l2, + l3: self.l3, + l4, + l7: self.l7, + body: self.body, + } + } + + #[allow(dead_code)] + pub fn set_l7(self, l7: T) -> Packet { + Packet { + l2: self.l2, + l3: self.l3, + l4: self.l4, + l7, + body: self.body, + } + } + + #[allow(dead_code)] + pub fn serialize(&self) -> Vec { + let mut buf = Vec::new(); + self.l2.serialize_stage1(&mut buf); + let l3_start = buf.len(); + self.l3.serialize_stage1(&mut buf); + let l4_start = buf.len(); + self.l4.serialize_stage1(&mut buf); + let l7_start = buf.len(); + self.l7.serialize_stage1(&mut buf); + let body_start = buf.len(); + buf.extend_from_slice(&self.body); + let (l7_hdr, l7_body) = &mut buf[l7_start..].split_at_mut(body_start - l7_start); + L7::serialize_stage2(l7_hdr, l7_body); + let (l4_hdr, l4_body) = &mut buf[l4_start..].split_at_mut(l7_start - l4_start); + L4::serialize_stage2(l4_hdr, l4_body); + let (l3_hdr, l3_body) = &mut buf[l3_start..].split_at_mut(l4_start - l3_start); + L3::serialize_stage2(l3_hdr, l3_body); + let (l2_hdr, l2_body) = &mut buf.split_at_mut(l3_start); + L2::serialize_stage2(l2_hdr, l2_body); + buf + } +} + +impl + Packet +{ + #[allow(dead_code)] + pub fn l2(&self) -> &L2 { + &self.l2 + } +} +impl + Packet +{ + #[allow(dead_code)] + pub fn l3(&self) -> &L3 { + &self.l3 + } +} +impl + Packet +{ + #[allow(dead_code)] + pub fn l4(&self) -> &L4 { + &self.l4 + } +} +impl + Packet +{ + #[allow(dead_code)] + pub fn l7(&self) -> &L7 { + &self.l7 + } +} + +impl Packet { + #[allow(dead_code)] + pub fn parse(self) -> Result, (Self, T::ParseError)> { + match T::parse(&self.body) { + Ok((header, body)) => Ok(Packet { + l2: header, + l3: self.l3, + l4: self.l4, + l7: self.l7, + body: body.to_vec(), + }), + Err(err) => Err((self, err)), + } + } +} + +impl Packet { + #[allow(dead_code)] + pub fn parse(self) -> Result, (Self, T::ParseError)> { + match T::parse(&self.body) { + Ok((header, body)) => Ok(Packet { + l2: self.l2, + l3: header, + l4: self.l4, + l7: self.l7, + body: body.to_vec(), + }), + Err(err) => Err((self, err)), + } + } +} + +impl Packet { + #[allow(dead_code)] + pub fn parse(self) -> Result, (Self, T::ParseError)> { + match T::parse(&self.body) { + Ok((header, body)) => Ok(Packet { + l2: self.l2, + l3: self.l3, + l4: header, + l7: self.l7, + body: body.to_vec(), + }), + Err(err) => Err((self, err)), + } + } +} + +impl Packet { + #[allow(dead_code)] + #[allow(clippy::type_complexity)] + pub fn parse(self) -> Result, (Self, T::ParseError)> { + match T::parse(&self.body) { + Ok((header, body)) => Ok(Packet { + l2: self.l2, + l3: self.l3, + l4: self.l4, + l7: header, + body: body.to_vec(), + }), + Err(err) => Err((self, err)), + } + } +}