From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 578621FF16B for ; Tue, 29 Jul 2025 18:56:11 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id C2CBE18DA7; Tue, 29 Jul 2025 18:57:32 +0200 (CEST) From: Stefan Hanreich To: pbs-devel@lists.proxmox.com Date: Tue, 29 Jul 2025 18:56:53 +0200 Message-ID: <20250729165655.681368-9-s.hanreich@proxmox.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20250729165655.681368-1-s.hanreich@proxmox.com> References: <20250729165655.681368-1-s.hanreich@proxmox.com> MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.320 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods KAM_LOTSOFHASH 0.25 Emails with lots of hash-like gibberish KAM_SHORT 0.001 Use of a URL Shortener for very short URL RDNS_NONE 0.793 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_NONE 0.001 SPF: sender does not publish an SPF Record Subject: [pbs-devel] [PATCH proxmox-network-interface-pinning 1/1] initial commit X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox Backup Server development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pbs-devel-bounces@lists.proxmox.com Sender: "pbs-devel" Introduce proxmox-network-interface-pinning, which is a reimplementation of the tool from pve-manager. It should function identically to the PVE version, except for virtual function support. It also uses the ifupdown2 configuration parser from Rust, instead of the perl implementation, which might have some subtle differences in their handling of ifupdown2 configuration files. In order to support hosts that have both Proxmox VE and Proxmox Backup Server installed, this tool tries to detect the existence of the Proxmox VE tool and executes the Proxmox VE tool instead, if it is present on the host. Signed-off-by: Stefan Hanreich --- .cargo/config.toml | 5 + .gitignore | 8 + Cargo.lock | 1619 ++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 24 + Makefile | 86 +++ debian/changelog | 5 + debian/control | 36 + debian/copyright | 17 + debian/debcargo.toml | 8 + debian/rules | 31 + src/main.rs | 579 +++++++++++++++ 11 files changed, 2418 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Makefile create mode 100644 debian/changelog create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/debcargo.toml create mode 100755 debian/rules create mode 100644 src/main.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..3b5b6e4 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,5 @@ +[source] +[source.debian-packages] +directory = "/usr/share/cargo/registry" +[source.crates-io] +replace-with = "debian-packages" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..26ba170 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/target +/proxmox-network-interface-pinning-[0-9]*/ +*.build +*.buildinfo +*.changes +*.deb +*.dsc +*.tar* diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..57631ff --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1619 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "cc" +version = "1.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", +] + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "const_format" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "endian_trait" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c844962d33db56fe7024846eeb8db92c79ccb68d3752a0ee37c261ac79fd46" +dependencies = [ + "endian_trait_derive", +] + +[[package]] +name = "endian_trait_derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "libc", +] + +[[package]] +name = "fd-lock" +version = "3.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "cfg-if", + "rustix", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +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 = "Could not get crate checksum" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +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 = "Could not get crate checksum" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "h2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "winapi", +] + +[[package]] +name = "http" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "log" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "libc", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "openssl" +version = "0.10.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-sys" +version = "0.9.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proxmox-api-macro" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proxmox-async" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "anyhow", + "futures", + "pin-utils", + "proxmox-io", + "proxmox-lang", + "tokio", +] + +[[package]] +name = "proxmox-config-digest" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "anyhow", + "hex", + "openssl", + "proxmox-schema", + "serde", + "serde_plain", +] + +[[package]] +name = "proxmox-http" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "anyhow", + "bytes", + "futures", + "http-body", + "http-body-util", + "hyper", + "sync_wrapper", +] + +[[package]] +name = "proxmox-http-error" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "anyhow", + "http", + "serde", +] + +[[package]] +name = "proxmox-io" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "endian_trait", + "tokio", +] + +[[package]] +name = "proxmox-lang" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "proxmox-log" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "anyhow", + "nix", + "proxmox-sys", + "proxmox-time", + "tokio", + "tracing", + "tracing-journald", + "tracing-log", + "tracing-subscriber", +] + +[[package]] +name = "proxmox-network-api" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "anyhow", + "const_format", + "libc", + "nix", + "proxmox-config-digest", + "proxmox-network-types", + "proxmox-product-config", + "proxmox-schema", + "proxmox-sys", + "regex", + "serde", + "serde_json", +] + +[[package]] +name = "proxmox-network-interface-pinning" +version = "0.1.0" +dependencies = [ + "anyhow", + "nix", + "proxmox-async", + "proxmox-log", + "proxmox-network-api", + "proxmox-network-types", + "proxmox-product-config", + "proxmox-router", + "proxmox-schema", + "serde", + "serde_json", + "walkdir", +] + +[[package]] +name = "proxmox-network-types" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "serde", + "serde_with", + "thiserror", +] + +[[package]] +name = "proxmox-product-config" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "anyhow", + "nix", + "proxmox-sys", +] + +[[package]] +name = "proxmox-router" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "anyhow", + "bytes", + "env_logger", + "futures", + "http", + "hyper", + "libc", + "nix", + "percent-encoding", + "proxmox-async", + "proxmox-http", + "proxmox-http-error", + "proxmox-schema", + "rustyline", + "serde", + "serde_json", + "serde_plain", + "unicode-width", +] + +[[package]] +name = "proxmox-schema" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "anyhow", + "const_format", + "proxmox-api-macro", + "regex", + "serde", + "serde_json", + "textwrap", +] + +[[package]] +name = "proxmox-sys" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "anyhow", + "libc", + "log", + "nix", + "proxmox-io", + "proxmox-lang", + "regex", + "serde", + "serde_json", +] + +[[package]] +name = "proxmox-time" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "anyhow", + "bitflags", + "js-sys", + "libc", + "nom", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", +] + +[[package]] +name = "rustyline" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "bitflags", + "cfg-if", + "fd-lock", + "home", + "libc", + "log", + "memchr", + "nix", + "radix_trie", + "unicode-segmentation", + "unicode-width", + "utf8parse", +] + +[[package]] +name = "ryu" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_plain" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95455e7e29fada2052e72170af226fbe368a4ca33dee847875325d9fdb133858" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_with" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "libc", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "time-macros" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tokio" +version = "1.43.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-journald" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "libc", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-linebreak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "hashbrown", + "regex", +] + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "vcpkg" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "zerocopy" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..017d2ad --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "proxmox-network-interface-pinning" +version = "0.1.0" +authors = ["Stefan Hanreich "] +edition = "2024" +license = "AGPL-3" +description = "Tool for pinning the name of network interfaces." + +exclude = ["debian"] + +[dependencies] +anyhow = "1.0.95" +nix = "0.29" +serde = "1.0.217" +serde_json = "1.0.139" +walkdir = "2.5.0" + +proxmox-async = "0.5.0" +proxmox-log = "1.0.0" +proxmox-network-api = { version = "1.0.0", features = [ "impl" ] } +proxmox-network-types = "0.1.0" +proxmox-product-config = "1" +proxmox-router = "3.2.2" +proxmox-schema = { version = "4.1.1", features = [ "api-macro" ] } diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a6bf0cc --- /dev/null +++ b/Makefile @@ -0,0 +1,86 @@ +include /usr/share/dpkg/default.mk + +PACKAGE=proxmox-network-interface-pinning +CRATENAME=proxmox-network-interface-pinning + +BUILDDIR ?= $(PACKAGE)-$(DEB_VERSION_UPSTREAM) +ORIG_SRC_TAR=$(PACKAGE)_$(DEB_VERSION_UPSTREAM).orig.tar.gz + +DEB=$(PACKAGE)_$(DEB_VERSION)_$(DEB_HOST_ARCH).deb +DBG_DEB=$(PACKAGE)-dbgsym_$(DEB_VERSION)_$(DEB_HOST_ARCH).deb +DSC=$(PACKAGE)_$(DEB_VERSION).dsc + +CARGO ?= cargo +ifeq ($(BUILD_MODE), release) +CARGO_BUILD_ARGS += --release +COMPILEDIR := target/release +else +COMPILEDIR := target/debug +endif + +PREFIX = /usr +LIBEXECDIR = $(PREFIX)/libexec +PROXMOX_LIBEXECDIR = $(LIBEXECDIR)/proxmox + +PROXMOX_NETWORK_INTERFACE_PINNING_BIN := $(addprefix $(COMPILEDIR)/,proxmox-network-interface-pinning) + +all: + +install: $(PROXMOX_NETWORK_INTERFACE_PINNING_BIN) + install -dm755 $(DESTDIR)$(PROXMOX_LIBEXECDIR) + install -m755 $(PROXMOX_NETWORK_INTERFACE_PINNING_BIN) $(DESTDIR)$(PROXMOX_LIBEXECDIR)/ + +$(PROXMOX_NETWORK_INTERFACE_PINNING_BIN): .do-cargo-build +.do-cargo-build: + $(CARGO) build $(CARGO_BUILD_ARGS) + touch .do-cargo-build + + +.PHONY: cargo-build +cargo-build: .do-cargo-build + +$(BUILDDIR): + rm -rf $@ $@.tmp + mkdir $@.tmp + cp -a debian/ src/ Makefile Cargo.toml $@.tmp + mv $@.tmp $@ + + +$(ORIG_SRC_TAR): $(BUILDDIR) + tar czf $(ORIG_SRC_TAR) --exclude="$(BUILDDIR)/debian" $(BUILDDIR) + +.PHONY: deb +deb: $(DEB) +$(DEB) $(DBG_DEB) &: $(BUILDDIR) + cd $(BUILDDIR); dpkg-buildpackage -b -uc -us + lintian $(DEB) + @echo $(DEB) + +.PHONY: dsc +dsc: + rm -rf $(DSC) $(BUILDDIR) + $(MAKE) $(DSC) + lintian $(DSC) + +$(DSC): $(BUILDDIR) $(ORIG_SRC_TAR) + cd $(BUILDDIR); dpkg-buildpackage -S -us -uc -d + +sbuild: $(DSC) + sbuild $(DSC) + +.PHONY: upload +upload: UPLOAD_DIST ?= $(DEB_DISTRIBUTION) +upload: $(DEB) $(DBG_DEB) + tar cf - $(DEB) $(DBG_DEB) |ssh -X repoman@repo.proxmox.com -- upload --product pbs --dist $(UPLOAD_DIST) --arch $(DEB_HOST_ARCH) + +.PHONY: clean distclean +distclean: clean +clean: + $(CARGO) clean + rm -rf $(PACKAGE)-[0-9]*/ build/ + rm -f *.deb *.changes *.dsc *.tar.* *.buildinfo *.build .do-cargo-build + +.PHONY: dinstall +dinstall: deb + dpkg -i $(DEB) + diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..56422e9 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +proxmox-network-interface-pinning (0.1.0) trixie; urgency=medium + + * Initial release. + + -- Proxmox Support Team Tue, 29 Jul 2025 14:39:57 +0200 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..5d3edf2 --- /dev/null +++ b/debian/control @@ -0,0 +1,36 @@ +Source: proxmox-network-interface-pinning +Section: admin +Priority: optional +Build-Depends: debhelper-compat (= 13), + dh-sequence-cargo, + cargo:native, + rustc:native, + libstd-rust-dev, + librust-anyhow-1+default-dev (>= 1.0.95-~~), + librust-nix-0.29+default-dev, + librust-proxmox-async-0.5+default-dev, + librust-proxmox-log-1+default-dev, + librust-proxmox-network-api-1+default-dev, + librust-proxmox-network-api-1+impl-dev, + librust-proxmox-network-types-0.1+default-dev, + librust-proxmox-product-config+default-dev, + librust-proxmox-router-3+default-dev (>= 3.2.2-~~), + librust-proxmox-schema-4+api-macro-dev (>= 4.1.1-~~), + librust-proxmox-schema-4+default-dev (>= 4.1.1-~~), + librust-serde-1+default-dev (>= 1.0.217-~~), + librust-serde-json-1+default-dev (>= 1.0.139-~~), + librust-walkdir-2+default-dev (>= 2.5.0-~~) +Maintainer: Proxmox Support Team +Standards-Version: 4.7.0 +Vcs-Git: +Vcs-Browser: +Rules-Requires-Root: no + +Package: proxmox-network-interface-pinning +Architecture: any +Multi-Arch: allowed +Depends: ${misc:Depends}, ${shlibs:Depends}, +Description: Pinning the name of network interfaces + This package contains the following binaries built from the Rust crate + "proxmox-network-interface-pinning": + - proxmox-network-interface-pinning diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..61c573d --- /dev/null +++ b/debian/copyright @@ -0,0 +1,17 @@ +Copyright (C) 2025 Proxmox Server Solutions GmbH + +This software is written by Proxmox Server Solutions GmbH + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + diff --git a/debian/debcargo.toml b/debian/debcargo.toml new file mode 100644 index 0000000..703440f --- /dev/null +++ b/debian/debcargo.toml @@ -0,0 +1,8 @@ +overlay = "." +crate_src_path = ".." +maintainer = "Proxmox Support Team " + +[source] +# TODO: update once public +vcs_git = "" +vcs_browser = "" diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..e157e13 --- /dev/null +++ b/debian/rules @@ -0,0 +1,31 @@ +#!/usr/bin/make -f +# See debhelper(7) (uncomment to enable) +# output every command that modifies files on the build system. +DH_VERBOSE = 1 + +include /usr/share/dpkg/pkg-info.mk +include /usr/share/rustc/architecture.mk + +export BUILD_MODE=release + +CARGO=/usr/share/cargo/bin/cargo + +export CFLAGS CXXFLAGS CPPFLAGS LDFLAGS +export DEB_HOST_RUST_TYPE DEB_HOST_GNU_TYPE +export CARGO_HOME = $(CURDIR)/debian/cargo_home + +export DEB_CARGO_CRATE=proxmox-network-interface-pinning_$(DEB_VERSION_UPSTREAM) +export DEB_CARGO_PACKAGE=proxmox-network-interface-pinning + +%: + dh $@ + +override_dh_auto_configure: + @perl -ne 'if (/^version\s*=\s*"(\d+(?:\.\d+)+)"/) { my $$v_cargo = $$1; my $$v_deb = "$(DEB_VERSION_UPSTREAM)"; \ + die "ERROR: d/changelog <-> Cargo.toml version mismatch: $$v_cargo != $$v_deb\n" if $$v_cargo ne $$v_deb; exit(0); }' Cargo.toml + $(CARGO) prepare-debian $(CURDIR)/debian/cargo_registry --link-from-system + dh_auto_configure + +override_dh_missing: + dh_missing --fail-missing + diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..33d9ce7 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,579 @@ +use std::collections::{HashMap, HashSet}; +use std::fmt::{Display, Formatter}; +use std::ops::{Deref, DerefMut}; +use std::os::unix::process::CommandExt; +use std::process::Command; +use std::process::Stdio; + +use anyhow::{anyhow, bail, format_err, Error}; +use nix::unistd::{Uid, User}; +use proxmox_network_api::IpLink; +use serde::de::{value::MapDeserializer, Deserialize}; + +use proxmox_log::{debug, LevelFilter}; +use proxmox_network_types::mac_address::MacAddress; +use proxmox_router::cli::{ + run_cli_command, CliCommand, CliCommandMap, CliEnvironment, Confirmation, +}; +use proxmox_schema::api; +use walkdir::WalkDir; + +const SYSTEMD_LINK_FILE_PATH: &str = "/usr/local/lib/systemd/network"; + +#[derive(Debug, Clone, serde::Deserialize, Default)] +pub struct InterfaceMapping { + mapping: HashMap, +} + +impl Deref for InterfaceMapping { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.mapping + } +} + +impl DerefMut for InterfaceMapping { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.mapping + } +} + +impl InterfaceMapping { + pub fn write(&self, ip_links: HashMap) -> Result<(), Error> { + if self.mapping.is_empty() { + return Ok(()); + } + + println!("Generating link files"); + + std::fs::create_dir_all(SYSTEMD_LINK_FILE_PATH)?; + + let mut sorted_links: Vec<&IpLink> = ip_links.values().collect(); + sorted_links.sort_by_key(|a| a.index()); + + for ip_link in sorted_links { + if let Some(new_name) = self.mapping.get(ip_link.name()) { + let link_file = LinkFile::new_ether(ip_link.permanent_mac(), new_name.to_string()); + + std::fs::write( + format!("{}/{}", SYSTEMD_LINK_FILE_PATH, link_file.file_name()), + link_file.to_string().as_bytes(), + )?; + } + } + + println!("Successfully generated .link files in '/usr/local/lib/systemd/network/'"); + Ok(()) + } +} + +struct PinnedInterfaces { + mapping: HashMap, +} + +impl PinnedInterfaces { + pub fn get(&self, mac_address: &MacAddress) -> Option<&String> { + self.mapping.get(mac_address) + } + + pub fn values(&self) -> impl Iterator { + self.mapping.values() + } + + pub fn contains_key(&self, mac_address: &MacAddress) -> bool { + self.mapping.contains_key(mac_address) + } +} + +impl FromIterator for PinnedInterfaces { + fn from_iter>(iter: T) -> Self { + Self { + mapping: iter + .into_iter() + .map(|link_file| { + ( + link_file.match_section.mac_address, + link_file.link_section.name, + ) + }) + .collect(), + } + } +} + +#[derive(Debug, Clone, serde::Deserialize)] +struct MatchSection { + #[serde(rename = "MACAddress")] + mac_address: MacAddress, + #[serde(rename = "Type")] + ty: String, +} + +impl Display for MatchSection { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + writeln!(f, "MACAddress={}", self.mac_address)?; + writeln!(f, "Type={}", self.ty)?; + + Ok(()) + } +} + +#[derive(Debug, Clone, serde::Deserialize)] +struct LinkSection { + #[serde(rename = "Name")] + name: String, +} + +impl Display for LinkSection { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + writeln!(f, "Name={}", self.name) + } +} + +#[derive(Debug, Clone, serde::Deserialize)] +struct LinkFile { + match_section: MatchSection, + link_section: LinkSection, +} + +impl LinkFile { + pub fn new_ether(mac_address: MacAddress, name: String) -> Self { + Self { + match_section: MatchSection { + mac_address, + ty: "ether".to_string(), + }, + link_section: LinkSection { name }, + } + } + + pub fn file_name(&self) -> String { + format!("50-pve-{}.link", self.link_section.name) + } +} + +#[derive(Debug, Clone, serde::Deserialize, Hash, Eq, PartialEq)] +pub enum LinkFileSection { + Match, + Link, +} + +impl std::str::FromStr for LinkFileSection { + type Err = Error; + + fn from_str(value: &str) -> Result { + Ok(match value { + "[Match]" => LinkFileSection::Match, + "[Link]" => LinkFileSection::Link, + _ => bail!("invalid section type: {value}"), + }) + } +} + +impl Display for LinkFileSection { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + writeln!( + f, + "{}", + match self { + LinkFileSection::Link => "[Link]", + LinkFileSection::Match => "[Match]", + } + ) + } +} + +#[derive(Debug)] +pub struct SerdeStringError(String); + +impl std::error::Error for SerdeStringError {} + +impl Display for SerdeStringError { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + f.write_str(&self.0) + } +} + +impl serde::de::Error for SerdeStringError { + fn custom(msg: T) -> Self { + Self(msg.to_string()) + } +} + +impl std::str::FromStr for LinkFile { + type Err = Error; + + fn from_str(value: &str) -> Result { + let mut sections: HashMap> = HashMap::new(); + let mut current_section = None; + + for line in value.lines() { + let line = line.trim(); + + if line.is_empty() || line.starts_with(['#', ';']) { + continue; + } + + if line.starts_with('[') { + current_section = Some(line.parse()?); + sections.insert(current_section.as_ref().cloned().unwrap(), HashMap::new()); + } else { + if current_section.is_none() { + bail!("config line without section") + } + + let Some((key, value)) = line.split_once("=") else { + bail!("could not find key, value pair in link config"); + }; + + if key.is_empty() || value.is_empty() { + bail!("could not find key, value pair in link config"); + } + + sections + .get_mut(current_section.as_ref().expect("current section is some")) + .expect("section has been inserted") + .insert(key.to_string(), value.to_string()); + } + } + + let link_data = sections + .remove(&LinkFileSection::Link) + .ok_or_else(|| anyhow!("no link section in link file"))?; + + let link_section = LinkSection::deserialize(MapDeserializer::< + std::collections::hash_map::IntoIter, + SerdeStringError, + >::new(link_data.into_iter()))?; + + let match_data = sections + .remove(&LinkFileSection::Match) + .ok_or_else(|| anyhow!("no match section in link file"))?; + + let match_section = MatchSection::deserialize(MapDeserializer::< + std::collections::hash_map::IntoIter, + SerdeStringError, + >::new(match_data.into_iter()))?; + + Ok(Self { + match_section, + link_section, + }) + } +} + +impl Display for LinkFile { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + write!(f, "{}", LinkFileSection::Match)?; + writeln!(f, "{}", self.match_section)?; + + write!(f, "{}", LinkFileSection::Link)?; + writeln!(f, "{}", self.link_section)?; + + Ok(()) + } +} + +pub struct PinningTool { + ip_links: HashMap, + pinned_interfaces: PinnedInterfaces, + existing_names: HashSet, +} + +impl PinningTool { + fn read_link_files() -> Result, Error> { + debug!("reading link data"); + + let link_files = WalkDir::new(SYSTEMD_LINK_FILE_PATH) + .max_depth(1) + .into_iter() + .filter_map(|entry| entry.ok()) + .filter(|entry| { + if let Some(file_name) = entry.path().file_name() { + let name = file_name.to_str().unwrap(); + return name.starts_with("50-pve-") && name.ends_with(".link"); + } + + false + }); + + let mut ip_links = Vec::new(); + + for link_file in link_files { + debug!("reading file {}", link_file.path().display()); + + let file_content = std::fs::read(link_file.path())?; + let ip_link = std::str::from_utf8(&file_content)?; + + ip_links.push(ip_link.parse()?); + } + + Ok(ip_links) + } + + pub fn new() -> Result { + let ip_links = proxmox_network_api::get_network_interfaces()?; + let pinned_interfaces: PinnedInterfaces = Self::read_link_files()?.into_iter().collect(); + + let mut existing_names = HashSet::new(); + + for name in ip_links.keys() { + existing_names.insert(name.clone()); + } + + for pinned_interface in pinned_interfaces.values() { + existing_names.insert(pinned_interface.clone()); + } + + Ok(Self { + ip_links, + pinned_interfaces, + existing_names, + }) + } + + pub fn pin_interface( + self, + interface_name: &str, + target_name: Option, + prefix: Option, + ) -> Result { + let ip_link = self + .ip_links + .get(interface_name) + .ok_or_else(|| anyhow!("cannot find interface with name {interface_name}"))?; + + if self + .pinned_interfaces + .contains_key(&ip_link.permanent_mac()) + { + bail!("pin already exists for interface {interface_name}"); + } + + let mut mapping = InterfaceMapping::default(); + + if let Some(target_name) = target_name { + if self.existing_names.contains(&target_name) { + bail!("target name already exists"); + } + + let mut current_altnames = Vec::new(); + + mapping.insert(ip_link.name().to_string(), target_name.to_string()); + + for altname in ip_link.altnames() { + current_altnames.push(altname.as_str()); + mapping.insert(altname.to_string(), target_name.to_string()); + } + + println!( + "Name for {} ({}) will change to {target_name}", + ip_link.name(), + current_altnames.join(", ") + ); + } else if let Some(prefix) = prefix { + let mut idx = 0; + + loop { + let target_name = format!("{prefix}{idx}"); + + if !self.existing_names.contains(&target_name) { + let mut current_altnames = Vec::new(); + + mapping.insert(ip_link.name().to_string(), target_name.to_string()); + + for altname in ip_link.altnames() { + current_altnames.push(altname.as_str()); + mapping.insert(altname.to_string(), target_name.to_string()); + } + + println!( + "Name for {} ({}) will change to {target_name}", + ip_link.name(), + current_altnames.join(", ") + ); + + break; + } + + idx += 1; + } + } else { + return Err(anyhow!( + "neither target-name nor prefix provided for interface" + )); + } + + mapping.write(self.ip_links)?; + Ok(mapping) + } + + pub fn pin_all(mut self, prefix: &str) -> Result { + let mut mapping = InterfaceMapping::default(); + + let mut idx = 0; + + for (name, ip_link) in &self.ip_links { + if ip_link.is_physical() + && self + .pinned_interfaces + .get(&ip_link.permanent_mac()) + .is_none() + { + loop { + let target_name = format!("{prefix}{idx}"); + + if !self.existing_names.contains(&target_name) { + let mut current_altnames = Vec::new(); + + mapping.insert(name.to_string(), target_name.to_string()); + + for altname in ip_link.altnames() { + current_altnames.push(altname.as_str()); + mapping.insert(altname.to_string(), target_name.to_string()); + } + + println!( + "Name for {} ({}) will change to {target_name}", + ip_link.name(), + current_altnames.join(", ") + ); + + self.existing_names.insert(target_name); + + break; + } + + idx += 1; + } + } + } + + mapping.write(self.ip_links)?; + Ok(mapping) + } +} + +#[api( + input: { + properties: { + interface: { + type: String, + optional: true, + description: "Only pin a specific interface.", + }, + prefix: { + type: String, + optional: true, + description: "Use a specific prefix for automatically choosing the pinned name.", + }, + "target-name": { + type: String, + optional: true, + description: "Pin the interface to a specific name.", + }, + } + } +)] +/// Generates link files to pin the names of network interfaces (based on MAC address). +fn generate_mapping( + interface: Option, + prefix: Option, + target_name: Option, +) -> Result<(), Error> { + let pinning_tool = PinningTool::new()?; + + let target = if let Some(ref interface) = interface { + interface.as_str() + } else { + "all interfaces" + }; + + let confirmation = Confirmation::query_with_default( + format!("This will generate name pinning configuration for {target} - continue (y/N)?") + .as_str(), + Confirmation::No, + )?; + + if confirmation.is_no() { + return Ok(()); + } + + let mapping = if let Some(interface) = interface { + pinning_tool.pin_interface(&interface, target_name, prefix)? + } else { + let prefix = prefix.unwrap_or("nic".to_string()); + pinning_tool.pin_all(&prefix)? + }; + + if mapping.is_empty() { + println!("Nothing to do. Aborting."); + return Ok(()); + } + + println!("Updating /etc/network/interfaces.new"); + + proxmox_network_api::lock_config()?; + + let (mut config, _) = proxmox_network_api::config()?; + config.rename_interfaces(mapping.deref())?; + + proxmox_network_api::save_config(&config)?; + + println!("Successfully updated network configuration files."); + + println!("\nPlease reboot to apply the changes to your configuration\n"); + + Ok(()) +} + +// TODO: make this load the unprivileged user dynamically, depending on product, default to backup +// for now since we only ship the tool with PBS currently +pub fn unprivileged_user() -> Result { + if cfg!(test) { + Ok(User::from_uid(Uid::current())?.expect("current user does not exist")) + } else { + User::from_name("backup")?.ok_or_else(|| format_err!("Unable to lookup 'backup' user.")) + } +} + +pub fn privileged_user() -> Result { + if cfg!(test) { + Ok(User::from_uid(Uid::current())?.expect("current user does not exist")) + } else { + User::from_name("root")?.ok_or_else(|| format_err!("Unable to lookup superuser.")) + } +} + +fn main() -> Result<(), Error> { + // This is run on a PVE host, so we use the PVE-specific pinning tool instead with the + // parameters supplied. + if std::fs::exists("/usr/bin/proxmox-network-interface-pinning")? { + let args = std::env::args().skip(1); + + return Err(Command::new("/usr/bin/proxmox-network-interface-pinning") + .args(args) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .exec() + .into()); + } + + proxmox_log::Logger::from_env("PBS_LOG", LevelFilter::INFO) + .stderr() + .init() + .expect("failed to initiate logger"); + + // required for locking the network config + proxmox_product_config::init(unprivileged_user()?, privileged_user()?); + + let generate_command = CliCommand::new(&API_METHOD_GENERATE_MAPPING); + let commands = CliCommandMap::new().insert("generate", generate_command); + + let rpcenv = CliEnvironment::new(); + + run_cli_command(commands, rpcenv, None); + + Ok(()) +} -- 2.47.2 _______________________________________________ pbs-devel mailing list pbs-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel