Initial commit

This commit is contained in:
pjht 2022-12-05 12:13:32 -06:00
commit 32f496d3d0
27 changed files with 2710 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

49
2 Normal file
View File

@ -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<Ipv4Addr>,
}
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<EthernetFrame, InvalidEtherType> {
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<Ipv4Addr> {
self.ip
}
pub fn set_ip(&mut self, ip: Ipv4Addr) {
self.ip = Some(ip);
}
}

686
Cargo.lock generated Normal file
View File

@ -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",
]

13
Cargo.toml Normal file
View File

@ -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"

1
nstack-runner Symbolic link
View File

@ -0,0 +1 @@
runner/target/debug/runner

47
runner/Cargo.lock generated Normal file
View File

@ -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",
]

10
runner/Cargo.toml Normal file
View File

@ -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" }

View File

@ -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

1
runner/iptool/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

215
runner/iptool/Cargo.lock generated Normal file
View File

@ -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"

21
runner/iptool/Cargo.toml Normal file
View File

@ -0,0 +1,21 @@
[package]
name = "iptool"
version = "0.1.0"
authors = ["Finn Behrens <fin@nyantec.com>"]
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 }

8
runner/iptool/LICENSE Normal file
View File

@ -0,0 +1,8 @@
Copyright 2020 nyantec GmbH <oss@nyantec.com>
Authors:
Finn Behrens <fin@nyantec.com>
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.

3
runner/iptool/README.md Normal file
View File

@ -0,0 +1,3 @@
# IpTool
Rust linux iptool helpers for network interfaces.

View File

@ -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

25
runner/iptool/flake.lock Normal file
View File

@ -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
}

28
runner/iptool/flake.nix Normal file
View File

@ -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;
};
}

133
runner/iptool/src/lib.rs Normal file
View File

@ -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<Self>;
}
#[cfg(feature = "pnet")]
impl MacAddrLinxExt for MacAddr {
fn from_interface(interface: &str) -> Result<Self> {
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());
}
}

369
runner/iptool/src/linux.rs Normal file
View File

@ -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<Self>;
}
impl IpAddrLinkExt for Ipv4Addr {
fn from_interface(dev: &str) -> Result<Self> {
let iptool = IpTool::new()?;
iptool.get_address(dev)
}
}
impl IpTool {
pub fn new() -> Result<Self> {
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<bool> {
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<u32> {
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<c_int> {
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<Ipv4Addr> {
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<libc::sa_family_t> {
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<c_int> {
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::<Self>()] = [0; core::mem::size_of::<Self>()];
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);
}
}

View File

@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

33
runner/src/main.rs Normal file
View File

@ -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();
}

217
src/arp.rs Normal file
View File

@ -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<u8>) {
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<dyn FnOnce(MacAddr) + 'a>;
pub struct ArpClient<'a> {
iface: &'a Iface,
cache: RefCell<HashMap<Ipv4Addr, MacAddr>>,
waiting: RefCell<HashMap<Ipv4Addr, Vec<ArpCallback<'a>>>>,
}
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<EthernetHeader, ArpMessage>) {
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<dyn FnOnce(MacAddr) + 'a>) {
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);
}
}
}
}
}

124
src/ethernet.rs Normal file
View File

@ -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<u8>) {
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..],
))
}
}

94
src/icmp.rs Normal file
View File

@ -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<u8>) {
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..],
)),
}
}
}

62
src/iface.rs Normal file
View File

@ -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<Option<Ipv4Addr>>,
}
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<Packet<EthernetHeader>, 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::<EthernetHeader>().map_err(|(_pkt, err)| err)
}
pub fn mac_to_self(&self, mac: MacAddr) -> bool {
mac == self.mac || mac.is_broadcast()
}
pub fn send<L3, L4, L7>(&self, dst: MacAddr, packet: Packet<NoHeader, L3, L4, L7>)
where
L3: Header,
L4: OptionalHeader,
L7: OptionalHeader,
EtherType: FromHeaderType<L3>,
{
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<Ipv4Addr> {
self.ip.get()
}
pub fn set_ip(&self, ip: Ipv4Addr) {
self.ip.set(Some(ip))
}
}

170
src/ip.rs Normal file
View File

@ -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<u8>) {
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<Iface>,
arp_client: &'a ArpClient<'b>,
}
impl<'a, 'b> IpSender<'a, 'b> {
pub fn new(iface: Rc<Iface>, arp_client: &'a ArpClient<'b>) -> Self {
Self { iface, arp_client }
}
pub fn send<L4: 'b, L7: 'b>(&self, dst: Ipv4Addr, packet: Packet<NoHeader, NoHeader, L4, L7>)
where
L4: Header,
L7: OptionalHeader,
IpProtocolType: FromHeaderType<L4>,
{
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);
}
}),
);
}
}

67
src/main.rs Normal file
View File

@ -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::<ArpMessage>().unwrap();
println!("------------------");
println!("{}", packet);
println!("------------------");
arp_client.handle_message(packet);
}
EtherType::Ipv4 => {
let packet = packet.parse::<IpHeader>().unwrap();
#[allow(clippy::single_match)]
match packet.l3().protocol() {
IpProtocolType::Icmp => {
let packet = packet.parse::<IcmpHeader>().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 }),
);
}
}
_ => (),
}
}
};
}
}

306
src/packet.rs Normal file
View File

@ -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<T: Header> {
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<u8>);
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<u8>);
fn serialize_stage2(_hdr: &mut [u8], _data: &[u8]) {}
}
impl<T: Header> OptionalHeader for T {
fn is_some(&self) -> bool {
true
}
fn serialize_stage1(&self, buf: &mut Vec<u8>) {
<Self as Header>::serialize_stage1(self, buf)
}
fn serialize_stage2(hdr: &mut [u8], data: &[u8]) {
<Self as Header>::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<u8>) {}
}
#[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<u8>,
}
impl<L2: OptionalHeader, L3: OptionalHeader, L4: OptionalHeader, L7: OptionalHeader> Display
for Packet<L2, L3, L4, L7>
{
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<u8>) -> Self {
Self {
l2: NoHeader,
l3: NoHeader,
l4: NoHeader,
l7: NoHeader,
body,
}
}
}
impl<L2: OptionalHeader, L3: OptionalHeader, L4: OptionalHeader, L7: OptionalHeader>
Packet<L2, L3, L4, L7>
{
#[allow(dead_code)]
pub fn body(&self) -> &[u8] {
self.body.as_ref()
}
#[allow(dead_code)]
pub fn set_l2<T: Header>(self, l2: T) -> Packet<T, L3, L4, L7> {
Packet {
l2,
l3: self.l3,
l4: self.l4,
l7: self.l7,
body: self.body,
}
}
#[allow(dead_code)]
pub fn set_l3<T: Header>(self, l3: T) -> Packet<L2, T, L4, L7> {
Packet {
l2: self.l2,
l3,
l4: self.l4,
l7: self.l7,
body: self.body,
}
}
#[allow(dead_code)]
pub fn set_l4<T: Header>(self, l4: T) -> Packet<L2, L3, T, L7> {
Packet {
l2: self.l2,
l3: self.l3,
l4,
l7: self.l7,
body: self.body,
}
}
#[allow(dead_code)]
pub fn set_l7<T: Header>(self, l7: T) -> Packet<L2, L3, L4, T> {
Packet {
l2: self.l2,
l3: self.l3,
l4: self.l4,
l7,
body: self.body,
}
}
#[allow(dead_code)]
pub fn serialize(&self) -> Vec<u8> {
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<L2: Header, L3: OptionalHeader, L4: OptionalHeader, L7: OptionalHeader>
Packet<L2, L3, L4, L7>
{
#[allow(dead_code)]
pub fn l2(&self) -> &L2 {
&self.l2
}
}
impl<L2: OptionalHeader, L3: Header, L4: OptionalHeader, L7: OptionalHeader>
Packet<L2, L3, L4, L7>
{
#[allow(dead_code)]
pub fn l3(&self) -> &L3 {
&self.l3
}
}
impl<L2: OptionalHeader, L3: OptionalHeader, L4: Header, L7: OptionalHeader>
Packet<L2, L3, L4, L7>
{
#[allow(dead_code)]
pub fn l4(&self) -> &L4 {
&self.l4
}
}
impl<L2: OptionalHeader, L3: OptionalHeader, L4: OptionalHeader, L7: Header>
Packet<L2, L3, L4, L7>
{
#[allow(dead_code)]
pub fn l7(&self) -> &L7 {
&self.l7
}
}
impl Packet {
#[allow(dead_code)]
pub fn parse<T: HeaderParse>(self) -> Result<Packet<T>, (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<L2: Header> Packet<L2> {
#[allow(dead_code)]
pub fn parse<T: HeaderParse>(self) -> Result<Packet<L2, T>, (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<L2: Header, L3: Header> Packet<L2, L3> {
#[allow(dead_code)]
pub fn parse<T: HeaderParse>(self) -> Result<Packet<L2, L3, T>, (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<L2: Header, L3: Header, L4: Header> Packet<L2, L3, L4> {
#[allow(dead_code)]
#[allow(clippy::type_complexity)]
pub fn parse<T: HeaderParse>(self) -> Result<Packet<L2, L3, L4, T>, (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)),
}
}
}