diff --git a/Cargo.lock b/Cargo.lock index e260e7f..123bd29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,20 +2,11 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -28,44 +19,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys", ] [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "byteorder" @@ -75,19 +66,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" - -[[package]] -name = "cc" -version = "1.2.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2" -dependencies = [ - "find-msvc-tools", - "shlex", -] +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cfg-if" @@ -102,10 +83,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] -name = "clap" -version = "4.5.51" +name = "cidr" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" +checksum = "579504560394e388085d0c080ea587dfa5c15f7e251b4d5247d1e1a61d1d6928" +dependencies = [ + "serde", +] + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -113,9 +103,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.51" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -125,9 +115,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", @@ -137,25 +127,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.6" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" - -[[package]] -name = "cordyceps" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" -dependencies = [ - "loom", - "tracing", -] +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "darling" @@ -223,12 +203,6 @@ dependencies = [ "syn", ] -[[package]] -name = "diatomic-waker" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" - [[package]] name = "either" version = "1.15.0" @@ -262,16 +236,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "fastrand" -version = "2.3.0" +name = "errno" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] [[package]] -name = "find-msvc-tools" -version = "0.1.4" +name = "fastrand" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "fixedbitset" @@ -287,9 +265,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -300,24 +278,11 @@ dependencies = [ "futures-util", ] -[[package]] -name = "futures-buffered" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8e0e1f38ec07ba4abbde21eed377082f17ccb988be9d988a5adbf4bafc118fd" -dependencies = [ - "cordyceps", - "diatomic-waker", - "futures-core", - "pin-project-lite", - "spin", -] - [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -325,30 +290,28 @@ dependencies = [ [[package]] name = "futures-concurrency" -version = "7.6.3" +version = "7.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eb68017df91f2e477ed4bea586c59eaecaa47ed885a770d0444e21e62572cd2" +checksum = "175cd8cca9e1d45b87f18ffa75088f2099e3c4fe5e2f83e42de112560bea8ea6" dependencies = [ "fixedbitset", - "futures-buffered", "futures-core", "futures-lite", "pin-project", - "slab", "smallvec", ] [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -357,9 +320,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" @@ -376,9 +339,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -387,21 +350,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -411,24 +374,9 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] -[[package]] -name = "generator" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" -dependencies = [ - "cc", - "cfg-if", - "libc", - "log", - "rustversion", - "windows", -] - [[package]] name = "getset" version = "0.1.6" @@ -443,9 +391,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "heck" @@ -461,9 +409,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "2.12.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown", @@ -494,9 +442,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.177" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "lock_api" @@ -509,22 +457,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" - -[[package]] -name = "loom" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "tracing", - "tracing-subscriber", -] +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "main_error" @@ -532,37 +467,28 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "155db5e86c6e45ee456bf32fad5a290ee1f7151c2faca27ea27097568da67d1a" -[[package]] -name = "matchers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] - [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "mio" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] name = "neli" -version = "0.7.1" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e328dd8a89fa4992c0628ef4ebd9c0b432a5e92522abead20b0f5a8f7bcb812" +checksum = "22f9786d56d972959e1408b6a93be6af13b9c1392036c5c1fafa08a1b0c6ee87" dependencies = [ "bitflags", "byteorder", @@ -576,9 +502,9 @@ dependencies = [ [[package]] name = "neli-proc-macros" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e502fe5db321c6e0ae649ccda600675680125a8e8dee327744fe1910b19332" +checksum = "05d8d08c6e98f20a62417478ebf7be8e1425ec9acecc6f63e22da633f6b71609" dependencies = [ "either", "proc-macro2", @@ -589,8 +515,9 @@ dependencies = [ [[package]] name = "netnsd" -version = "0.1.0" +version = "0.2.0" dependencies = [ + "cidr", "clap", "either", "futures", @@ -614,9 +541,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.30.1" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" dependencies = [ "bitflags", "cfg-if", @@ -626,9 +553,9 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" dependencies = [ "winapi", ] @@ -639,7 +566,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -663,9 +590,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -699,23 +626,23 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-link 0.2.1", + "windows-link", ] [[package]] name = "pin-project" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", @@ -724,15 +651,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" 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" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "proc-macro-error-attr2" @@ -758,18 +679,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.41" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -783,35 +704,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "regex-automata" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" @@ -820,9 +712,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sd-notify" -version = "0.4.5" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b943eadf71d8b69e661330cb0e2656e31040acf21ee7708e2c238a0ec6af2bf4" +checksum = "3e4ef7359e694bfaf1dd27a30f9d760b54c00dfae9f19bd0c05a39bc9128fe76" dependencies = [ "libc", ] @@ -859,9 +751,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.3" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] @@ -884,26 +776,21 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - [[package]] name = "signal-hook-registry" -version = "1.4.6" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -913,20 +800,14 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys", ] -[[package]] -name = "spin" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" - [[package]] name = "strsim" version = "0.11.1" @@ -935,9 +816,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.108" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -946,9 +827,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.37.2" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" +checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f" dependencies = [ "libc", "memchr", @@ -960,18 +841,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -989,9 +870,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.48.0" +version = "1.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ "bytes", "libc", @@ -1000,14 +881,14 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -1016,9 +897,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -1027,9 +908,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.8" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ "indexmap", "serde_core", @@ -1042,33 +923,33 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.4" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -1077,9 +958,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -1088,9 +969,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -1109,27 +990,23 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ - "matchers", "nu-ansi-term", - "once_cell", - "regex-automata", "sharded-slab", "smallvec", "thread_local", - "tracing", "tracing-core", "tracing-log", ] [[package]] name = "unicode-ident" -version = "1.0.20" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "utf8parse" @@ -1139,9 +1016,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uzers" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4df81ff504e7d82ad53e95ed1ad5b72103c11253f39238bcc0235b90768a97dd" +checksum = "0b8275fb1afee25b4111d2dc8b5c505dbbc4afd0b990cb96deb2d88bff8be18d" dependencies = [ "libc", "log", @@ -1183,47 +1060,46 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.61.3" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ "windows-collections", "windows-core", "windows-future", - "windows-link 0.1.3", "windows-numerics", ] [[package]] name = "windows-collections" -version = "0.2.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ "windows-core", ] [[package]] name = "windows-core" -version = "0.61.2" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.1.3", + "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-future" -version = "0.2.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ "windows-core", - "windows-link 0.1.3", + "windows-link", "windows-threading", ] @@ -1249,12 +1125,6 @@ dependencies = [ "syn", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" @@ -1263,39 +1133,30 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-numerics" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ "windows-core", - "windows-link 0.1.3", + "windows-link", ] [[package]] name = "windows-result" -version = "0.3.4" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.1.3", + "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets", + "windows-link", ] [[package]] @@ -1304,85 +1165,20 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link 0.2.1", - "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", + "windows-link", ] [[package]] name = "windows-threading" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ - "windows-link 0.1.3", + "windows-link", ] -[[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" - [[package]] name = "winnow" -version = "0.7.13" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" diff --git a/Cargo.toml b/Cargo.toml index 91e02ea..136eb34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,28 +1,29 @@ [package] name = "netnsd" -version = "0.1.0" +version = "0.2.0" edition = "2024" rust-version = "1.88.0" [dependencies] -tokio = { version = "1.48.0", features = ["macros", "rt", "signal", "net", "io-util"] } -tokio-stream = { version = "0.1.17", features = ["signal", "net"] } -toml = "0.9.8" +tokio = { version = "1.49.0", features = ["macros", "rt", "signal", "net", "io-util"] } +tokio-stream = { version = "0.1.18", features = ["signal", "net"] } +toml = "1.0.1" serde = { version = "1.0.228", features = ["derive"] } -clap = { version = "4.5.51", features = ["derive"] } -thiserror = "2.0.17" -tracing = "0.1.41" -tracing-subscriber = "0.3.20" +clap = { version = "4.5.58", features = ["derive"] } +thiserror = "2.0.18" +tracing = "0.1.44" +tracing-subscriber = "0.3.22" main_error = "0.1.2" -nix = { version = "0.30.1", features = ["mount", "sched", "user", "signal"] } -sd-notify = "0.4.5" +nix = { version = "0.31.1", features = ["mount", "sched", "user", "signal"] } +sd-notify = "0.5.0" futures = "0.3.31" -futures-concurrency = "7.6.3" -neli = "0.7.1" +futures-concurrency = "7.7.1" +neli = "0.7.4" either = "1.15.0" -uzers = "0.12.1" -sysinfo = "0.37.2" +uzers = "0.12.2" +sysinfo = "0.38.1" landlock = "0.4.4" +cidr = { version = "0.3.2", features = ["serde"] } [dev-dependencies] -serde_test = "1.0.177" \ No newline at end of file +serde_test = "1.0.177" diff --git a/README.md b/README.md index 40ebe43..5aa49de 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,11 @@ A declarative manager for Linux network namespaces. ## Features - Fully declarative configuration +- Standalone binary with no runtime dependencies - Hot reloading of configuration -- Port forwarding into the namespace +- Port forwarding into or out of the namespace +- Moving network devices to the namespace +- Setting up routing inside the namespace ## Usage @@ -53,6 +56,13 @@ You can specify a different configuration path with the `--config` option. [[namespace]] # name of the namespace to create name = "test" +# move existing devices into the namespace +devices = ["somelink"] + +# create a route inside the namespace +[[namespace.route]] +destination = "default" # either "default" or an ip range in CIDR notation +device = "somelink" # You can define any number of port forwards to setup into the namespace [[namespace.forward]] diff --git a/config.sample.toml b/config.sample.toml index a4132ce..7cfdef8 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -2,6 +2,13 @@ [[namespace]] # name of the namespace to create name = "test" +# move existing devices into the namespace +devices = ["somelink"] + +# create a route inside the namespace +[[namespace.route]] +destination = "default" # either "default" or an ip range in CIDR notation +device = "somelink" # You can define any number of port forwards to setup into the namespace [[namespace.forward]] diff --git a/flake.lock b/flake.lock index 4e38b6d..e7e1995 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "crane": { "locked": { - "lastModified": 1763938834, - "narHash": "sha256-j8iB0Yr4zAvQLueCZ5abxfk6fnG/SJ5JnGUziETjwfg=", + "lastModified": 1774313767, + "narHash": "sha256-hy0XTQND6avzGEUFrJtYBBpFa/POiiaGBr2vpU6Y9tY=", "owner": "ipetkov", "repo": "crane", - "rev": "d9e753122e51cee64eb8d2dddfe11148f339f5a2", + "rev": "3d9df76e29656c679c744968b17fbaf28f0e923d", "type": "github" }, "original": { @@ -22,11 +22,11 @@ ] }, "locked": { - "lastModified": 1764593611, - "narHash": "sha256-6SdexcO69Dlu14YN2xuB1A6JHWSrcqMj7Na9oK7IT2M=", + "lastModified": 1777298791, + "narHash": "sha256-MEQeYwRQcV7RvlKMVrFy07dmoY8t2s/SIK7EpCNLOu8=", "owner": "nix-community", "repo": "flakelight", - "rev": "0d63256401341f528dd628f1a8e96d3afecade7a", + "rev": "c4b125c5453559de6a228be70f71abeb017f3265", "type": "github" }, "original": { @@ -44,11 +44,11 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1764619631, - "narHash": "sha256-WojMP5S9qLmOLecEQ+7+yc33Ly1ydoRsODNG6hlLqiQ=", + "lastModified": 1776692281, + "narHash": "sha256-DCCSbpTUDqRiHsTS/sIgCTwa1r4gsJlWSFE6difdWxk=", "ref": "refs/heads/main", - "rev": "0fae557bf52d8493840aca52d433c473ecc305ef", - "revCount": 67, + "rev": "d1d0e0bb5b0acebed06ccced9cc27f82aafab058", + "revCount": 71, "type": "git", "url": "https://codeberg.org/icewind/mill-scale" }, @@ -59,11 +59,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1764522689, - "narHash": "sha256-SqUuBFjhl/kpDiVaKLQBoD8TLD+/cTUzzgVFoaHrkqY=", + "lastModified": 1777428379, + "narHash": "sha256-ypxFOeDz+CqADEQNL72haqGjvZQdBR5Vc7pyx2JDttI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8bb5646e0bed5dbd3ab08c7a7cc15b75ab4e1d0f", + "rev": "755f5aa91337890c432639c60b6064bb7fe67769", "type": "github" }, "original": { @@ -88,11 +88,11 @@ ] }, "locked": { - "lastModified": 1764557621, - "narHash": "sha256-kX5PoY8hQZ80+amMQgOO9t8Tc1JZ70gYRnzaVD4AA+o=", + "lastModified": 1774535687, + "narHash": "sha256-dpKS/8+uB0EoI4mCrpio+xs8Xxry6ZhLLwV8VIbbfrs=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "93316876c2229460a5d6f5f052766cc4cef538ce", + "rev": "75900435aa883f84b038316864b3f60956681523", "type": "github" }, "original": { diff --git a/nix/module.nix b/nix/module.nix index 855155a..903d14a 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -45,11 +45,37 @@ in { type = types.oneOf [types.port types.str]; description = "target port or address inside the namespace"; }; + reverse = mkOption { + type = types.bool; + default = false; + description = "forward from inside the namespace to outside instead"; + }; }; })); description = "ports to forward into the namespace"; default = []; }; + devices = mkOption { + type = types.listOf types.str; + default = []; + description = "devices to move into the namespace"; + }; + route = mkOption { + type = types.listOf (types.submodule ({config, ...}: { + options = { + device = mkOption { + type = types.str; + description = "device to route the traffic trough"; + }; + destination = mkOption { + type = types.str; + description = "What traffic to route. Either \"default\" or an ip range in CIDR notation"; + }; + }; + })); + description = "routes to setup inside the namespace"; + default = []; + }; }; })); description = "namespaces to setup"; @@ -61,7 +87,7 @@ in { # symlink instead of passing `configFile` directly to netnsd to allow changing the config without changing the path environment.etc."netnsd/netnsd.toml".source = configFile; - environment.systemPackages = with pkgs; [cfg.package]; + environment.systemPackages = [cfg.package]; systemd.services.netnsd = { reloadTriggers = [configFile]; diff --git a/nix/package.nix b/nix/package.nix index 382e08e..6afae2d 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -12,7 +12,7 @@ rustc = rust-bin.stable.latest.minimal; }; in - rustPlatform.buildRustPackage rec { + rustPlatform.buildRustPackage { pname = cargoPackage.name; inherit (cargoPackage) version; diff --git a/src/config/mod.rs b/src/config/mod.rs index 779fce1..0a31dd0 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -2,13 +2,18 @@ mod name; mod source; mod target; -pub use crate::config::name::NamespaceName; +pub use crate::config::name::{DeviceName, NamespaceName}; pub use crate::config::source::ForwardSource; pub use crate::config::target::ForwardTarget; -use serde::Deserialize; +use cidr::AnyIpCidr; +use serde::de::Error; +use serde::{Deserialize, Deserializer}; +use std::borrow::Cow; use std::collections::HashSet; +use std::fmt::{Display, Formatter}; use std::fs::read_to_string; use std::path::{Path, PathBuf}; +use std::str::FromStr; use thiserror::Error; use toml::from_str; @@ -80,7 +85,12 @@ impl RawConfig { #[derive(Deserialize, Debug)] pub struct NamespaceConfig { pub name: NamespaceName, + #[serde(default)] pub forward: Vec, + #[serde(default)] + pub devices: Vec, + #[serde(default, rename = "route")] + pub routes: Vec, } #[derive(Deserialize, Debug)] @@ -91,6 +101,27 @@ pub struct ForwardConfig { pub reverse: bool, } +#[derive(Deserialize, Debug, PartialEq, Clone)] +pub struct RouteConfig { + #[serde(deserialize_with = "parse_cidr")] + pub destination: AnyIpCidr, + pub device: DeviceName, +} + +impl Display for RouteConfig { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{} dev {}", self.destination, self.device) + } +} + +fn parse_cidr<'de, D: Deserializer<'de>>(deserializer: D) -> Result { + let str = Cow::<'de, str>::deserialize(deserializer)?; + match str.as_ref() { + "default" => Ok(AnyIpCidr::Any), + str => AnyIpCidr::from_str(str).map_err(D::Error::custom), + } +} + #[derive(Debug, Error)] pub enum ConfigError { #[error("Error while reading config from {}: {error:#}", path.display())] diff --git a/src/config/name.rs b/src/config/name.rs index 32fbbd6..9490fbe 100644 --- a/src/config/name.rs +++ b/src/config/name.rs @@ -7,8 +7,18 @@ use std::str::FromStr; use thiserror::Error; #[derive(Debug, Clone, Eq, PartialEq, Hash)] +struct ValidatedName(String); + +#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize)] +#[serde(from = "ValidatedName")] pub struct NamespaceName(String); +impl From for NamespaceName { + fn from(value: ValidatedName) -> Self { + NamespaceName(value.0) + } +} + impl TryFrom for NamespaceName { type Error = (); @@ -52,7 +62,60 @@ impl From for String { } } -impl<'de> Deserialize<'de> for NamespaceName { +#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize)] +#[serde(from = "ValidatedName")] +pub struct DeviceName(String); + +impl From for DeviceName { + fn from(value: ValidatedName) -> Self { + DeviceName(value.0) + } +} + +impl TryFrom for DeviceName { + type Error = (); + + fn try_from(value: OsString) -> Result { + let str = value.into_string().map_err(|_| ())?; + if validate_name(&str) { + Ok(DeviceName(str)) + } else { + Err(()) + } + } +} + +impl Display for DeviceName { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl AsRef for DeviceName { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} + +impl AsRef for DeviceName { + fn as_ref(&self) -> &Path { + self.0.as_ref() + } +} + +impl PartialEq<&str> for DeviceName { + fn eq(&self, other: &&str) -> bool { + self.0 == *other + } +} + +impl From for String { + fn from(value: DeviceName) -> Self { + value.0 + } +} + +impl<'de> Deserialize<'de> for ValidatedName { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, @@ -60,7 +123,7 @@ impl<'de> Deserialize<'de> for NamespaceName { struct NamespaceNameVisitor; impl Visitor<'_> for NamespaceNameVisitor { - type Value = NamespaceName; + type Value = ValidatedName; fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { formatter.write_str("A valid namespace name") @@ -73,7 +136,7 @@ impl<'de> Deserialize<'de> for NamespaceName { if !validate_name(v) { return Err(E::invalid_value(Unexpected::Str(v), &self)); } - Ok(NamespaceName(v.into())) + Ok(ValidatedName(v.into())) } fn visit_string(self, v: String) -> Result @@ -83,7 +146,7 @@ impl<'de> Deserialize<'de> for NamespaceName { if !validate_name(&v) { return Err(E::invalid_value(Unexpected::Str(&v), &self)); } - Ok(NamespaceName(v)) + Ok(ValidatedName(v)) } } @@ -92,20 +155,24 @@ impl<'de> Deserialize<'de> for NamespaceName { } impl FromStr for NamespaceName { - type Err = InvalidNamespaceNameError; + type Err = InvalidNameError; fn from_str(s: &str) -> Result { if !validate_name(s) { - return Err(InvalidNamespaceNameError { name: s.into() }); + return Err(InvalidNameError { + name: s.into(), + kind: "namespace", + }); } Ok(NamespaceName(s.into())) } } #[derive(Debug, Error)] -#[error("invalid name for namespace: '{name}'")] -pub struct InvalidNamespaceNameError { +#[error("invalid name for {kind}: '{name}'")] +pub struct InvalidNameError { name: String, + kind: &'static str, } /// Check if a name follows the portable filename character set diff --git a/src/daemon.rs b/src/daemon.rs index 57d6a0b..2367114 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -1,5 +1,10 @@ -use crate::config::{Config, ForwardConfig, NamespaceConfig, NamespaceName}; -use crate::namespace::{NamespaceError, NetNs}; +use crate::config::{ + Config, DeviceName, ForwardConfig, NamespaceConfig, NamespaceName, RouteConfig, +}; +use crate::link::{LinkError, LinkManager}; +use crate::namespace::{ + NamespaceEnterError, NamespaceError, NamespaceHandle, NamespaceHandleError, NetNs, +}; use crate::proxy::{ActiveProxy, ProxyError}; use futures::FutureExt; use futures::StreamExt; @@ -25,7 +30,15 @@ async fn daemon_async(mut config: Config) -> Result<(), DaemonError> { state.update(&config)?; // now the namespaces are setup, we can tell systemd to start any service depending on them - notify(false, &[NotifyState::Ready]).map_err(DaemonError::Notify)?; + notify(&[ + NotifyState::Ready, + NotifyState::Status(&format!( + "Started with {} namespaces", + state.namespaces.len() + )), + ]) + .map_err(DaemonError::Notify)?; + info!("ready"); let reload_signal = signal(SignalKind::hangup()).map_err(DaemonError::Signal)?; let reload_signal = SignalStream::new(reload_signal).map(|_| Event::Reload); @@ -52,7 +65,7 @@ async fn daemon_async(mut config: Config) -> Result<(), DaemonError> { match NotifyState::monotonic_usec_now() { Ok(notify_time) => { - notify(false, &[NotifyState::Reloading, notify_time]) + notify(&[NotifyState::Reloading, notify_time]) .map_err(DaemonError::Notify)?; } Err(error) => { @@ -70,7 +83,15 @@ async fn daemon_async(mut config: Config) -> Result<(), DaemonError> { } } - notify(false, &[NotifyState::Ready]).map_err(DaemonError::Notify)?; + notify(&[ + NotifyState::Ready, + NotifyState::Status(&format!( + "Reloaded with {} namespaces", + state.namespaces.len() + )), + ]) + .map_err(DaemonError::Notify)?; + info!("reloaded"); } Event::Info => { for namespace in &state.namespaces { @@ -83,7 +104,7 @@ async fn daemon_async(mut config: Config) -> Result<(), DaemonError> { } } - let _ = notify(false, &[NotifyState::Stopping]); + let _ = notify(&[NotifyState::Stopping]); Ok(()) } @@ -101,7 +122,7 @@ struct State { impl State { pub fn new() -> Result { - let namespaces = NetNs::existing()? + let namespaces = NetNs::existing(false)? .map(ActiveNamespace::new) .collect::, _>>()?; Ok(State { namespaces }) @@ -124,6 +145,8 @@ impl State { for namespace in &mut self.namespaces { let config = config.get_namespace(namespace.name()).unwrap(); namespace.update_proxies(config)?; + namespace.update_devices(config)?; + namespace.update_links(config)?; } Ok(()) @@ -139,6 +162,8 @@ impl State { struct ActiveNamespace { ns: NetNs, proxies: Vec, + devices: Vec, + routes: Vec, } impl ActiveNamespace { @@ -148,6 +173,8 @@ impl ActiveNamespace { Ok(ActiveNamespace { ns, proxies: Vec::default(), + devices: Vec::default(), + routes: Vec::default(), }) } @@ -164,10 +191,106 @@ impl ActiveNamespace { Ok(()) } + pub fn update_devices(&mut self, config: &NamespaceConfig) -> Result<(), DaemonError> { + let parent_namespace = NamespaceHandle::parent()?; + + let removed: Vec<_> = self + .devices + .extract_if(.., |existing| { + !config.devices.iter().any(|new| existing == new) + }) + .collect(); + + self.ns.handle().run_in(move || { + let link_manager = LinkManager::new()?; + for link in link_manager.get_links()?.flatten() { + if removed.iter().any(|name| *name == link.name.as_str()) { + info!(namespace = %config.name, link = link.name , "moving link out of namespace"); + link_manager.move_link(&link, &parent_namespace)? + } + } + Ok::<_, LinkError>(()) + })??; + + let mut added = Vec::new(); + for new in &config.devices { + if !self.has_device(new) { + added.push(new.clone()); + } + } + + let link_manager = LinkManager::new()?; + for link in link_manager.get_links()?.flatten() { + if added.iter().any(|name| *name == link.name.as_str()) { + info!(namespace = %config.name, link = link.name , "moving link into namespace"); + link_manager.move_link(&link, self.ns.handle())? + } + } + for new in added { + self.devices.push(new); + } + + Ok(()) + } + + pub fn update_links(&mut self, config: &NamespaceConfig) -> Result<(), DaemonError> { + let removed: Vec<_> = self + .routes + .extract_if(.., |existing| { + !config.routes.iter().any(|new| existing == new) + }) + .collect(); + + let mut added = Vec::new(); + for new in &config.routes { + if !self.has_route(new) { + added.push(new.clone()); + } + } + + self.ns.handle().run_in(|| { + let link_manager = LinkManager::new()?; + for link in link_manager.get_links()?.flatten() { + if let Some(route) = removed + .iter() + .find(|route| route.device == link.name.as_str()) + { + info!(namespace = %config.name, %route, "deleting route"); + link_manager.delete_route(&link, route.destination)?; + } + } + + for link in link_manager.get_links()?.flatten() { + if let Some(route) = added + .iter() + .find(|route| route.device == link.name.as_str()) + { + info!(namespace = %config.name, %route, "adding route"); + link_manager.add_route(&link, route.destination)?; + } + } + Ok::<_, DaemonError>(()) + })??; + + for new in added { + self.routes.push(new); + } + + Ok(()) + } + fn has_forward(&self, config: &ForwardConfig) -> bool { self.proxies.iter().any(|existing| existing == config) } + fn has_device(&self, name: &DeviceName) -> bool { + self.devices.iter().any(|existing| existing == name) + } + + fn has_route(&self, route: &RouteConfig) -> bool { + self.routes.iter().any(|existing| existing == route) + } + pub fn name(&self) -> &NamespaceName { self.ns.name() } @@ -183,4 +306,10 @@ pub enum DaemonError { Signal(IoError), #[error(transparent)] Proxy(#[from] ProxyError), + #[error(transparent)] + Handle(#[from] NamespaceHandleError), + #[error(transparent)] + Enter(#[from] NamespaceEnterError), + #[error(transparent)] + Link(#[from] LinkError), } diff --git a/src/down.rs b/src/down.rs index f29f50d..fed2891 100644 --- a/src/down.rs +++ b/src/down.rs @@ -2,7 +2,7 @@ use crate::namespace::NetNs; use main_error::MainResult; pub fn down() -> MainResult { - for name in NetNs::existing()? { + for name in NetNs::existing(true)? { let ns = NetNs::new(name)?; ns.delete()? } diff --git a/src/link.rs b/src/link.rs index 4543393..98bf0cc 100644 --- a/src/link.rs +++ b/src/link.rs @@ -1,96 +1,265 @@ +use cidr::{AnyIpCidr, Family}; use neli::consts::nl::NlmF; -use neli::consts::rtnl::Ifla; -use neli::consts::rtnl::RtAddrFamily; use neli::consts::rtnl::Rtm; +use neli::consts::rtnl::{Ifla, RtScope, RtTable, Rta}; +use neli::consts::rtnl::{RtAddrFamily, Rtn, Rtprot}; use neli::consts::socket::NlFamily; use neli::err::RouterError; use neli::nl::NlPayload; use neli::router::synchronous::NlRouter; -use neli::rtnl::Ifinfomsg; -use neli::rtnl::IfinfomsgBuilder; +use neli::rtnl::{Ifinfomsg, RtattrBuilder, Rtmsg}; +use neli::rtnl::{IfinfomsgBuilder, RtmsgBuilder}; +use neli::types::{Buffer, RtBuffer}; use neli::utils::Groups; -use nix::errno::Errno; -use nix::sched::{setns, CloneFlags}; -use std::fs::File; -use std::io::Error as IoError; -use std::path::{Path, PathBuf}; -use std::thread::spawn; +use nix::libc::c_int; +use std::fmt::{Debug, Display, Formatter}; +use std::os::fd::AsRawFd; use thiserror::Error; +use tracing::{info, instrument}; #[derive(Debug, Error)] pub enum LinkError { - #[error("failed to communicate with netlink")] - Netlink, - #[error("failed to code netlink response")] - Parse, - #[error("unexpected panic in link setup")] - Panic, - #[error("failed to enter namespace in link setup: {0}")] - Namespace(Errno), - #[error("Failed to open namespace file {}: {error:#}", path.display())] - OpenNamespace { path: PathBuf, error: IoError }, + #[error("Failed to communicate with netlink: {0}")] + Netlink(String), + #[error("Failed to parse netlink response: {0}")] + Parse(String), + #[error("Link not found: {0}")] + NotFound(String), } -impl From> for LinkError { - fn from(_value: RouterError) -> Self { - LinkError::Netlink +impl From> for LinkError +where + T: Debug, + P: Debug, +{ + fn from(value: RouterError) -> Self { + LinkError::Netlink(value.to_string()) } } -/// Set a link to UP inside a namespace -pub fn link_up_ns(namespace: impl AsRef, link_name: &'static str) -> Result<(), LinkError> { - let namespace = namespace.as_ref(); - let ns_handle = File::open(namespace).map_err(|error| LinkError::OpenNamespace { - error, - path: namespace.into(), - })?; +pub struct LinkManager { + router: NlRouter, +} - spawn(move || { - setns(ns_handle, CloneFlags::CLONE_NEWNET).map_err(LinkError::Namespace)?; - link_up(link_name) - }) - .join() - .map_err(|_| LinkError::Panic)? +pub struct Link { + family: RtAddrFamily, + index: c_int, + pub name: String, +} + +impl Display for Link { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.name) + } +} + +impl Link { + fn msg_builder(&self) -> IfinfomsgBuilder { + IfinfomsgBuilder::default() + .ifi_family(self.family) + .ifi_index(self.index) + } +} + +impl LinkManager { + pub fn new() -> Result { + let (router, _) = NlRouter::connect(NlFamily::Route, None, Groups::empty())?; + router.enable_ext_ack(true)?; + router.enable_strict_checking(true)?; + Ok(LinkManager { router }) + } + + pub fn get_link(&self, name: impl AsRef) -> Result { + let name = name.as_ref(); + for link in self.get_links()? { + let link = link?; + if link.name == name { + return Ok(link); + } + } + Err(LinkError::NotFound(name.into())) + } + + pub fn get_links(&self) -> Result>, LinkError> { + let ifinfomsg = IfinfomsgBuilder::default() + .ifi_family(RtAddrFamily::Inet) + .build() + .unwrap(); + + let recv = self.router.send::<_, _, Rtm, Ifinfomsg>( + Rtm::Getlink, + NlmF::DUMP | NlmF::ACK, + NlPayload::Payload(ifinfomsg), + )?; + Ok(recv + .map(|response| { + if let Some(payload) = response?.get_payload() { + let name = payload + .rtattrs() + .get_attr_handle() + .get_attr_payload_as_with_len::(Ifla::Ifname) + .map_err(|e| LinkError::Parse(e.to_string()))?; + Ok(Some(Link { + family: *payload.ifi_family(), + index: *payload.ifi_index(), + name, + })) + } else { + Ok(None) + } + }) + .filter_map(|item| item.transpose())) + } + + /// Move a link to a namespace + pub fn move_link(&self, link: &Link, namespace: &Fd) -> Result<(), LinkError> { + let ns_handle = namespace.as_raw_fd(); + + let mut info_attrs = RtBuffer::::new(); + info_attrs.push( + RtattrBuilder::default() + .rta_type(Ifla::NetNsFd) + .rta_payload(ns_handle) + .build() + .expect("invalid rtattr"), + ); + + let msg = link.msg_builder().rtattrs(info_attrs).build().unwrap(); + self.router.send::<_, _, Rtm, Ifinfomsg>( + Rtm::Setlink, + NlmF::ACK, + NlPayload::Payload(msg), + )?; + Ok(()) + } + + pub fn up(&self, link: &Link) -> Result<(), LinkError> { + let up_msg = link.msg_builder().up().build().unwrap(); + self.router.send::<_, _, Rtm, Ifinfomsg>( + Rtm::Setlink, + NlmF::ACK, + NlPayload::Payload(up_msg), + )?; + Ok(()) + } + + #[instrument(skip_all, fields(link = %link, destination = %destination))] + pub fn add_route(&self, link: &Link, destination: AnyIpCidr) -> Result<(), LinkError> { + let rt_msg = route_message_for(link, destination); + + let res = self.router.send::<_, _, Rtm, Rtmsg>( + Rtm::Newroute, + NlmF::CREATE | NlmF::EXCL | NlmF::REQUEST | NlmF::ACK, + NlPayload::Payload(rt_msg), + )?; + + for msg in res { + match msg { + Err(RouterError::Nlmsgerr(err)) if *err.error() == -17 => { + info!("route already exists"); + // already exists + } + Err(err) => { + return Err(err.into()); + } + _ => {} + } + } + Ok(()) + } + + #[instrument(skip_all, fields(link = %link, destination = %destination))] + pub fn delete_route(&self, link: &Link, destination: AnyIpCidr) -> Result<(), LinkError> { + let rt_msg = route_message_for(link, destination); + + let res = self.router.send::<_, _, Rtm, Rtmsg>( + Rtm::Delroute, + NlmF::REQUEST | NlmF::ACK, + NlPayload::Payload(rt_msg), + )?; + + for msg in res { + msg?; + } + Ok(()) + } } /// Set a link to UP pub fn link_up(link_name: &str) -> Result<(), LinkError> { - // I honestly don't really know how this code works - // It's mostly a copy from one of neli's examples and seems to do what it needs to - let (rtnl, _) = NlRouter::connect(NlFamily::Route, None, Groups::empty())?; - rtnl.enable_ext_ack(true)?; - rtnl.enable_strict_checking(true)?; - let ifinfomsg = IfinfomsgBuilder::default() - .ifi_family(RtAddrFamily::Inet) - .build() - .unwrap(); + let manager = LinkManager::new()?; + let link = manager.get_link(link_name)?; + manager.up(&link) +} - let recv = rtnl.send::<_, _, Rtm, Ifinfomsg>( - Rtm::Getlink, - NlmF::DUMP | NlmF::ACK, - NlPayload::Payload(ifinfomsg), - )?; - for response in recv { - if let Some(payload) = response?.get_payload() { - let name = payload - .rtattrs() - .get_attr_handle() - .get_attr_payload_as_with_len::(Ifla::Ifname) - .map_err(|_| LinkError::Parse)?; - if name == link_name { - let up_msg = IfinfomsgBuilder::default() - .ifi_family(RtAddrFamily::Inet) - .ifi_index(*payload.ifi_index()) - .up() - .build() - .unwrap(); - rtnl.send::<_, _, Rtm, Ifinfomsg>( - Rtm::Setlink, - NlmF::ACK, - NlPayload::Payload(up_msg), - )?; - } +/// Move a link into a namespace +pub fn move_link_into(link_name: &str, namespace: &Fd) -> Result<(), LinkError> { + let manager = LinkManager::new()?; + // todo, might already be in target ns + let link = manager.get_link(link_name)?; + info!(name = &link.name, "moving link into namespace"); + manager.move_link(&link, namespace) +} + +/// Move all links from the current namespace (except lo) into a namespace +pub fn move_all_links(namespace: &Fd) -> Result<(), LinkError> { + let manager = LinkManager::new()?; + for link in manager.get_links()?.flatten() { + if link.name != "lo" { + info!(name = &link.name, "moving link"); + manager.move_link(&link, namespace)? } } - Ok(()) + Ok::<_, LinkError>(()) +} + +fn route_message_for(link: &Link, destination: AnyIpCidr) -> Rtmsg { + let mut info_attrs = RtBuffer::::new(); + match &destination { + AnyIpCidr::V4(addr) => { + info_attrs.push( + RtattrBuilder::default() + .rta_type(Rta::Dst) + .rta_payload(addr.first_address().octets()) + .build() + .expect("invalid rtattr"), + ); + } + AnyIpCidr::V6(addr) => { + info_attrs.push( + RtattrBuilder::default() + .rta_type(Rta::Dst) + .rta_payload(addr.first_address().octets()) + .build() + .expect("invalid rtattr"), + ); + } + _ => {} + } + info_attrs.push( + RtattrBuilder::default() + .rta_type(Rta::Oif) + .rta_payload(link.index) + .build() + .expect("invalid rtattr"), + ); + + let family = match &destination.family() { + None => RtAddrFamily::Inet, + Some(Family::Ipv4) => RtAddrFamily::Inet, + Some(Family::Ipv6) => RtAddrFamily::Inet6, + }; + + RtmsgBuilder::default() + .rtm_table(RtTable::Main) + .rtm_scope(RtScope::Universe) + .rtm_family(family) + .rtattrs(info_attrs) + .rtm_src_len(0) + .rtm_tos(0) + .rtm_protocol(Rtprot::Boot) + .rtm_type(Rtn::Unicast) + .rtm_dst_len(destination.network_length().unwrap_or_default()) + .build() + .expect("rt msg") } diff --git a/src/main.rs b/src/main.rs index e5e52a7..c906f49 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,10 +5,10 @@ use crate::proxy::proxy; use crate::up::up; use clap::{Parser, Subcommand}; use main_error::MainResult; -use std::path::PathBuf; use nix::errno::Errno; -use nix::sys::signal::{kill, Signal}; +use nix::sys::signal::{Signal, kill}; use nix::unistd::Pid; +use std::path::PathBuf; use sysinfo::{ProcessRefreshKind, RefreshKind, System, UpdateKind}; use tracing::{error, info, warn}; @@ -94,10 +94,12 @@ fn reload() -> MainResult { match kill(Pid::from_raw(proc.pid().as_u32() as i32), Signal::SIGHUP) { Ok(_) => { info!("Sent reload command to daemon") - }, + } Err(Errno::EPERM) => { - error!("Sending signal not permitted, try are you running the command as root?"); - }, + error!( + "Sending signal not permitted, try are you running the command as root?" + ); + } Err(error) => { error!(%error, "Unexpected error"); } diff --git a/src/namespace/handle.rs b/src/namespace/handle.rs new file mode 100644 index 0000000..3e73187 --- /dev/null +++ b/src/namespace/handle.rs @@ -0,0 +1,76 @@ +use crate::namespace::NamespaceEnterError; +use nix::errno::Errno; +use nix::sched::{setns, CloneFlags}; +use std::fs::File; +use std::io::Error as IoError; +use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd}; +use std::path::{Path, PathBuf}; +use std::thread::scope; +use thiserror::Error; + +pub struct NamespaceHandle { + path: PathBuf, + fd: OwnedFd, +} + +impl NamespaceHandle { + /// Open the namespace handle for a path + pub fn open>(path: P) -> Result { + let path = path.as_ref(); + let file = File::open(path).map_err(|error| NamespaceHandleError::Open { + error, + path: path.into(), + })?; + Ok(NamespaceHandle { + path: path.into(), + fd: file.into(), + }) + } + + /// Open the namespace handle for the namespace the current process is in + pub fn parent() -> Result { + Self::open("/proc/self/ns/net") + } + + pub fn run_in T + Send>(&self, f: F) -> Result { + scope(|scope| { + scope + .spawn(|| { + setns(&self.fd, CloneFlags::CLONE_NEWNET)?; + Ok(f()) + }) + .join() + .expect("namespace thread panicked") + }) + .map_err(|error| NamespaceEnterError { + namespace: self.path.clone(), + error, + }) + } +} + +impl AsFd for NamespaceHandle { + fn as_fd(&self) -> BorrowedFd<'_> { + self.fd.as_fd() + } +} + +impl AsRawFd for NamespaceHandle { + fn as_raw_fd(&self) -> RawFd { + self.fd.as_raw_fd() + } +} + +impl AsRawFd for &NamespaceHandle { + fn as_raw_fd(&self) -> RawFd { + self.fd.as_raw_fd() + } +} + +#[derive(Debug, Error)] +pub enum NamespaceHandleError { + #[error("Failed to open namespace handle {}: {error:#}", path.display())] + Open { path: PathBuf, error: IoError }, + #[error("Failed to enter namespace: {0:#}")] + Enter(Errno), +} diff --git a/src/namespace/mod.rs b/src/namespace/mod.rs index b10c635..747281b 100644 --- a/src/namespace/mod.rs +++ b/src/namespace/mod.rs @@ -1,8 +1,12 @@ +mod handle; mod raw; +mod sysctl; -use crate::config::NamespaceName; -use crate::link::{link_up_ns, LinkError}; +use crate::config::{DeviceName, NamespaceName}; +use crate::link::{link_up, move_all_links, move_link_into, LinkError}; +pub use crate::namespace::handle::{NamespaceHandle, NamespaceHandleError}; use crate::namespace::raw::{create_network_namespace, NamespaceSetupError}; +use crate::namespace::sysctl::{CtlError, NamespaceCtl}; use either::Either; use nix::errno::Errno; use nix::mount::{mount, umount2, MntFlags, MsFlags}; @@ -12,16 +16,19 @@ use std::iter::empty; use std::os::unix::fs::symlink; use std::path::{Path, PathBuf}; use thiserror::Error; -use tracing::{debug, error, info}; +use tracing::{debug, info}; pub struct NetNs { name: NamespaceName, path: PathBuf, nsd_path: PathBuf, + handle: NamespaceHandle, } impl NetNs { - pub fn existing() -> Result, NamespaceError> { + pub fn existing( + include_broken: bool, + ) -> Result, NamespaceError> { let dir = match read_dir("/var/run/netnsd") { Ok(dir) => Ok(dir), Err(error) if error.kind() == ErrorKind::NotFound => { @@ -32,12 +39,14 @@ impl NetNs { error, }), }?; - Ok(Either::Right(dir.flatten().flat_map(|entry| { - NamespaceName::try_from(entry.file_name()).ok() - }))) + Ok(Either::Right( + dir.flatten() + .filter(move |entry| include_broken || entry.path().is_symlink()) + .flat_map(|entry| NamespaceName::try_from(entry.file_name()).ok()), + )) } - /// Create a new named network namespace that will be removed when dropped + /// Create a new named network namespace pub fn new(name: NamespaceName) -> Result { let parent = Path::new("/var/run/netns"); let nsd_parent = Path::new("/var/run/netnsd"); @@ -47,22 +56,34 @@ impl NetNs { let path = parent.join(&name); let nsd_path = nsd_parent.join(&name); + remove_non_mount(&path).map_err(|error| NamespaceError::Delete { + error, + path: nsd_path.clone(), + })?; + match File::create_new(&path) { Ok(_) => {} Err(e) if e.kind() == ErrorKind::AlreadyExists => { info!(%name, "using existing network namespace"); if !nsd_path.is_symlink() { + remove_file_if_exists(&nsd_path).map_err(|error| NamespaceError::Delete { + error, + path: nsd_path.clone(), + })?; + symlink(&path, &nsd_path).map_err(|error| NamespaceError::Symlink { error, path: nsd_path.clone(), })?; } + let handle = NamespaceHandle::open(&path)?; return Ok(NetNs { name, nsd_path, path, + handle, }); } Err(e) => return Err(NamespaceError::from_create(path.clone(), e)), @@ -77,10 +98,12 @@ impl NetNs { path: nsd_path.clone(), })?; } + let handle = NamespaceHandle::open(&path)?; Result::<_, NamespaceError>::Ok(NetNs { name, path, nsd_path, + handle, }) })?; @@ -133,24 +156,46 @@ impl NetNs { } fn setup_interfaces(&self) -> Result<(), NamespaceError> { - link_up_ns(&self.path, "lo")?; + let ctl = NamespaceCtl::read()?; + self.handle.run_in(move || { + link_up("lo").map_err(NamespaceError::from)?; + dbg!(ctl).apply()?; + Ok::<_, NamespaceError>(()) + })??; Ok(()) } pub fn delete(self) -> Result<(), NamespaceError> { + let parent_namespace = NamespaceHandle::parent()?; + + self.handle.run_in(|| move_all_links(&parent_namespace))??; let name = self.path.file_name().unwrap().to_str().unwrap(); info!(name, "deleting network namespace"); - umount2(&self.path, MntFlags::MNT_DETACH).map_err(NamespaceError::UnMount)?; - remove_file(&self.path).map_err(|error| NamespaceError::Delete { + match umount2(&self.path, MntFlags::MNT_DETACH) { + Err(Errno::EINVAL) => Ok(()), // not a mountpoint, namespace doesn't exist + rest => rest, + } + .map_err(NamespaceError::UnMount)?; + remove_file_if_exists(&self.path).map_err(|error| NamespaceError::Delete { error, path: self.path, })?; - remove_file(&self.nsd_path).map_err(|error| NamespaceError::Delete { + remove_file_if_exists(&self.nsd_path).map_err(|error| NamespaceError::Delete { error, path: self.nsd_path, })?; Ok(()) } + + /// Move a device into this namespace + pub fn move_device(&self, device: &DeviceName) -> Result<(), LinkError> { + move_link_into(device.as_ref(), self.handle()) + } + + /// Get the namespace handle + pub fn handle(&self) -> &NamespaceHandle { + &self.handle + } } #[derive(Debug, Error)] @@ -171,10 +216,16 @@ pub enum NamespaceError { Mount(Errno), #[error("Failed to unmount netns handle: {0:?}")] UnMount(Errno), - #[error("Failed to setup loopback inside namespace: {0:#}")] - Link(#[from] LinkError), #[error("Failed to scan {} for namespaces: {error:#}", path.display())] Scan { path: PathBuf, error: IoError }, + #[error(transparent)] + Handle(#[from] NamespaceHandleError), + #[error(transparent)] + Enter(#[from] NamespaceEnterError), + #[error(transparent)] + Link(#[from] LinkError), + #[error("Failed to setup sysctl for namespace: {0:#}")] + Sysctl(#[from] CtlError), } impl NamespaceError { @@ -182,3 +233,27 @@ impl NamespaceError { NamespaceError::Create { path, error } } } + +#[derive(Debug, Error)] +#[error("Error while entering namespace {}: {0:#}", namespace.display())] +pub struct NamespaceEnterError { + namespace: PathBuf, + error: Errno, +} + +/// `remove_file`, but ignore "file not found" errors +fn remove_file_if_exists>(path: P) -> std::io::Result<()> { + match remove_file(path) { + Err(err) if err.kind() == ErrorKind::NotFound => Ok(()), + rest => rest, + } +} + +/// `remove_file`, but ignore errors if the file doesn't exist or is a mount point +fn remove_non_mount>(path: P) -> std::io::Result<()> { + match remove_file(path) { + Err(err) if err.kind() == ErrorKind::NotFound => Ok(()), + Err(err) if err.kind() == ErrorKind::ResourceBusy => Ok(()), + rest => rest, + } +} diff --git a/src/namespace/raw.rs b/src/namespace/raw.rs index 2ece09f..5cffe5d 100644 --- a/src/namespace/raw.rs +++ b/src/namespace/raw.rs @@ -4,7 +4,6 @@ use nix::sys::signal::Signal; use nix::sys::wait::{waitpid, WaitStatus}; use std::path::PathBuf; use thiserror::Error; -use tracing::error; #[derive(Debug, Error)] pub enum NamespaceSetupError { diff --git a/src/namespace/sysctl.rs b/src/namespace/sysctl.rs new file mode 100644 index 0000000..d2f6b6b --- /dev/null +++ b/src/namespace/sysctl.rs @@ -0,0 +1,90 @@ +use std::fmt::Display; +use std::fs::{File, OpenOptions}; +use std::io::Write; +use std::io::{Error as IoError, ErrorKind, Read}; +use std::path::{Path, PathBuf}; +use std::str::FromStr; +use thiserror::Error; + +#[derive(Debug)] +pub struct NamespaceCtl { + use_temp_addr: u32, + temp_valid_lft: u32, + temp_preferred_lft: u32, +} + +const PATH_TEMP_ADDR: &str = "/proc/sys/net/ipv6/conf/default/use_tempaddr"; +const PATH_VALID_LFT: &str = "/proc/sys/net/ipv6/conf/default/temp_valid_lft"; +const PATH_PREFERRED_LFT: &str = "/proc/sys/net/ipv6/conf/default/temp_prefered_lft"; + +impl NamespaceCtl { + pub fn read() -> Result { + Ok(NamespaceCtl { + use_temp_addr: read_sysctl(PATH_TEMP_ADDR)?, + temp_valid_lft: read_sysctl(PATH_VALID_LFT)?, + temp_preferred_lft: read_sysctl(PATH_PREFERRED_LFT)?, + }) + } + + pub fn apply(&self) -> Result<(), CtlError> { + write_sysctl(PATH_TEMP_ADDR, self.use_temp_addr)?; + write_sysctl(PATH_VALID_LFT, self.temp_valid_lft)?; + write_sysctl(PATH_PREFERRED_LFT, self.temp_preferred_lft)?; + Ok(()) + } +} + +fn read_sysctl>(path: P) -> Result +where + ::Err: Display, +{ + let path = path.as_ref(); + let mut buff = [0; 16]; + let mut file = File::open(path).map_err(|error| CtlError::Read { + path: path.into(), + error, + })?; + let read = file.read(&mut buff).map_err(|error| CtlError::Read { + path: path.into(), + error, + })?; + let data = &buff[0..read]; + let str = str::from_utf8(data) + .map_err(|_| CtlError::Read { + path: path.into(), + error: IoError::new(ErrorKind::InvalidData, "stream did not contain valid UTF-8"), + })? + .trim(); + T::from_str(str).map_err(|error| CtlError::Parse { + path: path.into(), + error: error.to_string(), + }) +} + +fn write_sysctl>(path: P, value: T) -> Result<(), CtlError> { + let path = path.as_ref(); + + let mut file = OpenOptions::new() + .create(false) + .truncate(true) + .write(true) + .open(path) + .map_err(|error| CtlError::Write { + path: path.into(), + error, + })?; + writeln!(&mut file, "{value}").map_err(|error| CtlError::Write { + path: path.into(), + error, + }) +} + +#[derive(Debug, Error)] +pub enum CtlError { + #[error("failed to read sysctl option {}: {error:#}", path.display())] + Read { path: PathBuf, error: IoError }, + #[error("failed to read sysctl option {}: {error}", path.display())] + Parse { path: PathBuf, error: String }, + #[error("failed to write sysctl option {}: {error:#}", path.display())] + Write { path: PathBuf, error: IoError }, +} diff --git a/src/proxy/mod.rs b/src/proxy/mod.rs index b7de7ba..ef353fa 100644 --- a/src/proxy/mod.rs +++ b/src/proxy/mod.rs @@ -3,7 +3,12 @@ mod tcp; use crate::config::{ForwardConfig, ForwardSource, ForwardTarget, NamespaceName}; use crate::proxy::tcp::Proxy; use futures::future::AbortHandle; +use landlock::{ + ABI, Access, AccessFs, AccessNet, NetPort, Ruleset, RulesetAttr, RulesetCreatedAttr, + RulesetError, RulesetStatus, +}; use main_error::MainResult; +use nix::errno::Errno; use nix::sched::{CloneFlags, setns}; use nix::sys::signal::{SIGINT, kill}; use nix::unistd::{Gid, Pid, Uid, setgid, setuid}; @@ -13,8 +18,6 @@ use std::net::SocketAddr; use std::path::{Path, PathBuf}; use std::process::{Child, Command}; use std::thread::spawn; -use landlock::{Access, AccessFs, AccessNet, NetPort, Ruleset, RulesetAttr, RulesetCreatedAttr, RulesetError, RulesetStatus, ABI}; -use nix::errno::Errno; use thiserror::Error; use tokio::runtime::Builder; use tokio::signal::ctrl_c; @@ -215,4 +218,4 @@ fn landlock(port: u16) -> Result<(), RulesetError> { RulesetStatus::NotEnforced => error!("Not sandboxed! Please update your kernel."), } Ok(()) -} \ No newline at end of file +} diff --git a/src/proxy/tcp.rs b/src/proxy/tcp.rs index 0f163c1..159be8e 100644 --- a/src/proxy/tcp.rs +++ b/src/proxy/tcp.rs @@ -1,6 +1,6 @@ /// Loosely based on https://github.com/fooker/netns-proxy/blob/main/src/tcp.rs -use crate::config::{ForwardTarget, ForwardSource}; -use crate::proxy::{ProxyError}; +use crate::config::{ForwardSource, ForwardTarget}; +use crate::proxy::ProxyError; use futures::TryStreamExt; use futures::stream::{AbortRegistration, Abortable}; use std::fs::{remove_file, set_permissions}; @@ -56,28 +56,18 @@ impl Proxy { })?; debug!("Created TCP socket"); - Ok(Self { - socket, - }) + Ok(Self { socket }) } pub async fn run(self, target: ForwardTarget, abort: AbortRegistration) { match self.socket { - ProxyListener::Tcp(socket) => { - run_tcp(socket, target.addr, abort).await - } - ProxyListener::Unix(socket) => { - run_unix(socket, target.addr, abort).await - } + ProxyListener::Tcp(socket) => run_tcp(socket, target.addr, abort).await, + ProxyListener::Unix(socket) => run_unix(socket, target.addr, abort).await, } } } -async fn run_tcp( - socket: TcpListener, - target: SocketAddr, - abort: AbortRegistration, -) { +async fn run_tcp(socket: TcpListener, target: SocketAddr, abort: AbortRegistration) { let accepts = TcpListenerStream::new(socket).map_err(|error| ProxyError::Accept { error }); let mut accepts = pin!(Abortable::new(accepts, abort)); while let Some(client) = accepts.next().await { @@ -94,11 +84,7 @@ async fn run_tcp( } } -async fn run_unix( - socket: UnixListener, - target: SocketAddr, - abort: AbortRegistration, -) { +async fn run_unix(socket: UnixListener, target: SocketAddr, abort: AbortRegistration) { let accepts = UnixListenerStream::new(socket).map_err(|error| ProxyError::Accept { error }); let mut accepts = pin!(Abortable::new(accepts, abort)); while let Some(client) = accepts.next().await { diff --git a/src/up.rs b/src/up.rs index a7c65b4..4e33d78 100644 --- a/src/up.rs +++ b/src/up.rs @@ -1,9 +1,11 @@ use crate::config::{Config, NamespaceName}; +use crate::link::{LinkError, LinkManager}; use crate::namespace::NetNs; use main_error::MainResult; +use tracing::error; pub fn up(config: Config) -> MainResult { - let mut namespaces = NetNs::existing()? + let mut namespaces = NetNs::existing(false)? .map(NetNs::new) .collect::, _>>()?; @@ -15,7 +17,21 @@ pub fn up(config: Config) -> MainResult { for new in config.namespaces { if !has_namespace(&namespaces, &new.name) { - namespaces.push(NetNs::new(new.name)?); + namespaces.push(NetNs::new(new.name.clone())?); + } + let namespace = get_namespace(&namespaces, &new.name).expect("namespace is just created"); + for device in new.devices { + if let Err(error) = namespace.move_device(&device) { + error!(%error, "failed to move device into namespace"); + } + } + for route in new.routes { + namespace.handle().run_in(|| { + let manager = LinkManager::new()?; + let link = manager.get_link(&route.device)?; + manager.add_route(&link, route.destination)?; + Ok::<_, LinkError>(()) + })??; } } @@ -25,3 +41,7 @@ pub fn up(config: Config) -> MainResult { fn has_namespace(namespaces: &[NetNs], name: &NamespaceName) -> bool { namespaces.iter().any(|existing| existing.name() == name) } + +fn get_namespace<'a>(namespaces: &'a [NetNs], name: &NamespaceName) -> Option<&'a NetNs> { + namespaces.iter().find(|existing| existing.name() == name) +}