Initial revision of Softnet, a software networking for Tart
This commit is contained in:
commit
226a7e6c86
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
|
|
@ -0,0 +1,688 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[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 = "block"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
"clap_derive",
|
||||
"clap_lex",
|
||||
"indexmap",
|
||||
"lazy_static",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "3.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213"
|
||||
dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "649c91bc01e8b1eac09fb91e8dbc7d517684ca6be8ebc75bb9cafc894f9fdb6f"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dhcproto"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5e3e1b355a83bd58b250b7611ce150f36f943d9a5bfa67c95647cd13483169e"
|
||||
dependencies = [
|
||||
"rand",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum-iterator"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a7cbade83f72b1330fa58dc6203176f374e61eb3682055b6d3df05f56723f85"
|
||||
dependencies = [
|
||||
"enum-iterator-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum-iterator-derive"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a375a452d20c4dbbaa397e84cdfaa2712b4b3d22bf49426e9c8725d10fef005"
|
||||
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 = "getrandom"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hexdump"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e40283dadb02f3af778878be1d717b17b4e4ab92e1d935ab03a730b0542905f2"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"itertools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ip_network"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.4.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4a9b56eb56058f43dc66e58f40a214b2ccbc9f3df51861b63d51dec7b65bc3f"
|
||||
|
||||
[[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 = "log"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mac_address"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df1d1bc1084549d60725ccc53a2bfa07f67fe4689fda07b05a36531f2988104a"
|
||||
dependencies = [
|
||||
"nix 0.23.1",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "managed"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f17df307904acd05aa8e32e97bb20f2a0df1728bbc2d771ae8f9a90463441e9"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa"
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"log",
|
||||
"wepoll-ffi",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||
|
||||
[[package]]
|
||||
name = "privdrop"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad5b1f7e40f628a2f8f90e40d3f313be83066cc61997fdcb96cade6abf7cee93"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"nix 0.24.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[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 = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.137"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
|
||||
|
||||
[[package]]
|
||||
name = "smoltcp"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72165c4af59f5f19c7fb774b88b95660591b612380305b5f4503157341a9f7ee"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"libc",
|
||||
"log",
|
||||
"managed",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "softnet"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"dhcproto",
|
||||
"ip_network",
|
||||
"libc",
|
||||
"mac_address",
|
||||
"num_enum",
|
||||
"polling",
|
||||
"privdrop",
|
||||
"smoltcp",
|
||||
"system-configuration",
|
||||
"thiserror",
|
||||
"users",
|
||||
"vmnet",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[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 = "system-configuration"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d75182f12f490e953596550b65ee31bda7c8e043d9386174b353bda50838c3fd"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"core-foundation",
|
||||
"system-configuration-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
|
||||
|
||||
[[package]]
|
||||
name = "users"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6d5d669b51467dcf7b2f1a796ce0f955f05f01cafda6c19d6e95f730df29238"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "vmnet"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd3466ad0e4409268556ce303dc9d86c8dc500a180650eef600676a7b515cbe5"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"block",
|
||||
"enum-iterator",
|
||||
"hexdump",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"num_enum",
|
||||
"thiserror",
|
||||
"uuid",
|
||||
"vmnet-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vmnet-derive"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff8d8b9df9abd3c39593e6d79cf2a28771c6d90f4fe6c524491f6906a509de2b"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
|
||||
[[package]]
|
||||
name = "wepoll-ffi"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[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-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
[package]
|
||||
name = "softnet"
|
||||
version = "0.1.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
path = "lib/mod.rs"
|
||||
|
||||
[dependencies]
|
||||
smoltcp = "0.8.1"
|
||||
libc = "0.2.126"
|
||||
polling = "2.2.0"
|
||||
dhcproto = "0.7.0"
|
||||
vmnet = "0.1.1"
|
||||
clap = { version = "3.1.18", features = ["derive"] }
|
||||
mac_address = "1.1.3"
|
||||
privdrop = "0.5.2"
|
||||
thiserror = "1.0.31"
|
||||
ip_network = "0.4.1"
|
||||
users = "0.11.0"
|
||||
system-configuration = "0.5.0"
|
||||
num_enum = "0.5.7"
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# Softnet
|
||||
|
||||
Softnet is a software networking for [Tart](https://github.com/cirruslabs/tart) which provides better network isolation and alleviates DHCP shortage on production systems.
|
||||
|
||||
## Working model
|
||||
|
||||
Softnet solves two problems:
|
||||
|
||||
1. VM network isolation
|
||||
* [`VZNATNetworkDeviceAttachment`](https://developer.apple.com/documentation/virtualization/vznatnetworkdeviceattachment) (the default networking in Tart) enables [vmnet's bridge isolation](https://developer.apple.com/documentation/vmnet/vmnet_enable_isolation_key) by default and prevents cross-VM traffic, however it's still possible for any VM to spoof the host's ARP-table and capture other VMs traffic, for example
|
||||
2. DHCP exhaustion
|
||||
* macOS built-in DHCP-server allocates a `/24` subnet with 86400 seconds lease time by default, which only allows for ~253 VMs a day (or 1 VM every ~6 minutes) to be spawned without causing a denial-of-service, which is pretty limiting for CI services like Cirrus CI
|
||||
|
||||
And assumes that:
|
||||
|
||||
1. Tart gives it's VMs unique MAC-addresses
|
||||
2. macOS built-in DHCP-server won't re-use the IP-addresses from it's pool until their lease expire
|
||||
|
||||
...otherwise it's possible for two VMs to receive an identical IP-address from the macOS built-in DHCP-server (even in the presence of Softnet's packet filtering) and thus bypass the protections offered by Softnet.
|
||||
|
||||
## Installing
|
||||
|
||||
For proper functioning Softnet binary requires two things:
|
||||
|
||||
* a [SUID-bit](https://en.wikipedia.org/wiki/Setuid#SUID) set which effectively gives it `root` privileges
|
||||
* this is needed in order to create [`vmnet.framework`](https://developer.apple.com/documentation/vmnet) interface and perform DHCP-related tweaks
|
||||
* the privileges will be dropped once all of the initialization is completed
|
||||
* to be available in `PATH`
|
||||
* so that the Tart will be able to find it
|
||||
|
||||
## Running
|
||||
|
||||
Softnet is started and managed automatically by Tart if `--with-softnet` flag is present when calling `tart run`.
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
use dhcproto::v4::{DhcpOption, MessageType, OptionCode};
|
||||
use dhcproto::Decodable;
|
||||
use smoltcp::wire::Ipv4Address;
|
||||
use std::collections::HashSet;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DhcpSnooper {
|
||||
vm_lease: Option<Lease>,
|
||||
}
|
||||
|
||||
impl DhcpSnooper {
|
||||
pub fn register_dhcp_reply(&mut self, dhcp_packet: &[u8]) {
|
||||
let mut decoder = dhcproto::v4::Decoder::new(dhcp_packet);
|
||||
|
||||
let message = match dhcproto::v4::Message::decode(&mut decoder) {
|
||||
Ok(message) => message,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
match message.opts().msg_type() {
|
||||
Some(MessageType::Ack) => {
|
||||
let lease_time = match message.opts().get(OptionCode::AddressLeaseTime) {
|
||||
Some(DhcpOption::AddressLeaseTime(lease_time)) => lease_time,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let dns_ips = match message.opts().get(OptionCode::DomainNameServer) {
|
||||
Some(DhcpOption::DomainNameServer(dns_ips)) => HashSet::from_iter(
|
||||
dns_ips.iter().map(|dns_ip| Ipv4Address(dns_ip.octets())),
|
||||
),
|
||||
_ => HashSet::new(),
|
||||
};
|
||||
|
||||
self.vm_lease = Some(Lease::new(
|
||||
message.yiaddr().into(),
|
||||
Duration::from_secs(*lease_time as u64),
|
||||
dns_ips,
|
||||
))
|
||||
}
|
||||
Some(MessageType::Nak) => {
|
||||
self.vm_lease = None;
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn lease(&self) -> &Option<Lease> {
|
||||
&self.vm_lease
|
||||
}
|
||||
|
||||
pub fn valid_dns_target(&self, addr: &Ipv4Address) -> bool {
|
||||
if let Some(lease) = &self.vm_lease {
|
||||
return lease.dns_ips.contains(addr);
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Lease {
|
||||
address: Ipv4Address,
|
||||
valid_until: Instant,
|
||||
dns_ips: HashSet<Ipv4Address>,
|
||||
}
|
||||
|
||||
impl Lease {
|
||||
fn new(address: Ipv4Address, lease_time: Duration, dns_ips: HashSet<Ipv4Address>) -> Lease {
|
||||
Lease {
|
||||
address,
|
||||
valid_until: Instant::now() + lease_time,
|
||||
dns_ips,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn valid_ip_source(&self, address: Ipv4Address) -> bool {
|
||||
self.address == address && Instant::now() < self.valid_until
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
use crate::{Error, Result};
|
||||
use std::net::Ipv4Addr;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::os::unix::net::UnixDatagram;
|
||||
use std::str::FromStr;
|
||||
use std::sync::mpsc::{sync_channel, SyncSender};
|
||||
use vmnet::mode::Mode;
|
||||
use vmnet::parameters::{Parameter, ParameterKind};
|
||||
use vmnet::{Events, Options};
|
||||
|
||||
pub struct Host {
|
||||
interface: vmnet::Interface,
|
||||
new_packets_rx: UnixDatagram,
|
||||
callback_can_continue_tx: SyncSender<()>,
|
||||
pub gateway_ip: smoltcp::wire::Ipv4Address,
|
||||
pub max_packet_size: u64,
|
||||
finalized: bool,
|
||||
}
|
||||
|
||||
impl Host {
|
||||
pub fn new() -> Result<Host> {
|
||||
// Initialize a vmnet.framework NAT interface with isolation enabled
|
||||
let mut interface = vmnet::Interface::new(
|
||||
Mode::Shared(Default::default()),
|
||||
Options {
|
||||
enable_isolation: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.map_err(|err| Error::VmnetFailed { source: err })?;
|
||||
|
||||
// Retrieve first IP (gateway) used for this interface
|
||||
let gateway_ip = match interface.parameters().get(ParameterKind::StartAddress) {
|
||||
Some(Parameter::StartAddress(gateway_ip)) => gateway_ip,
|
||||
_ => return Err(Error::VmnetUnexpected),
|
||||
};
|
||||
let gateway_ip = Ipv4Addr::from_str(&gateway_ip).map_err(|_| Error::VmnetUnexpected)?;
|
||||
|
||||
// Retrieve max packet size for this interface
|
||||
let max_packet_size = match interface.parameters().get(ParameterKind::MaxPacketSize) {
|
||||
Some(Parameter::MaxPacketSize(max_packet_size)) => max_packet_size,
|
||||
_ => return Err(Error::VmnetUnexpected),
|
||||
};
|
||||
|
||||
// Set up a socketpair() to emulate polling of the vmnet interface
|
||||
let (new_packets_tx, new_packets_rx) =
|
||||
UnixDatagram::pair().map_err(|err| Error::InitFailed { source: err.into() })?;
|
||||
new_packets_rx
|
||||
.set_nonblocking(true)
|
||||
.map_err(|err| Error::InitFailed { source: err.into() })?;
|
||||
|
||||
let (callback_can_continue_tx, callback_can_continue_rx) = sync_channel(0);
|
||||
|
||||
interface
|
||||
.set_event_callback(Events::PACKETS_AVAILABLE, move |_mask, _params| {
|
||||
// Send a dummy datagram to make the other end of socketpair() readable
|
||||
new_packets_tx.send(&[0; 1]).unwrap();
|
||||
|
||||
// Wait for the permission to continue to avoid
|
||||
// wasting CPU cycles or in case of termination,
|
||||
// to unblock this Block[1] and allow
|
||||
// vmnet.framework to terminate
|
||||
//
|
||||
// [1]: https://en.wikipedia.org/wiki/Blocks_(C_language_extension)
|
||||
callback_can_continue_rx.recv().unwrap();
|
||||
})
|
||||
.map_err(|err| Error::VmnetFailed { source: err })?;
|
||||
|
||||
Ok(Host {
|
||||
interface,
|
||||
new_packets_rx,
|
||||
callback_can_continue_tx,
|
||||
gateway_ip: gateway_ip.into(),
|
||||
max_packet_size,
|
||||
finalized: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Host {
|
||||
pub fn read(&mut self, buf: &mut [u8]) -> vmnet::Result<usize> {
|
||||
// Dequeue dummy datagram from the socket (if any)
|
||||
// to free up buffer space and reduce false-positives
|
||||
// when polling
|
||||
let mut buf_to_be_discarded: [u8; 1] = [0; 1];
|
||||
let _ = self.new_packets_rx.recv(&mut buf_to_be_discarded);
|
||||
|
||||
let result = self.interface.read(buf);
|
||||
|
||||
if let Err(vmnet::Error::VmnetReadNothing) = result {
|
||||
// We've emptied everything, unlock the callback
|
||||
// so that it will be able to pick up new events
|
||||
let _ = self.callback_can_continue_tx.send(());
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn write(&mut self, buf: &[u8]) -> vmnet::Result<usize> {
|
||||
self.interface.write(buf)
|
||||
}
|
||||
|
||||
pub fn finalize(&mut self) -> Result<()> {
|
||||
// First make sure our callback won't be scheduled again after it finishes
|
||||
self.interface
|
||||
.clear_event_callback()
|
||||
.map_err(|err| Error::VmnetFailed { source: err })?;
|
||||
|
||||
// Now let the callback finish
|
||||
self.callback_can_continue_tx.send(()).unwrap();
|
||||
|
||||
self.interface
|
||||
.finalize()
|
||||
.map_err(|err| Error::VmnetFailed { source: err })?;
|
||||
|
||||
self.finalized = true;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Host {
|
||||
fn drop(&mut self) {
|
||||
if !self.finalized {
|
||||
let _ = self.finalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for Host {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.new_packets_rx.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
mod dhcp_snooper;
|
||||
mod host;
|
||||
mod poller;
|
||||
pub mod proxy;
|
||||
mod vm;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("initialization failed")]
|
||||
InitFailed { source: Box<dyn std::error::Error> },
|
||||
|
||||
#[error("failed to poll")]
|
||||
PollFailed { source: std::io::Error },
|
||||
|
||||
#[error("vmnet failed")]
|
||||
VmnetFailed { source: vmnet::Error },
|
||||
|
||||
#[error("vmnet returned unexpected data")]
|
||||
VmnetUnexpected,
|
||||
|
||||
#[error("failed to do I/O on VM socket")]
|
||||
VMIOFailed { source: std::io::Error },
|
||||
|
||||
#[error("failed to do I/O on host socket")]
|
||||
HostIOFailed { source: vmnet::Error },
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
use crate::{Error, Result};
|
||||
use num_enum::IntoPrimitive;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct Poller {
|
||||
poller: polling::Poller,
|
||||
events: Vec<polling::Event>,
|
||||
vm_fd: RawFd,
|
||||
host_fd: RawFd,
|
||||
}
|
||||
|
||||
#[derive(IntoPrimitive)]
|
||||
#[repr(usize)]
|
||||
enum EventKey {
|
||||
VM,
|
||||
Host,
|
||||
}
|
||||
|
||||
impl Poller {
|
||||
pub fn new(vm_fd: RawFd, host_fd: RawFd) -> Result<Poller> {
|
||||
let poller =
|
||||
polling::Poller::new().map_err(|err| Error::InitFailed { source: err.into() })?;
|
||||
|
||||
Ok(Poller {
|
||||
poller,
|
||||
events: Vec::new(),
|
||||
vm_fd,
|
||||
host_fd,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn arm(&self) -> Result<()> {
|
||||
self.poller
|
||||
.add(self.vm_fd as RawFd, self.vm_interest())
|
||||
.map_err(|err| Error::PollFailed { source: err })?;
|
||||
|
||||
self.poller
|
||||
.add(self.host_fd as RawFd, self.host_interest())
|
||||
.map_err(|err| Error::PollFailed { source: err })?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn rearm(&mut self) -> Result<()> {
|
||||
self.events.clear();
|
||||
|
||||
self.poller
|
||||
.modify(self.vm_fd as RawFd, self.vm_interest())
|
||||
.map_err(|err| Error::PollFailed { source: err })?;
|
||||
self.poller
|
||||
.modify(self.host_fd as RawFd, self.host_interest())
|
||||
.map_err(|err| Error::PollFailed { source: err })?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn wait(&mut self) -> Result<(bool, bool)> {
|
||||
self.poller
|
||||
.wait(&mut self.events, Some(Duration::from_millis(100)))
|
||||
.map_err(|err| Error::PollFailed { source: err })?;
|
||||
|
||||
let vm_readable = self.events.iter().any(|ev| ev.key == EventKey::VM.into());
|
||||
let host_readable = self.events.iter().any(|ev| ev.key == EventKey::Host.into());
|
||||
|
||||
Ok((vm_readable, host_readable))
|
||||
}
|
||||
|
||||
fn vm_interest(&self) -> polling::Event {
|
||||
polling::Event::readable(EventKey::VM.into())
|
||||
}
|
||||
|
||||
fn host_interest(&self) -> polling::Event {
|
||||
polling::Event::readable(EventKey::Host.into())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
use crate::proxy::udp_packet_helper::UdpPacketHelper;
|
||||
use crate::proxy::Proxy;
|
||||
use crate::{Error, Result};
|
||||
use smoltcp::wire::{EthernetFrame, EthernetProtocol, Ipv4Packet, UdpPacket};
|
||||
|
||||
impl Proxy {
|
||||
pub(crate) fn process_frame_from_host(&mut self, frame: &EthernetFrame<&[u8]>) -> Result<()> {
|
||||
if self.allowed_from_host(frame).is_none() {
|
||||
// Block packet by not forwarding it to the VM
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Snoop bootpd(8) replies from the host to
|
||||
// figure out the IP assigned to the VM
|
||||
self.snoop(frame);
|
||||
|
||||
self.vm
|
||||
.write(frame.as_ref())
|
||||
.map(|_| ())
|
||||
.map_err(|err| Error::VMIOFailed { source: err })
|
||||
}
|
||||
|
||||
fn allowed_from_host(&mut self, frame: &EthernetFrame<&[u8]>) -> Option<()> {
|
||||
match frame.ethertype() {
|
||||
EthernetProtocol::Arp => Some(()),
|
||||
EthernetProtocol::Ipv4 => Some(()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn snoop(&mut self, frame: &EthernetFrame<&[u8]>) {
|
||||
if frame.ethertype() != EthernetProtocol::Ipv4 {
|
||||
return;
|
||||
}
|
||||
|
||||
let ipv4_pkt = match Ipv4Packet::new_checked(frame.payload()) {
|
||||
Ok(ipv4_pkt) => ipv4_pkt,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if ipv4_pkt.src_addr() != self.host.gateway_ip {
|
||||
return;
|
||||
}
|
||||
|
||||
if ipv4_pkt.protocol() != smoltcp::wire::IpProtocol::Udp {
|
||||
return;
|
||||
}
|
||||
|
||||
let udp_pkt = match UdpPacket::new_checked(ipv4_pkt.payload()) {
|
||||
Ok(udp_pkt) => udp_pkt,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
if !udp_pkt.is_dhcp_response() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.dhcp_snooper.register_dhcp_reply(udp_pkt.payload());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
mod host;
|
||||
mod udp_packet_helper;
|
||||
mod vm;
|
||||
|
||||
use crate::dhcp_snooper::DhcpSnooper;
|
||||
use crate::host::Host;
|
||||
use crate::poller::Poller;
|
||||
use crate::vm::VM;
|
||||
use crate::Error;
|
||||
use crate::Result;
|
||||
use mac_address::MacAddress;
|
||||
use smoltcp::wire::EthernetFrame;
|
||||
use std::io::ErrorKind;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
pub struct Proxy {
|
||||
vm: VM,
|
||||
host: Host,
|
||||
poller: Poller,
|
||||
vm_mac_address: smoltcp::wire::EthernetAddress,
|
||||
dhcp_snooper: DhcpSnooper,
|
||||
}
|
||||
|
||||
impl Proxy {
|
||||
pub fn new(vm_fd: RawFd, vm_mac_address: MacAddress) -> Result<Proxy> {
|
||||
let vm = VM::new(vm_fd)?;
|
||||
let host = Host::new()?;
|
||||
let poller = Poller::new(vm.as_raw_fd(), host.as_raw_fd())?;
|
||||
|
||||
Ok(Proxy {
|
||||
vm,
|
||||
host,
|
||||
poller,
|
||||
vm_mac_address: smoltcp::wire::EthernetAddress(vm_mac_address.bytes()),
|
||||
dhcp_snooper: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> Result<()> {
|
||||
let mut buf: Vec<u8> = vec![0; self.host.max_packet_size as usize];
|
||||
|
||||
self.poller.arm()?;
|
||||
|
||||
loop {
|
||||
let (vm_readable, host_readable) = self.poller.wait()?;
|
||||
|
||||
if vm_readable {
|
||||
self.read_from_vm(buf.as_mut_slice())?;
|
||||
}
|
||||
|
||||
if host_readable {
|
||||
self.read_from_host(buf.as_mut_slice())?;
|
||||
}
|
||||
|
||||
self.poller.rearm()?;
|
||||
}
|
||||
}
|
||||
|
||||
fn read_from_vm(&mut self, buf: &mut [u8]) -> Result<()> {
|
||||
loop {
|
||||
match self.vm.read(buf) {
|
||||
Ok(n) => {
|
||||
if let Ok(frame) = EthernetFrame::new_checked(&buf[..n]) {
|
||||
self.process_frame_from_vm(frame)?;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
if err.kind() == ErrorKind::WouldBlock {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
return Err(Error::VMIOFailed { source: err });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_from_host(&mut self, buf: &mut [u8]) -> Result<()> {
|
||||
loop {
|
||||
match self.host.read(buf) {
|
||||
Ok(n) => {
|
||||
if let Ok(pkt) = EthernetFrame::new_checked(&buf[..n]) {
|
||||
self.process_frame_from_host(&pkt)?;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
if let vmnet::Error::VmnetReadNothing = err {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
return Err(Error::HostIOFailed { source: err });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
use smoltcp::wire::UdpPacket;
|
||||
|
||||
pub(crate) trait UdpPacketHelper {
|
||||
const DNS_PORT: u16 = 53;
|
||||
const BOOTPS_PORT: u16 = 67;
|
||||
const BOOTPC_PORT: u16 = 68;
|
||||
|
||||
fn is_dns_request(&self) -> bool;
|
||||
|
||||
fn is_dhcp_request(&self) -> bool;
|
||||
fn is_dhcp_response(&self) -> bool;
|
||||
}
|
||||
|
||||
impl UdpPacketHelper for UdpPacket<&[u8]> {
|
||||
fn is_dns_request(&self) -> bool {
|
||||
self.dst_port() == Self::DNS_PORT
|
||||
}
|
||||
|
||||
fn is_dhcp_request(&self) -> bool {
|
||||
self.src_port() == Self::BOOTPC_PORT || self.dst_port() == Self::BOOTPS_PORT
|
||||
}
|
||||
|
||||
fn is_dhcp_response(&self) -> bool {
|
||||
self.src_port() == Self::BOOTPS_PORT || self.dst_port() == Self::BOOTPC_PORT
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
use crate::proxy::udp_packet_helper::UdpPacketHelper;
|
||||
use crate::proxy::Proxy;
|
||||
use crate::{Error, Result};
|
||||
use smoltcp::wire::{
|
||||
ArpPacket, EthernetFrame, EthernetProtocol, IpProtocol, Ipv4Packet, UdpPacket,
|
||||
};
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
impl Proxy {
|
||||
pub(crate) fn process_frame_from_vm(&mut self, frame: EthernetFrame<&[u8]>) -> Result<()> {
|
||||
if self.allowed_from_vm(&frame).is_none() {
|
||||
// Block packet by not forwarding it to the host
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.host
|
||||
.write(frame.as_ref())
|
||||
.map(|_| ())
|
||||
.map_err(|err| Error::HostIOFailed { source: err })
|
||||
}
|
||||
|
||||
fn allowed_from_vm(&self, frame: &EthernetFrame<&[u8]>) -> Option<()> {
|
||||
if frame.src_addr() != self.vm_mac_address {
|
||||
return None;
|
||||
}
|
||||
|
||||
match frame.ethertype() {
|
||||
EthernetProtocol::Arp => {
|
||||
let arp_pkt = ArpPacket::new_checked(frame.payload()).ok()?;
|
||||
self.allowed_from_vm_arp(arp_pkt)
|
||||
}
|
||||
EthernetProtocol::Ipv4 => {
|
||||
let ipv4_pkt = Ipv4Packet::new_checked(frame.payload()).ok()?;
|
||||
self.allowed_from_vm_ipv4(ipv4_pkt)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn allowed_from_vm_arp(&self, arp_pkt: ArpPacket<&[u8]>) -> Option<()> {
|
||||
if arp_pkt.source_hardware_addr() != self.vm_mac_address.0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let source_protocol_addr: [u8; 4] = arp_pkt.source_protocol_addr().try_into().unwrap();
|
||||
let source_protocol_addr = Ipv4Addr::from(source_protocol_addr);
|
||||
|
||||
if let Some(lease) = self.dhcp_snooper.lease() {
|
||||
if lease.valid_ip_source(source_protocol_addr.into()) {
|
||||
return Some(());
|
||||
}
|
||||
} else if source_protocol_addr.is_unspecified() {
|
||||
return Some(());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn allowed_from_vm_ipv4(&self, ipv4_pkt: Ipv4Packet<&[u8]>) -> Option<()> {
|
||||
// Once we've learned the VM's IP from the DHCP snooping,
|
||||
// allow all global traffic for that VM's IP
|
||||
if let Some(lease) = &self.dhcp_snooper.lease() {
|
||||
let dst_is_global =
|
||||
ip_network::IpNetwork::from(Ipv4Addr::from(ipv4_pkt.dst_addr().0)).is_global();
|
||||
|
||||
if lease.valid_ip_source(ipv4_pkt.src_addr()) && dst_is_global {
|
||||
return Some(());
|
||||
}
|
||||
}
|
||||
|
||||
if ipv4_pkt.protocol() == IpProtocol::Udp {
|
||||
let udp_pkt = UdpPacket::new_checked(ipv4_pkt.payload()).ok()?;
|
||||
|
||||
// Allow DNS communication with the DNS-servers provided by DHCP
|
||||
if udp_pkt.is_dns_request() && self.dhcp_snooper.valid_dns_target(&ipv4_pkt.dst_addr())
|
||||
{
|
||||
return Some(());
|
||||
}
|
||||
|
||||
// Allow DHCP communication with the bootpd(8) on host
|
||||
let allowed_dhcp_target =
|
||||
ipv4_pkt.dst_addr().is_broadcast() || ipv4_pkt.dst_addr() == self.host.gateway_ip;
|
||||
|
||||
if udp_pkt.is_dhcp_request() && allowed_dhcp_target {
|
||||
return Some(());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
use crate::{Error, Result};
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
use std::os::unix::net::UnixDatagram;
|
||||
|
||||
pub struct VM {
|
||||
sock: UnixDatagram,
|
||||
}
|
||||
|
||||
impl VM {
|
||||
pub fn new(vm_fd: RawFd) -> Result<VM> {
|
||||
let sock = unsafe { UnixDatagram::from_raw_fd(vm_fd) };
|
||||
sock.set_nonblocking(true)
|
||||
.map_err(|err| Error::InitFailed { source: err.into() })?;
|
||||
|
||||
Ok(VM { sock })
|
||||
}
|
||||
|
||||
pub fn write(&self, pkt: &[u8]) -> std::io::Result<usize> {
|
||||
self.sock.send(pkt)
|
||||
}
|
||||
|
||||
pub fn read(&self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
self.sock.recv(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for VM {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.sock.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
use clap::Parser;
|
||||
use libc::geteuid;
|
||||
use privdrop::PrivDrop;
|
||||
use softnet::proxy::Proxy;
|
||||
use std::os::raw::c_int;
|
||||
use std::os::unix::io::RawFd;
|
||||
use system_configuration::core_foundation::base::TCFType;
|
||||
use system_configuration::core_foundation::dictionary::CFDictionary;
|
||||
use system_configuration::core_foundation::number::CFNumber;
|
||||
use system_configuration::core_foundation::string::CFString;
|
||||
use system_configuration::preferences::SCPreferences;
|
||||
use system_configuration::sys::preferences::{SCPreferencesCommitChanges, SCPreferencesSetValue};
|
||||
use users::{get_current_groupname, get_current_username};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Args {
|
||||
#[clap(
|
||||
long,
|
||||
help = "FD number to use for communicating with the VM's networking stack"
|
||||
)]
|
||||
vm_fd: c_int,
|
||||
|
||||
#[clap(long, help = "MAC address to enforce for the VM")]
|
||||
vm_mac_address: mac_address::MacAddress,
|
||||
|
||||
#[clap(
|
||||
long,
|
||||
help = "set bootpd(8) lease time to this value (in seconds) before starting the VM",
|
||||
default_value_t = 60
|
||||
)]
|
||||
bootpd_lease_time: u32,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(err) = try_main() {
|
||||
match err.source() {
|
||||
Some(source) => eprintln!("{}: {}", err, source),
|
||||
None => eprintln!("{}", err),
|
||||
}
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn try_main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args: Args = Args::parse();
|
||||
|
||||
// Ensure we are running as root
|
||||
if unsafe { geteuid() } != 0 {
|
||||
return Err("root privileges are required to run".into());
|
||||
}
|
||||
|
||||
// Set bootpd(8) min/max lease time while still having the root privileges
|
||||
set_bootpd_lease_time(args.bootpd_lease_time);
|
||||
|
||||
// Initialize the proxy while still having the root privileges
|
||||
let mut proxy = Proxy::new(args.vm_fd as RawFd, args.vm_mac_address)?;
|
||||
|
||||
// Retrieve real (not effective) user and group names
|
||||
let user = get_current_username().ok_or("failed to resolve real user name")?;
|
||||
let group = get_current_groupname().ok_or("failed to resolve real group name")?;
|
||||
|
||||
// Drop effective privileges to the user
|
||||
// and group which have had invoked us
|
||||
PrivDrop::default()
|
||||
.user(user)
|
||||
.group(group)
|
||||
.apply()
|
||||
.map_err(|err| format!("failed to drop privileges: {}", err))?;
|
||||
|
||||
// Run proxy
|
||||
proxy.run().map_err(|err| err.into())
|
||||
}
|
||||
|
||||
fn set_bootpd_lease_time(lease_time: u32) {
|
||||
let prefs = SCPreferences::group(
|
||||
&CFString::new("softnet"),
|
||||
&CFString::new("com.apple.InternetSharing.default.plist"),
|
||||
);
|
||||
|
||||
let bootpd_dict = CFDictionary::from_CFType_pairs(&[(
|
||||
CFString::new("DHCPLeaseTimeSecs"),
|
||||
CFNumber::from(lease_time as i32),
|
||||
)]);
|
||||
|
||||
unsafe {
|
||||
SCPreferencesSetValue(
|
||||
prefs.as_concrete_TypeRef(),
|
||||
CFString::new("bootpd").as_concrete_TypeRef(),
|
||||
bootpd_dict.as_concrete_TypeRef().cast(),
|
||||
);
|
||||
|
||||
SCPreferencesCommitChanges(prefs.as_concrete_TypeRef());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue