container-tool: init
This commit is contained in:
527
container-tool/Cargo.lock
generated
Normal file
527
container-tool/Cargo.lock
generated
Normal file
@@ -0,0 +1,527 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "container-tool"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"rtnetlink",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.177"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "netlink-packet-core"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3463cbb78394cb0141e2c926b93fc2197e473394b761986eca3b9da2c63ae0f4"
|
||||
dependencies = [
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "netlink-packet-route"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ec2f5b6839be2a19d7fa5aab5bc444380f6311c2b693551cb80f45caaa7b5ef"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
"log",
|
||||
"netlink-packet-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "netlink-proto"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b65d130ee111430e47eed7896ea43ca693c387f097dd97376bffafbf25812128"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures",
|
||||
"log",
|
||||
"netlink-packet-core",
|
||||
"netlink-sys",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "netlink-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16c903aa70590cb93691bf97a767c8d1d6122d2cc9070433deb3bbf36ce8bd23"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures",
|
||||
"libc",
|
||||
"log",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rtnetlink"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08fd15aa4c64c34d0b3178e45ec6dad313a9f02b193376d501668a7950264bb7"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"log",
|
||||
"netlink-packet-core",
|
||||
"netlink-packet-route",
|
||||
"netlink-proto",
|
||||
"netlink-sys",
|
||||
"nix",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.145"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.110"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||
15
container-tool/Cargo.toml
Normal file
15
container-tool/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "container-tool"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
futures-util = "0.3.31"
|
||||
rtnetlink = "0.18.1"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.145"
|
||||
tokio = { version = "1.48.0", features = ["macros", "rt", "signal"] }
|
||||
|
||||
[[bin]]
|
||||
name = "container-tool"
|
||||
path = "src/main.rs"
|
||||
687
container-tool/src/main.rs
Normal file
687
container-tool/src/main.rs
Normal file
@@ -0,0 +1,687 @@
|
||||
mod netlink_commands;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
||||
os::unix::fs::PermissionsExt,
|
||||
path::PathBuf,
|
||||
process::Command,
|
||||
str::FromStr,
|
||||
};
|
||||
use tokio::signal::unix::{SignalKind, signal};
|
||||
|
||||
use crate::netlink_commands::{
|
||||
ip_addr_add, ip_link_add_to_bridge, ip_link_del, ip_link_get_index, ip_link_set_name,
|
||||
ip_link_up, ip_route_add, ip_route_add_default_via_addr,
|
||||
};
|
||||
|
||||
// TODO: read global and instance specific config from json
|
||||
|
||||
fn main() {
|
||||
let command = std::env::args().nth(1).expect("No command provided");
|
||||
let config_file = std::env::args().nth(2).expect("No config file provided");
|
||||
let config_data = std::fs::read_to_string(config_file).expect("Failed to read config file");
|
||||
let config: Config = serde_json::from_str(&config_data).expect("Failed to parse config file");
|
||||
|
||||
match command.as_str() {
|
||||
// Prepare the networking on the host side
|
||||
"prepare-host-networking" => prepare_host_networking(config),
|
||||
|
||||
// Set up directories and files in the container root directory
|
||||
"prepare-rootdir" => prepare_rootdir(config),
|
||||
|
||||
// Set up the networking inside the container (needs to be running)
|
||||
"prepare-networking-in-container" => unimplemented!(),
|
||||
|
||||
// Run the container
|
||||
"run-container" => unimplemented!(),
|
||||
|
||||
// Teardown the networking on the host side
|
||||
"teardown-host-networking" => teardown_host_networking(config),
|
||||
|
||||
// Reload the container (e.g., after a configuration change)
|
||||
"reload-container" => reload_container(config),
|
||||
_ => panic!("Unknown command: {}", command),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Config {
|
||||
instance_name: String,
|
||||
|
||||
root_dir: PathBuf,
|
||||
|
||||
ephemeral: bool,
|
||||
private_network: bool,
|
||||
private_users: Option<String>,
|
||||
personality: Option<String>,
|
||||
|
||||
host_bridge: Option<String>,
|
||||
host_address: Option<Ipv4Addr>,
|
||||
local_address: Option<Ipv4Addr>,
|
||||
host_address6: Option<Ipv6Addr>,
|
||||
local_address6: Option<Ipv6Addr>,
|
||||
|
||||
#[serde(default)]
|
||||
port: Vec<Port>,
|
||||
|
||||
#[serde(default)]
|
||||
interfaces: Vec<String>,
|
||||
|
||||
#[serde(default)]
|
||||
macvlans: Vec<String>,
|
||||
|
||||
#[serde(default)]
|
||||
extra_veths: Vec<Veth>,
|
||||
|
||||
#[serde(default)]
|
||||
bind_mounts: Vec<BindMount>,
|
||||
|
||||
#[serde(default)]
|
||||
additional_capabilities: Vec<String>,
|
||||
|
||||
#[serde(default)]
|
||||
tmpfs: Vec<String>,
|
||||
|
||||
#[serde(default)]
|
||||
extra_nspawn_args: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Veth {
|
||||
name: String,
|
||||
host_bridge: Option<String>,
|
||||
host_address: Option<String>,
|
||||
local_address: Option<String>,
|
||||
host_address6: Option<String>,
|
||||
local_address6: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct BindMount {
|
||||
source: String,
|
||||
target: String,
|
||||
options: Vec<String>,
|
||||
}
|
||||
|
||||
impl ToString for BindMount {
|
||||
fn to_string(&self) -> String {
|
||||
if !self.options.is_empty() {
|
||||
format!("{}:{}:{}", self.source, self.target, self.options.join(","))
|
||||
} else {
|
||||
format!("{}:{}", self.source, self.target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for BindMount {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let parts: Vec<&str> = s.split(':').collect();
|
||||
match parts.as_slice() {
|
||||
[path] => Ok(BindMount {
|
||||
source: path.to_string(),
|
||||
target: path.to_string(),
|
||||
options: vec![],
|
||||
}),
|
||||
[source, target] => Ok(BindMount {
|
||||
source: source.to_string(),
|
||||
target: target.to_string(),
|
||||
options: vec![],
|
||||
}),
|
||||
[source, target, options] => Ok(BindMount {
|
||||
source: source.to_string(),
|
||||
target: target.to_string(),
|
||||
options: options.split(',').map(|s| s.to_string()).collect(),
|
||||
}),
|
||||
_ => Err(format!("Invalid bind mount format: {}", s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Port {
|
||||
protocol: String, // "tcp" or "udp"
|
||||
host_port: u16,
|
||||
container_port: u16,
|
||||
}
|
||||
|
||||
impl FromStr for Port {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let parts: Vec<&str> = s.split(':').collect();
|
||||
match parts[..] {
|
||||
[port] => {
|
||||
let port_num: u16 = port
|
||||
.parse()
|
||||
.map_err(|_| format!("Invalid port number: {}", port))?;
|
||||
Ok(Port {
|
||||
protocol: "tcp".to_string(),
|
||||
host_port: port_num,
|
||||
container_port: port_num,
|
||||
})
|
||||
}
|
||||
[protocol, port] if ["tcp", "udp"].contains(&protocol) => {
|
||||
let port_num: u16 = port
|
||||
.parse()
|
||||
.map_err(|_| format!("Invalid port number: {}", port))?;
|
||||
Ok(Port {
|
||||
protocol: protocol.to_string(),
|
||||
host_port: port_num,
|
||||
container_port: port_num,
|
||||
})
|
||||
}
|
||||
[host_port, container_port] => {
|
||||
let host_port_num: u16 = host_port
|
||||
.parse()
|
||||
.map_err(|_| format!("Invalid host port number: {}", host_port))?;
|
||||
let container_port_num: u16 = container_port
|
||||
.parse()
|
||||
.map_err(|_| format!("Invalid container port number: {}", container_port))?;
|
||||
Ok(Port {
|
||||
protocol: "tcp".to_string(),
|
||||
host_port: host_port_num,
|
||||
container_port: container_port_num,
|
||||
})
|
||||
}
|
||||
[protocol, host_port, container_port] if ["tcp", "udp"].contains(&protocol) => {
|
||||
let host_port_num: u16 = host_port
|
||||
.parse()
|
||||
.map_err(|_| format!("Invalid host port number: {}", host_port))?;
|
||||
let container_port_num: u16 = container_port
|
||||
.parse()
|
||||
.map_err(|_| format!("Invalid container port number: {}", container_port))?;
|
||||
Ok(Port {
|
||||
protocol: protocol.to_string(),
|
||||
host_port: host_port_num,
|
||||
container_port: container_port_num,
|
||||
})
|
||||
}
|
||||
_ => Err(format!("Invalid port format: {}", s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for Port {
|
||||
fn to_string(&self) -> String {
|
||||
format!(
|
||||
"{}:{}:{}",
|
||||
self.protocol, self.host_port, self.container_port
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_host_networking(config: Config) {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed to create Tokio runtime")
|
||||
.block_on(async {
|
||||
let (netlink_conn, netlink_handle, _) =
|
||||
rtnetlink::new_connection().expect("Failed to create netlink connection");
|
||||
tokio::spawn(netlink_conn);
|
||||
if (config.host_address.is_some()
|
||||
|| config.local_address.is_some()
|
||||
|| config.host_address6.is_some()
|
||||
|| config.local_address6.is_some())
|
||||
&& config.host_bridge.is_none()
|
||||
{
|
||||
let if_host = format!("ve-{}", config.instance_name);
|
||||
let if_index = ip_link_get_index(&netlink_handle, &if_host)
|
||||
.await
|
||||
.expect(format!("Failed to get index for interface {}", if_host).as_str());
|
||||
|
||||
ip_link_up(&netlink_handle, if_index)
|
||||
.await
|
||||
.expect(format!("Failed to bring up interface {}", if_host).as_str());
|
||||
|
||||
if let Some(addr) = &config.host_address {
|
||||
ip_addr_add(&netlink_handle, if_index, IpAddr::V4(*addr))
|
||||
.await
|
||||
.expect("Failed to add host address");
|
||||
}
|
||||
|
||||
if let Some(addr6) = &config.host_address6 {
|
||||
ip_addr_add(&netlink_handle, if_index, IpAddr::V6(*addr6))
|
||||
.await
|
||||
.expect("Failed to add host address6");
|
||||
}
|
||||
|
||||
if let Some(addr) = &config.local_address {
|
||||
ip_route_add(&netlink_handle, if_index, IpAddr::V4(*addr))
|
||||
.await
|
||||
.expect("Failed to add local address");
|
||||
}
|
||||
|
||||
if let Some(addr6) = &config.local_address6 {
|
||||
ip_route_add(&netlink_handle, if_index, IpAddr::V6(*addr6))
|
||||
.await
|
||||
.expect("Failed to add local address6");
|
||||
}
|
||||
}
|
||||
|
||||
for veth in &config.extra_veths {
|
||||
let if_name = &veth.name;
|
||||
let if_index = ip_link_get_index(&netlink_handle, if_name)
|
||||
.await
|
||||
.expect(format!("Failed to get index for interface {}", if_name).as_str());
|
||||
|
||||
if let Some(host_bridge) = &veth.host_bridge {
|
||||
let bridge_index = ip_link_get_index(&netlink_handle, host_bridge)
|
||||
.await
|
||||
.expect(format!("Failed to get index for bridge {}", host_bridge).as_str());
|
||||
|
||||
ip_link_add_to_bridge(&netlink_handle, if_index, bridge_index)
|
||||
.await
|
||||
.expect(
|
||||
format!(
|
||||
"Failed to add interface {} to bridge {}",
|
||||
if_name, host_bridge
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
} else {
|
||||
ip_link_up(&netlink_handle, if_index)
|
||||
.await
|
||||
.expect(format!("Failed to bring up interface {}", if_name).as_str());
|
||||
|
||||
if let Some(addr) = &veth.host_address {
|
||||
let ip_addr = IpAddr::from_str(addr)
|
||||
.expect(format!("Invalid IP address: {}", addr).as_str());
|
||||
ip_addr_add(&netlink_handle, if_index, ip_addr)
|
||||
.await
|
||||
.expect("Failed to add veth host address");
|
||||
}
|
||||
|
||||
if let Some(addr6) = &veth.host_address6 {
|
||||
let ip_addr6 = IpAddr::from_str(addr6)
|
||||
.expect(format!("Invalid IP address: {}", addr6).as_str());
|
||||
ip_addr_add(&netlink_handle, if_index, ip_addr6)
|
||||
.await
|
||||
.expect("Failed to add veth host address6");
|
||||
}
|
||||
|
||||
if let Some(addr) = &veth.local_address {
|
||||
let ip_addr = IpAddr::from_str(addr)
|
||||
.expect(format!("Invalid IP address: {}", addr).as_str());
|
||||
ip_route_add(&netlink_handle, if_index, ip_addr)
|
||||
.await
|
||||
.expect("Failed to add veth local address");
|
||||
}
|
||||
|
||||
if let Some(addr6) = &veth.local_address6 {
|
||||
let ip_addr6 = IpAddr::from_str(addr6)
|
||||
.expect(format!("Invalid IP address: {}", addr6).as_str());
|
||||
ip_route_add(&netlink_handle, if_index, ip_addr6)
|
||||
.await
|
||||
.expect("Failed to add veth local address6");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn prepare_rootdir(config: Config) {
|
||||
for (dir, mode) in [
|
||||
("/etc", 0o755),
|
||||
("/var/lib", 0o755),
|
||||
("/var/lib/private", 0o700),
|
||||
("/root", 0o700),
|
||||
] {
|
||||
let path = config.root_dir.join(dir.strip_prefix('/').unwrap());
|
||||
std::fs::create_dir_all(&path)
|
||||
.expect(format!("Failed to create directory {}", path.display()).as_str());
|
||||
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(mode))
|
||||
.expect(format!("Failed to set permissions for directory {}", path.display()).as_str());
|
||||
}
|
||||
|
||||
if !config.root_dir.join("etc/os-release").exists() {
|
||||
std::fs::File::create(config.root_dir.join("etc/os-release"))
|
||||
.expect("Failed to create /etc/os-release");
|
||||
}
|
||||
|
||||
if !config.root_dir.join("etc/machine-id").exists() {
|
||||
std::fs::File::create(config.root_dir.join("etc/machine-id"))
|
||||
.expect("Failed to create /etc/machine-id");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: make this into a separate executable that is copied into the container.
|
||||
// It can still use the config file to get the necessary parameters.
|
||||
|
||||
fn prepare_networking_in_container(config: Config) {
|
||||
// # The container's init script, a small wrapper around the regular
|
||||
// # NixOS stage-2 init script.
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed to create Tokio runtime")
|
||||
.block_on(async {
|
||||
tokio::spawn(async {
|
||||
const SIGRTMIN: i32 = 34; // Typically 34 on Linux systems
|
||||
let mut signal = signal(SignalKind::from_raw(SIGRTMIN))
|
||||
.expect("Failed to set up signal handler");
|
||||
signal.recv().await;
|
||||
std::process::exit(0);
|
||||
});
|
||||
|
||||
let (netlink_conn, netlink_handle, _) =
|
||||
rtnetlink::new_connection().expect("Failed to create netlink connection");
|
||||
tokio::spawn(netlink_conn);
|
||||
|
||||
// # Initialise the container side of the veth pair.
|
||||
if config.host_address.is_some()
|
||||
|| config.local_address.is_some()
|
||||
|| config.host_address6.is_some()
|
||||
|| config.local_address6.is_some()
|
||||
|| config.host_bridge.is_some()
|
||||
{
|
||||
let if_index = ip_link_get_index(&netlink_handle, "host0")
|
||||
.await
|
||||
.expect("Failed to get index for interface host0");
|
||||
|
||||
ip_link_set_name(&netlink_handle, if_index, "eth0")
|
||||
.await
|
||||
.expect("Failed to rename interface host0 to eth0");
|
||||
ip_link_up(&netlink_handle, if_index)
|
||||
.await
|
||||
.expect("Failed to bring up interface eth0");
|
||||
|
||||
if let Some(addr) = &config.local_address {
|
||||
ip_addr_add(&netlink_handle, if_index, IpAddr::V4(*addr))
|
||||
.await
|
||||
.expect("Failed to add local address");
|
||||
}
|
||||
if let Some(addr6) = &config.local_address6 {
|
||||
ip_addr_add(&netlink_handle, if_index, IpAddr::V6(*addr6))
|
||||
.await
|
||||
.expect("Failed to add local address6");
|
||||
}
|
||||
if let Some(addr) = &config.host_address {
|
||||
ip_route_add(&netlink_handle, if_index, IpAddr::V4(*addr))
|
||||
.await
|
||||
.expect("Failed to add host address");
|
||||
ip_route_add_default_via_addr(&netlink_handle, if_index, IpAddr::V4(*addr))
|
||||
.await
|
||||
.expect("Failed to add default route via host address");
|
||||
}
|
||||
if let Some(addr6) = &config.host_address6 {
|
||||
ip_route_add(&netlink_handle, if_index, IpAddr::V6(*addr6))
|
||||
.await
|
||||
.expect("Failed to add host address6");
|
||||
ip_route_add_default_via_addr(&netlink_handle, if_index, IpAddr::V6(*addr6))
|
||||
.await
|
||||
.expect("Failed to add default route via host address6");
|
||||
}
|
||||
}
|
||||
|
||||
// renderExtraVeth = (
|
||||
// name: cfg: ''
|
||||
// echo "Bringing ${name} up"
|
||||
// ip link set dev ${name} up
|
||||
// ${optionalString (cfg.localAddress != null) ''
|
||||
// echo "Setting ip for ${name}"
|
||||
// ip addr add ${cfg.localAddress} dev ${name}
|
||||
// ''}
|
||||
// ${optionalString (cfg.localAddress6 != null) ''
|
||||
// echo "Setting ip6 for ${name}"
|
||||
// ip -6 addr add ${cfg.localAddress6} dev ${name}
|
||||
// ''}
|
||||
// ${optionalString (cfg.hostAddress != null) ''
|
||||
// echo "Setting route to host for ${name}"
|
||||
// ip route add ${cfg.hostAddress} dev ${name}
|
||||
// ''}
|
||||
// ${optionalString (cfg.hostAddress6 != null) ''
|
||||
// echo "Setting route6 to host for ${name}"
|
||||
// ip -6 route add ${cfg.hostAddress6} dev ${name}
|
||||
// ''}
|
||||
// ''
|
||||
// );
|
||||
|
||||
// ${concatStringsSep "\n" (mapAttrsToList renderExtraVeth cfg.extraVeths)}
|
||||
|
||||
// # Start the regular stage 2 script.
|
||||
// # We source instead of exec to not lose an early stop signal, which is
|
||||
// # also the only _reliable_ shutdown signal we have since early stop
|
||||
// # does not execute ExecStop* commands.
|
||||
// set +e
|
||||
// . "$1"
|
||||
// ''
|
||||
});
|
||||
}
|
||||
|
||||
fn run_container(config: Config) {
|
||||
// let command = Command::new("systemd-nspawn");
|
||||
|
||||
let mut args = vec![
|
||||
"--keep-unit".to_string(),
|
||||
"-M".to_string(),
|
||||
config.instance_name.clone(),
|
||||
"-D".to_string(),
|
||||
config.root_dir.to_str().unwrap().to_string(),
|
||||
"--notify-ready=yes".to_string(),
|
||||
"--resolv-conf=copy-host".to_string(),
|
||||
"--kill-signal=SIGRTMIN+3".to_string(),
|
||||
];
|
||||
|
||||
if config.private_network {
|
||||
args.push("--private-network".to_string());
|
||||
}
|
||||
if let Some(private_users) = &config.private_users {
|
||||
args.push(format!("--private-users={}", private_users));
|
||||
}
|
||||
|
||||
// NIX_BIND_OPT=""
|
||||
// if [ -n "$PRIVATE_USERS" ]; then
|
||||
// extraFlags+=("--private-users=$PRIVATE_USERS")
|
||||
// if [[
|
||||
// "$PRIVATE_USERS" = "pick"
|
||||
// || ("$PRIVATE_USERS" =~ ^[[:digit:]]+$ && "$PRIVATE_USERS" -gt 0)
|
||||
// ]]; then
|
||||
// # when user namespacing is enabled, we use `idmap` mount option so that
|
||||
// # bind mounts under /nix get proper owner (and not nobody/nogroup).
|
||||
// NIX_BIND_OPT=":idmap"
|
||||
// fi
|
||||
// fi
|
||||
|
||||
if config.host_address.is_some()
|
||||
|| config.local_address.is_some()
|
||||
|| config.host_address6.is_some()
|
||||
|| config.local_address6.is_some()
|
||||
{
|
||||
args.push("--network-veth".to_string());
|
||||
}
|
||||
|
||||
for port in &config.port {
|
||||
args.push(format!("--port={}", port.to_string()));
|
||||
}
|
||||
|
||||
if let Some(host_bridge) = &config.host_bridge {
|
||||
args.push(format!("--network-bridge={}", host_bridge));
|
||||
}
|
||||
|
||||
if let Some(network_namespace_path) = &config.host_bridge {
|
||||
args.push(format!(
|
||||
"--network-namespace-path={}",
|
||||
network_namespace_path
|
||||
));
|
||||
}
|
||||
|
||||
for veth in &config.extra_veths {
|
||||
args.push(format!("--network-veth-extra={}", veth.name));
|
||||
}
|
||||
|
||||
for iface in &config.interfaces {
|
||||
args.push(format!("--network-interface={}", iface));
|
||||
}
|
||||
|
||||
for iface in &config.macvlans {
|
||||
args.push(format!("--network-macvlan={}", iface));
|
||||
}
|
||||
|
||||
if let Some(personality) = &config.personality {
|
||||
args.push(format!("--personality={}", personality));
|
||||
}
|
||||
|
||||
// --bind-ro=/nix/store:/nix/store$NIX_BIND_OPT \
|
||||
// --bind-ro=/nix/var/nix/db:/nix/var/nix/db$NIX_BIND_OPT \
|
||||
// --bind-ro=/nix/var/nix/daemon-socket:/nix/var/nix/daemon-socket$NIX_BIND_OPT \
|
||||
// --bind="/nix/var/nix/profiles/per-container/$INSTANCE:/nix/var/nix/profiles$NIX_BIND_OPT" \
|
||||
// --bind="/nix/var/nix/gcroots/per-container/$INSTANCE:/nix/var/nix/gcroots$NIX_BIND_OPT" \
|
||||
|
||||
for bind_mount in &config.bind_mounts {
|
||||
args.push(format!("--bind={}", bind_mount.to_string()));
|
||||
}
|
||||
|
||||
if !config.ephemeral {
|
||||
args.push("--link-journal=try-guest".to_string());
|
||||
}
|
||||
|
||||
if config.ephemeral {
|
||||
args.push("--ephemeral".to_string());
|
||||
}
|
||||
|
||||
if !config.additional_capabilities.is_empty() {
|
||||
args.push(format!(
|
||||
"--capability={}",
|
||||
config.additional_capabilities.join(",")
|
||||
));
|
||||
}
|
||||
|
||||
for tmpfs in &config.tmpfs {
|
||||
args.push(format!("--tmpfs={}", tmpfs));
|
||||
}
|
||||
|
||||
for extra_arg in &config.extra_nspawn_args {
|
||||
args.push(extra_arg.clone());
|
||||
}
|
||||
|
||||
unimplemented!();
|
||||
// declare -a extraFlags
|
||||
|
||||
// export SYSTEMD_NSPAWN_UNIFIED_HIERARCHY=1
|
||||
|
||||
// # Run systemd-nspawn without startup notification (we'll
|
||||
// # wait for the container systemd to signal readiness)
|
||||
// # Kill signal handling means systemd-nspawn will pass a system-halt signal
|
||||
// # to the container systemd when it receives SIGTERM for container shutdown;
|
||||
// # containerInit and stage2 have to handle this as well.
|
||||
// # TODO: fix shellcheck issue properly
|
||||
// # shellcheck disable=SC2086
|
||||
// exec ${config.systemd.package}/bin/systemd-nspawn \
|
||||
// --keep-unit \
|
||||
// -M "$INSTANCE" -D "$root" "''${extraFlags[@]}" \
|
||||
// --notify-ready=yes \
|
||||
// --resolv-conf=copy-host \
|
||||
// --kill-signal=SIGRTMIN+3 \
|
||||
// --bind-ro=/nix/store:/nix/store$NIX_BIND_OPT \
|
||||
// --bind-ro=/nix/var/nix/db:/nix/var/nix/db$NIX_BIND_OPT \
|
||||
// --bind-ro=/nix/var/nix/daemon-socket:/nix/var/nix/daemon-socket$NIX_BIND_OPT \
|
||||
// --bind="/nix/var/nix/profiles/per-container/$INSTANCE:/nix/var/nix/profiles$NIX_BIND_OPT" \
|
||||
// --bind="/nix/var/nix/gcroots/per-container/$INSTANCE:/nix/var/nix/gcroots$NIX_BIND_OPT" \
|
||||
// ${optionalString (!cfg.ephemeral) "--link-journal=try-guest"} \
|
||||
// --setenv PRIVATE_NETWORK="$PRIVATE_NETWORK" \
|
||||
// --setenv PRIVATE_USERS="$PRIVATE_USERS" \
|
||||
// --setenv HOST_BRIDGE="$HOST_BRIDGE" \
|
||||
// --setenv HOST_ADDRESS="$HOST_ADDRESS" \
|
||||
// --setenv LOCAL_ADDRESS="$LOCAL_ADDRESS" \
|
||||
// --setenv HOST_ADDRESS6="$HOST_ADDRESS6" \
|
||||
// --setenv LOCAL_ADDRESS6="$LOCAL_ADDRESS6" \
|
||||
// --setenv HOST_PORT="$HOST_PORT" \
|
||||
// --setenv PATH="$PATH" \
|
||||
// ${optionalString cfg.ephemeral "--ephemeral"} \
|
||||
// ${
|
||||
// optionalString (
|
||||
// cfg.additionalCapabilities != null && cfg.additionalCapabilities != [ ]
|
||||
// ) ''--capability="${concatStringsSep "," cfg.additionalCapabilities}"''
|
||||
// } \
|
||||
// ${
|
||||
// optionalString (
|
||||
// cfg.tmpfs != null && cfg.tmpfs != [ ]
|
||||
// ) ''--tmpfs=${concatStringsSep " --tmpfs=" cfg.tmpfs}''
|
||||
// } \
|
||||
// $EXTRA_NSPAWN_FLAGS \
|
||||
// ${containerInit cfg} "''${SYSTEM_PATH:-/nix/var/nix/profiles/system}/init"
|
||||
}
|
||||
|
||||
fn teardown_host_networking(config: Config) {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed to create Tokio runtime")
|
||||
.block_on(async {
|
||||
let (netlink_conn, netlink_handle, _) =
|
||||
rtnetlink::new_connection().expect("Failed to create netlink connection");
|
||||
|
||||
tokio::spawn(netlink_conn);
|
||||
|
||||
if config.host_address.is_some()
|
||||
|| config.local_address.is_some()
|
||||
|| config.host_address6.is_some()
|
||||
|| config.local_address6.is_some()
|
||||
{
|
||||
let ve_if_host = format!("ve-{}", config.instance_name);
|
||||
let ve_if_index = ip_link_get_index(&netlink_handle, &ve_if_host).await;
|
||||
if let Some(ve_if_index) = ve_if_index {
|
||||
ip_link_del(&netlink_handle, ve_if_index)
|
||||
.await
|
||||
// TODO: should these expects block the whole teardown?
|
||||
.expect(format!("Failed to delete interface {}", ve_if_host).as_str());
|
||||
}
|
||||
|
||||
let vb_if_host = format!("vb-{}", config.instance_name);
|
||||
let vb_if_index = ip_link_get_index(&netlink_handle, &ve_if_host).await;
|
||||
if let Some(vb_if_index) = vb_if_index {
|
||||
ip_link_del(&netlink_handle, vb_if_index)
|
||||
.await
|
||||
.expect(format!("Failed to delete interface {}", vb_if_host).as_str());
|
||||
}
|
||||
}
|
||||
|
||||
for veth in &config.extra_veths {
|
||||
let if_index = ip_link_get_index(&netlink_handle, &veth.name).await;
|
||||
if let Some(if_index) = if_index {
|
||||
ip_link_del(&netlink_handle, if_index)
|
||||
.await
|
||||
.expect(format!("Failed to delete interface {}", veth.name).as_str());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// # Run a command in the container.
|
||||
// sub runInContainer {
|
||||
// my @args = @_;
|
||||
// my $leader = getLeader;
|
||||
// exec($nsenter, "--all", "-t", $leader, "--", @args);
|
||||
// die "cannot run ‘nsenter’: $!\n";
|
||||
// }
|
||||
//
|
||||
// from 'run' command:
|
||||
// runInContainer("@su@", "root", "-l", "-c", "exec " . $s);
|
||||
|
||||
fn run_in_container(config: Config, script: &str) {
|
||||
Command::new("systemd-run")
|
||||
.args(&[
|
||||
"--machine",
|
||||
&config.instance_name,
|
||||
"--wait",
|
||||
"--quiet",
|
||||
"--",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
script,
|
||||
])
|
||||
.status()
|
||||
.expect("Failed to run command in container");
|
||||
}
|
||||
|
||||
// from reload script:
|
||||
// ${nixos-container}/bin/nixos-container run "$INSTANCE" -- \
|
||||
// bash --login -c "''${SYSTEM_PATH:-/nix/var/nix/profiles/system}/bin/switch-to-configuration test"
|
||||
|
||||
fn reload_container(config: Config) {
|
||||
unimplemented!();
|
||||
}
|
||||
169
container-tool/src/netlink_commands.rs
Normal file
169
container-tool/src/netlink_commands.rs
Normal file
@@ -0,0 +1,169 @@
|
||||
use futures_util::TryStreamExt;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
use rtnetlink::{
|
||||
AddressMessageBuilder, Error, Handle, LinkMessageBuilder, LinkUnspec, RouteMessageBuilder,
|
||||
};
|
||||
|
||||
const DEFAULT_PREFIX_LEN_V4: u8 = 24;
|
||||
const DEFAULT_PREFIX_LEN_V6: u8 = 64;
|
||||
|
||||
pub async fn ip_link_get_index(handle: &Handle, if_name: &str) -> Option<u32> {
|
||||
let mut links = handle
|
||||
.link()
|
||||
.get()
|
||||
.match_name(if_name.to_string())
|
||||
.execute();
|
||||
|
||||
while let Some(msg) = links.try_next().await.ok()? {
|
||||
return Some(msg.header.index);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub async fn ip_link_up(handle: &Handle, if_index: u32) -> Result<(), Error> {
|
||||
handle
|
||||
.link()
|
||||
.set(LinkUnspec::new_with_index(if_index).up().build())
|
||||
.execute()
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn ip_link_down(handle: &Handle, if_index: u32) -> Result<(), Error> {
|
||||
handle
|
||||
.link()
|
||||
.set(LinkUnspec::new_with_index(if_index).down().build())
|
||||
.execute()
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn ip_link_del(handle: &Handle, if_index: u32) -> Result<(), Error> {
|
||||
handle
|
||||
.link()
|
||||
.del(if_index)
|
||||
.execute()
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn ip_link_add_to_bridge(
|
||||
handle: &Handle,
|
||||
if_index: u32,
|
||||
bridge_index: u32,
|
||||
) -> Result<(), Error> {
|
||||
let message = LinkMessageBuilder::<LinkUnspec>::new()
|
||||
.index(if_index)
|
||||
.controller(bridge_index)
|
||||
.build();
|
||||
|
||||
handle.link().set(message).execute().await
|
||||
}
|
||||
|
||||
pub async fn ip_link_set_name(
|
||||
handle: &Handle,
|
||||
if_index: u32,
|
||||
new_name: &str,
|
||||
) -> Result<(), Error> {
|
||||
handle
|
||||
.link()
|
||||
.set(LinkUnspec::new_with_index(if_index).name(new_name.to_owned()).build())
|
||||
.execute()
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn ip_addr_add(handle: &Handle, if_index: u32, addr: IpAddr) -> Result<(), Error> {
|
||||
handle
|
||||
.address()
|
||||
.add(
|
||||
if_index,
|
||||
addr,
|
||||
if addr.is_ipv4() {
|
||||
DEFAULT_PREFIX_LEN_V4
|
||||
} else {
|
||||
DEFAULT_PREFIX_LEN_V6
|
||||
},
|
||||
)
|
||||
.execute()
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn ip_addr_del(handle: &Handle, if_index: u32, addr: IpAddr) -> Result<(), Error> {
|
||||
let message = match addr {
|
||||
IpAddr::V4(ipv4) => AddressMessageBuilder::<Ipv4Addr>::new()
|
||||
.index(if_index)
|
||||
.address(ipv4, DEFAULT_PREFIX_LEN_V4)
|
||||
.build(),
|
||||
|
||||
IpAddr::V6(ipv6) => AddressMessageBuilder::<Ipv6Addr>::new()
|
||||
.index(if_index)
|
||||
.address(ipv6, DEFAULT_PREFIX_LEN_V6)
|
||||
.build(),
|
||||
};
|
||||
|
||||
handle.address().del(message).execute().await
|
||||
}
|
||||
|
||||
pub async fn ip_route_add(handle: &Handle, if_index: u32, addr: IpAddr) -> Result<(), Error> {
|
||||
match addr {
|
||||
IpAddr::V4(ipv4) => {
|
||||
let message = RouteMessageBuilder::<Ipv4Addr>::new()
|
||||
.destination_prefix(ipv4, DEFAULT_PREFIX_LEN_V4)
|
||||
.output_interface(if_index)
|
||||
.build();
|
||||
|
||||
handle.route().add(message).execute().await
|
||||
}
|
||||
IpAddr::V6(ipv6) => {
|
||||
let message = RouteMessageBuilder::<Ipv6Addr>::new()
|
||||
.destination_prefix(ipv6, DEFAULT_PREFIX_LEN_V6)
|
||||
.output_interface(if_index)
|
||||
.build();
|
||||
|
||||
handle.route().add(message).execute().await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn ip_route_add_default_via_addr(handle: &Handle, if_index: u32, addr: IpAddr) -> Result<(), Error> {
|
||||
match addr {
|
||||
IpAddr::V4(ipv4) => {
|
||||
let message = RouteMessageBuilder::<Ipv4Addr>::new()
|
||||
.destination_prefix(Ipv4Addr::UNSPECIFIED, 0)
|
||||
.gateway(ipv4)
|
||||
.output_interface(if_index)
|
||||
.build();
|
||||
|
||||
handle.route().add(message).execute().await
|
||||
}
|
||||
IpAddr::V6(ipv6) => {
|
||||
let message = RouteMessageBuilder::<Ipv6Addr>::new()
|
||||
.destination_prefix(Ipv6Addr::UNSPECIFIED, 0)
|
||||
.gateway(ipv6)
|
||||
.output_interface(if_index)
|
||||
.build();
|
||||
|
||||
handle.route().add(message).execute().await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn ip_route_del(handle: &Handle, if_index: u32, addr: IpAddr) -> Result<(), Error> {
|
||||
match addr {
|
||||
IpAddr::V4(ipv4) => {
|
||||
let message = RouteMessageBuilder::<Ipv4Addr>::new()
|
||||
.destination_prefix(ipv4, DEFAULT_PREFIX_LEN_V4)
|
||||
.output_interface(if_index)
|
||||
.build();
|
||||
|
||||
handle.route().del(message).execute().await
|
||||
}
|
||||
IpAddr::V6(ipv6) => {
|
||||
let message = RouteMessageBuilder::<Ipv6Addr>::new()
|
||||
.destination_prefix(ipv6, DEFAULT_PREFIX_LEN_V6)
|
||||
.output_interface(if_index)
|
||||
.build();
|
||||
|
||||
handle.route().del(message).execute().await
|
||||
}
|
||||
}
|
||||
}
|
||||
23
flake.lock
generated
23
flake.lock
generated
@@ -17,7 +17,28 @@
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1763606317,
|
||||
"narHash": "sha256-lsq4Urmb9Iyg2zyg2yG6oMQk9yuaoIgy+jgvYM4guxA=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "a5615abaf30cfaef2e32f1ff9bd5ca94e2911371",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
65
flake.nix
65
flake.nix
@@ -1,7 +1,12 @@
|
||||
{
|
||||
inputs.nixpkgs.url = "nixpkgs/nixos-unstable";
|
||||
inputs = {
|
||||
nixpkgs.url = "nixpkgs/nixos-unstable";
|
||||
|
||||
outputs = { self, nixpkgs }: let
|
||||
rust-overlay.url = "github:oxalica/rust-overlay";
|
||||
rust-overlay.inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, rust-overlay }: let
|
||||
inherit (nixpkgs) lib;
|
||||
|
||||
systems = [
|
||||
@@ -9,9 +14,21 @@
|
||||
"aarch64-linux"
|
||||
];
|
||||
|
||||
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system nixpkgs.legacyPackages.${system});
|
||||
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [
|
||||
(import rust-overlay)
|
||||
];
|
||||
};
|
||||
|
||||
rust-bin = rust-overlay.lib.mkRustBin { } pkgs.buildPackages;
|
||||
toolchain = rust-bin.stable.latest.default.override {
|
||||
extensions = [ "rust-src" ];
|
||||
};
|
||||
in f system pkgs toolchain);
|
||||
in {
|
||||
apps = forAllSystems (system: pkgs: {
|
||||
apps = forAllSystems (system: pkgs: _: {
|
||||
default = self.apps.${system}.vm;
|
||||
vm = {
|
||||
type = "app";
|
||||
@@ -19,9 +36,47 @@
|
||||
};
|
||||
});
|
||||
|
||||
devShell = forAllSystems (system: pkgs: toolchain: pkgs.mkShell {
|
||||
nativeBuildInputs = with pkgs; [
|
||||
toolchain
|
||||
cargo-edit
|
||||
];
|
||||
|
||||
RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library";
|
||||
});
|
||||
|
||||
overlays = {
|
||||
default = self.overlays.container-tool;
|
||||
container-tool = final: prev: {
|
||||
inherit (self.packages.${prev.stdenv.hostPlatform.system}) container-tool;
|
||||
};
|
||||
};
|
||||
|
||||
packages = forAllSystems (system: pkgs: _: {
|
||||
default = self.packages.${system}.container-tool;
|
||||
container-tool = let
|
||||
cargoToml = builtins.fromTOML (builtins.readFile ./container-tool/Cargo.toml);
|
||||
in pkgs.callPackage ({
|
||||
lib,
|
||||
rustPlatform,
|
||||
}:
|
||||
rustPlatform.buildRustPackage {
|
||||
pname = cargoToml.package.name;
|
||||
version = cargoToml.package.version;
|
||||
src = pkgs.lib.cleanSource ./container-tool;
|
||||
|
||||
cargoLock.lockFile = ./container-tool/Cargo.lock;
|
||||
meta = with lib; {
|
||||
license = licenses.mit;
|
||||
platforms = platforms.linux ++ platforms.darwin;
|
||||
mainProgram = (lib.head (cargoToml.bin)).name;
|
||||
};
|
||||
}) { };
|
||||
});
|
||||
|
||||
nixosModules.default = ./modules/user-jails.nix;
|
||||
|
||||
nixosConfigurations = lib.mapAttrs' (n: v: lib.nameValuePair "vm-${n}" v) (forAllSystems (system: pkgs:
|
||||
nixosConfigurations = lib.mapAttrs' (n: v: lib.nameValuePair "vm-${n}" v) (forAllSystems (system: pkgs: _:
|
||||
lib.nixosSystem {
|
||||
inherit system pkgs;
|
||||
modules = [
|
||||
|
||||
Reference in New Issue
Block a user