patches and low-level development discussion
 help / color / mirror / code / Atom feed
From: Demi Marie Obenour <demiobenour@gmail.com>
To: Yureka Lilian <yureka@cyberchaos.dev>, devel@spectrum-os.org
Subject: Re: [PATCH v1 RFC 3/4] tools: add spectrum-router
Date: Mon, 24 Nov 2025 14:05:09 -0500	[thread overview]
Message-ID: <ed5badb2-36dc-4069-9f28-8dc73d86d376@gmail.com> (raw)
In-Reply-To: <20251124163531.91029-4-yureka@cyberchaos.dev>


[-- Attachment #1.1.1: Type: text/plain, Size: 39619 bytes --]

On 11/24/25 11:35, Yureka Lilian wrote:
> The tokio-vhost & vhost-device-net crates which we also wrote and depend
> on are left external in the outlook of becoming a rust-vmm project soon.
> 
> Signed-off-by: Yureka Lilian <yureka@cyberchaos.dev>
> ---
>  pkgs/default.nix         |   2 +
>  tools/router/Cargo.lock  | 785 +++++++++++++++++++++++++++++++++++++++
>  tools/router/Cargo.toml  |  18 +
>  tools/router/default.nix |  18 +
>  tools/router/src/main.rs | 235 ++++++++++++
>  5 files changed, 1058 insertions(+)
>  create mode 100644 tools/router/Cargo.lock
>  create mode 100644 tools/router/Cargo.toml
>  create mode 100644 tools/router/default.nix
>  create mode 100644 tools/router/src/main.rs
> 
> diff --git a/pkgs/default.nix b/pkgs/default.nix
> index cc60228..7c1c9c3 100644
> --- a/pkgs/default.nix
> +++ b/pkgs/default.nix
> @@ -52,6 +52,8 @@ let
>      xdg-desktop-portal-spectrum-host =
>        self.callSpectrumPackage ../tools/xdg-desktop-portal-spectrum-host {};
>  
> +    spectrum-router = self.callSpectrumPackage ../tools/router {};
> +
>      # Packages from the overlay, so it's possible to build them from
>      # the CLI easily.
>      inherit (pkgs) cloud-hypervisor dbus;
> diff --git a/tools/router/Cargo.lock b/tools/router/Cargo.lock
> new file mode 100644
> index 0000000..04179af
> --- /dev/null
> +++ b/tools/router/Cargo.lock
> @@ -0,0 +1,785 @@
> +# This file is automatically @generated by Cargo.
> +# 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"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
> +dependencies = [
> + "anstyle",
> + "anstyle-parse",
> + "anstyle-query",
> + "anstyle-wincon",
> + "colorchoice",
> + "is_terminal_polyfill",
> + "utf8parse",
> +]
> +
> +[[package]]
> +name = "anstyle"
> +version = "1.0.13"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
> +
> +[[package]]
> +name = "anstyle-parse"
> +version = "0.2.7"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
> +dependencies = [
> + "utf8parse",
> +]
> +
> +[[package]]
> +name = "anstyle-query"
> +version = "1.1.5"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
> +dependencies = [
> + "windows-sys 0.61.2",
> +]
> +
> +[[package]]
> +name = "anstyle-wincon"
> +version = "3.0.11"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
> +dependencies = [
> + "anstyle",
> + "once_cell_polyfill",
> + "windows-sys 0.61.2",
> +]
> +
> +[[package]]
> +name = "anyhow"
> +version = "1.0.100"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
> +
> +[[package]]
> +name = "async-stream"
> +version = "0.3.6"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
> +dependencies = [
> + "async-stream-impl",
> + "futures-core",
> + "pin-project-lite",
> +]
> +
> +[[package]]
> +name = "async-stream-impl"
> +version = "0.3.6"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
> +dependencies = [
> + "proc-macro2",
> + "quote",
> + "syn",
> +]
> +
> +[[package]]
> +name = "bitflags"
> +version = "1.3.2"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
> +
> +[[package]]
> +name = "bitvec"
> +version = "1.0.1"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
> +dependencies = [
> + "funty",
> + "radium",
> + "tap",
> + "wyz",
> +]
> +
> +[[package]]
> +name = "bytes"
> +version = "1.11.0"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
> +
> +[[package]]
> +name = "clap"
> +version = "4.5.53"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
> +dependencies = [
> + "clap_builder",
> + "clap_derive",
> +]
> +
> +[[package]]
> +name = "clap_builder"
> +version = "4.5.53"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
> +dependencies = [
> + "anstream",
> + "anstyle",
> + "clap_lex",
> + "strsim",
> +]
> +
> +[[package]]
> +name = "clap_derive"
> +version = "4.5.49"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
> +dependencies = [
> + "heck",
> + "proc-macro2",
> + "quote",
> + "syn",
> +]
> +
> +[[package]]
> +name = "clap_lex"
> +version = "0.7.6"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
> +
> +[[package]]
> +name = "colorchoice"
> +version = "1.0.4"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
> +
> +[[package]]
> +name = "env_filter"
> +version = "0.1.4"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2"
> +dependencies = [
> + "log",
> + "regex",
> +]
> +
> +[[package]]
> +name = "env_logger"
> +version = "0.11.8"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
> +dependencies = [
> + "anstream",
> + "anstyle",
> + "env_filter",
> + "jiff",
> + "log",
> +]
> +
> +[[package]]
> +name = "fastrand"
> +version = "2.3.0"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
> +
> +[[package]]
> +name = "funty"
> +version = "2.0.0"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
> +
> +[[package]]
> +name = "futures-core"
> +version = "0.3.31"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
> +
> +[[package]]
> +name = "futures-io"
> +version = "0.3.31"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
> +
> +[[package]]
> +name = "futures-lite"
> +version = "2.6.1"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
> +dependencies = [
> + "fastrand",
> + "futures-core",
> + "futures-io",
> + "parking",
> + "pin-project-lite",
> +]
> +
> +[[package]]
> +name = "futures-macro"
> +version = "0.3.31"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
> +dependencies = [
> + "proc-macro2",
> + "quote",
> + "syn",
> +]
> +
> +[[package]]
> +name = "futures-sink"
> +version = "0.3.31"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
> +
> +[[package]]
> +name = "futures-task"
> +version = "0.3.31"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
> +
> +[[package]]
> +name = "futures-util"
> +version = "0.3.31"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
> +dependencies = [
> + "futures-core",
> + "futures-macro",
> + "futures-sink",
> + "futures-task",
> + "pin-project-lite",
> + "pin-utils",
> + "slab",
> +]
> +
> +[[package]]
> +name = "heck"
> +version = "0.5.0"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
> +
> +[[package]]
> +name = "is_terminal_polyfill"
> +version = "1.70.2"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
> +
> +[[package]]
> +name = "jiff"
> +version = "0.2.16"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35"
> +dependencies = [
> + "jiff-static",
> + "log",
> + "portable-atomic",
> + "portable-atomic-util",
> + "serde_core",
> +]
> +
> +[[package]]
> +name = "jiff-static"
> +version = "0.2.16"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69"
> +dependencies = [
> + "proc-macro2",
> + "quote",
> + "syn",
> +]
> +
> +[[package]]
> +name = "libc"
> +version = "0.2.177"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
> +
> +[[package]]
> +name = "log"
> +version = "0.4.28"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
> +
> +[[package]]
> +name = "memchr"
> +version = "2.7.6"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
> +
> +[[package]]
> +name = "mio"
> +version = "1.1.0"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873"
> +dependencies = [
> + "libc",
> + "wasi",
> + "windows-sys 0.61.2",
> +]
> +
> +[[package]]
> +name = "once_cell_polyfill"
> +version = "1.70.2"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
> +
> +[[package]]
> +name = "parking"
> +version = "2.2.1"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
> +
> +[[package]]
> +name = "pin-project-lite"
> +version = "0.2.16"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
> +
> +[[package]]
> +name = "pin-utils"
> +version = "0.1.0"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
> +
> +[[package]]
> +name = "portable-atomic"
> +version = "1.11.1"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
> +
> +[[package]]
> +name = "portable-atomic-util"
> +version = "0.2.4"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
> +dependencies = [
> + "portable-atomic",
> +]
> +
> +[[package]]
> +name = "proc-macro2"
> +version = "1.0.103"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
> +dependencies = [
> + "unicode-ident",
> +]
> +
> +[[package]]
> +name = "quote"
> +version = "1.0.42"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
> +dependencies = [
> + "proc-macro2",
> +]
> +
> +[[package]]
> +name = "radium"
> +version = "0.7.0"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
> +
> +[[package]]
> +name = "regex"
> +version = "1.12.2"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
> +dependencies = [
> + "aho-corasick",
> + "memchr",
> + "regex-automata",
> + "regex-syntax",
> +]
> +
> +[[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 = "serde_core"
> +version = "1.0.228"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
> +dependencies = [
> + "serde_derive",
> +]
> +
> +[[package]]
> +name = "serde_derive"
> +version = "1.0.228"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
> +dependencies = [
> + "proc-macro2",
> + "quote",
> + "syn",
> +]
> +
> +[[package]]
> +name = "slab"
> +version = "0.4.11"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
> +
> +[[package]]
> +name = "socket2"
> +version = "0.6.1"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
> +dependencies = [
> + "libc",
> + "windows-sys 0.60.2",
> +]
> +
> +[[package]]
> +name = "spectrum-router"
> +version = "0.1.0"
> +dependencies = [
> + "anyhow",
> + "clap",
> + "env_logger",
> + "futures-util",
> + "log",
> + "tokio",
> + "tokio-stream",
> + "vhost-device-net",
> + "zerocopy",
> +]
> +
> +[[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.110"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
> +dependencies = [
> + "proc-macro2",
> + "quote",
> + "unicode-ident",
> +]
> +
> +[[package]]
> +name = "tap"
> +version = "1.0.1"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
> +
> +[[package]]
> +name = "thiserror"
> +version = "1.0.69"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
> +dependencies = [
> + "thiserror-impl",
> +]
> +
> +[[package]]
> +name = "thiserror-impl"
> +version = "1.0.69"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
> +dependencies = [
> + "proc-macro2",
> + "quote",
> + "syn",
> +]
> +
> +[[package]]
> +name = "tokio"
> +version = "1.48.0"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
> +dependencies = [
> + "bytes",
> + "libc",
> + "mio",
> + "pin-project-lite",
> + "socket2",
> + "tokio-macros",
> + "windows-sys 0.61.2",
> +]
> +
> +[[package]]
> +name = "tokio-eventfd"
> +version = "0.2.2"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "7e66bd133670ac39baa1aca5c3a86709f4595c08ca4464a1e1400b83d62c0639"
> +dependencies = [
> + "futures-lite",
> + "libc",
> + "tokio",
> +]
> +
> +[[package]]
> +name = "tokio-macros"
> +version = "2.6.0"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
> +dependencies = [
> + "proc-macro2",
> + "quote",
> + "syn",
> +]
> +
> +[[package]]
> +name = "tokio-stream"
> +version = "0.1.17"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
> +dependencies = [
> + "futures-core",
> + "pin-project-lite",
> + "tokio",
> +]
> +
> +[[package]]
> +name = "tokio-vhost"
> +version = "0.1.0"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "21d64b3e4d573da90b2bb040d69a9c2d754e8a3ab9d9ecf04a268748c99f1cd3"
> +dependencies = [
> + "async-stream",
> + "bitvec",
> + "futures-util",
> + "libc",
> + "log",
> + "tokio",
> + "tokio-eventfd",
> + "virtio-queue",
> + "vm-memory",
> + "zerocopy",
> +]
> +
> +[[package]]
> +name = "unicode-ident"
> +version = "1.0.22"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
> +
> +[[package]]
> +name = "utf8parse"
> +version = "0.2.2"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
> +
> +[[package]]
> +name = "vhost-device-net"
> +version = "0.1.0"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "ac05caccd6d484f672551a187f7110ff9d32edd6a39bb16bb04f53017b1e6fd0"
> +dependencies = [
> + "futures-util",
> + "log",
> + "tokio",
> + "tokio-vhost",
> + "vm-memory",
> +]
> +
> +[[package]]
> +name = "virtio-bindings"
> +version = "0.2.6"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "804f498a26d5a63be7bbb8bdcd3869c3f286c4c4a17108905276454da0caf8cb"
> +
> +[[package]]
> +name = "virtio-queue"
> +version = "0.16.0"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "fb0479158f863e59323771a1f684d843962f76960b86fecfec2bfa9c8f0f9180"
> +dependencies = [
> + "log",
> + "virtio-bindings",
> + "vm-memory",
> + "vmm-sys-util",
> +]
> +
> +[[package]]
> +name = "vm-memory"
> +version = "0.16.2"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "1fd5e56d48353c5f54ef50bd158a0452fc82f5383da840f7b8efc31695dd3b9d"
> +dependencies = [
> + "libc",
> + "thiserror",
> + "winapi",
> +]
> +
> +[[package]]
> +name = "vmm-sys-util"
> +version = "0.14.0"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "d21f366bf22bfba3e868349978766a965cbe628c323d58e026be80b8357ab789"
> +dependencies = [
> + "bitflags",
> + "libc",
> +]
> +
> +[[package]]
> +name = "wasi"
> +version = "0.11.1+wasi-snapshot-preview1"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
> +
> +[[package]]
> +name = "winapi"
> +version = "0.3.9"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
> +dependencies = [
> + "winapi-i686-pc-windows-gnu",
> + "winapi-x86_64-pc-windows-gnu",
> +]
> +
> +[[package]]
> +name = "winapi-i686-pc-windows-gnu"
> +version = "0.4.0"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
> +
> +[[package]]
> +name = "winapi-x86_64-pc-windows-gnu"
> +version = "0.4.0"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
> +
> +[[package]]
> +name = "windows-link"
> +version = "0.2.1"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
> +
> +[[package]]
> +name = "windows-sys"
> +version = "0.60.2"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
> +dependencies = [
> + "windows-targets",
> +]
> +
> +[[package]]
> +name = "windows-sys"
> +version = "0.61.2"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
> +dependencies = [
> + "windows-link",
> +]
> +
> +[[package]]
> +name = "windows-targets"
> +version = "0.53.5"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
> +dependencies = [
> + "windows-link",
> + "windows_aarch64_gnullvm",
> + "windows_aarch64_msvc",
> + "windows_i686_gnu",
> + "windows_i686_gnullvm",
> + "windows_i686_msvc",
> + "windows_x86_64_gnu",
> + "windows_x86_64_gnullvm",
> + "windows_x86_64_msvc",
> +]
> +
> +[[package]]
> +name = "windows_aarch64_gnullvm"
> +version = "0.53.1"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
> +
> +[[package]]
> +name = "windows_aarch64_msvc"
> +version = "0.53.1"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
> +
> +[[package]]
> +name = "windows_i686_gnu"
> +version = "0.53.1"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
> +
> +[[package]]
> +name = "windows_i686_gnullvm"
> +version = "0.53.1"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
> +
> +[[package]]
> +name = "windows_i686_msvc"
> +version = "0.53.1"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
> +
> +[[package]]
> +name = "windows_x86_64_gnu"
> +version = "0.53.1"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
> +
> +[[package]]
> +name = "windows_x86_64_gnullvm"
> +version = "0.53.1"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
> +
> +[[package]]
> +name = "windows_x86_64_msvc"
> +version = "0.53.1"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
> +
> +[[package]]
> +name = "wyz"
> +version = "0.5.1"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
> +dependencies = [
> + "tap",
> +]
> +
> +[[package]]
> +name = "zerocopy"
> +version = "0.8.28"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "43fa6694ed34d6e57407afbccdeecfa268c470a7d2a5b0cf49ce9fcc345afb90"
> +dependencies = [
> + "zerocopy-derive",
> +]
> +
> +[[package]]
> +name = "zerocopy-derive"
> +version = "0.8.28"
> +source = "registry+https://github.com/rust-lang/crates.io-index"
> +checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26"
> +dependencies = [
> + "proc-macro2",
> + "quote",
> + "syn",
> +]
> diff --git a/tools/router/Cargo.toml b/tools/router/Cargo.toml
> new file mode 100644
> index 0000000..f9d8138
> --- /dev/null
> +++ b/tools/router/Cargo.toml
> @@ -0,0 +1,18 @@
> +# SPDX-License-Identifier: CC0-1.0
> +# SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev>
> +
> +[package]
> +name = "spectrum-router"
> +version = "0.1.0"
> +edition = "2024"
> +
> +[dependencies]
> +anyhow = "1.0.100"
> +clap = { version = "4.5.45", features = ["derive"] }
> +env_logger = "0.11.8"
> +log = { version = "0.4.27", features = ["release_max_level_debug"] }
> +vhost-device-net = "0.1.0"
> +tokio = { version = "1.48.0", features = ["macros", "rt"] }
> +futures-util = "0.3.31"
> +zerocopy = "0.8.27"
> +tokio-stream = "0.1.17"
> diff --git a/tools/router/default.nix b/tools/router/default.nix
> new file mode 100644
> index 0000000..e70f9ec
> --- /dev/null
> +++ b/tools/router/default.nix
> @@ -0,0 +1,18 @@
> +# SPDX-FileCopyrightText: 2024 Alyssa Ross <hi@alyssa.is>
> +# SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev>
> +# SPDX-License-Identifier: MIT
> +
> +import ../../lib/call-package.nix (
> +{ src, lib, rustPlatform }:
> +
> +rustPlatform.buildRustPackage {
> +  name = "spectrum-router";
> +
> +  src = lib.fileset.toSource {
> +    root = ../..;
> +    fileset = lib.fileset.intersection src ./.;
> +  };
> +  sourceRoot = "source/tools/router";
> +
> +  cargoLock.lockFile = ./Cargo.lock;
> +}) (_: {})
> diff --git a/tools/router/src/main.rs b/tools/router/src/main.rs
> new file mode 100644
> index 0000000..42fb8f7
> --- /dev/null
> +++ b/tools/router/src/main.rs
> @@ -0,0 +1,235 @@
> +// SPDX-License-Identifier: EUPL-1.2+
> +// SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev>
> +
> +use std::collections::HashMap;
> +use std::io;
> +use std::net::Ipv6Addr;
> +use std::path::PathBuf;
> +
> +use clap::Parser;

Nit: Would it make sense to use a lighter option parser, possibly even
C getopt?  This isn't going to be used interactively, so completions
aren't a concern.  Reducing the number of dependencies would be a
good idea.

> +use futures_util::SinkExt;
> +use futures_util::StreamExt;
> +use log::{debug, error, info, trace, warn};
> +use tokio::net::UnixListener;
> +use tokio_stream::StreamMap;
> +use vhost_device_net::VhostDeviceNet;
> +
> +use zerocopy::byteorder::network_endian::{U16, U32};
> +use zerocopy::*;
> +
> +type MacAddr = [u8; 6];
> +
> +#[derive(Debug, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
> +#[repr(C)]
> +struct EtherFrame {
> +    //preamble_sfd: U64,
> +    dst_addr: MacAddr,
> +    src_addr: MacAddr,
> +    ether_type: U16,
> +    //data: [u8]
> +}
> +
> +impl AsRef<[u8]> for EtherFrame {
> +    fn as_ref(&self) -> &[u8] {
> +        self.as_bytes()
> +    }
> +}
> +
> +#[derive(Debug, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
> +#[repr(C)]
> +struct VlanTag {
> +    tag_control_information: U16,
> +    ether_type: U16,
> +}
> +
> +impl AsRef<[u8]> for VlanTag {
> +    fn as_ref(&self) -> &[u8] {
> +        self.as_bytes()
> +    }
> +}
> +
> +#[derive(Debug, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
> +#[repr(C)]
> +struct Ipv6Header {
> +    version_traffic_class_flow_label: U32,
> +    payload_length: U16,
> +    hext_header: u8,
> +    hop_limit: u8,
> +    src_addr: [u8; 16],
> +    dst_addr: [u8; 16],
> +    //data: [u8]
> +}
> +
> +impl AsRef<[u8]> for Ipv6Header {
> +    fn as_ref(&self) -> &[u8] {
> +        self.as_bytes()
> +    }
> +}
> +
> +#[derive(Parser, Debug)]
> +#[command()] //version = None, about = None, long_about = None)]
> +struct Args {
> +    #[arg(long)]
> +    driver_listen_path: PathBuf,
> +    #[arg(long)]
> +    app_listen_path: PathBuf,
> +    #[arg(long)]
> +    upstream_interface: u16,
> +}
> +
> +#[tokio::main(flavor = "current_thread")]
> +async fn main() -> anyhow::Result<()> {
> +    env_logger::init();
> +    let args = Args::parse();
> +
> +    run_router(args).await
> +}
> +
> +#[derive(Debug, Clone, PartialEq, Eq, Hash)]
> +enum InterfaceId {
> +    Driver,
> +    App(usize),
> +}
> +
> +async fn run_router(args: Args) -> anyhow::Result<()> {
> +    let driver_listener = UnixListener::bind(&args.driver_listen_path)?;
> +    let app_listener = UnixListener::bind(&args.app_listen_path)?;
> +
> +    let fib: HashMap<Ipv6Addr, (MacAddr, InterfaceId)> = Default::default();
> +
> +    let mut fib = fib.clone();
> +    let mut phys_mac = None;
> +
> +    let mut streams = StreamMap::new();

This won't scale to large numbers of streams.  See the documentation:

> In general, StreamMap works best with a “smallish” number of
> streams as all entries are scanned on insert, remove, and polling.

Also, would it be possible to provide a type signature here?  It's not
necessary, but it makes the code quite a bit easier to review,
especially when one doesn't have an IDE.

> +    let mut sinks = HashMap::<InterfaceId, _>::new();
> +
> +    let mut app_num = 0;
> +
> +    loop {
> +        tokio::select! {

tokio::select! isn't cancellation-safe in general.  Would it make
sense to use multiple tasks instead?

> +            driver_conn = driver_listener.accept() => {
> +                info!("driver connected");
> +                match driver_conn {
> +                    Ok((stream, _addr)) => {
> +                        let device = VhostDeviceNet::from_unix_stream(stream).await?;
> +                        streams.insert(InterfaceId::Driver, Box::pin(device.tx().await?));
> +                        sinks.insert(InterfaceId::Driver, Box::pin(device.rx::<Box<dyn io::Read + Send + Sync + 'static>>().await?));
> +                        phys_mac = None;
> +                    }
> +                    Err(e) => error!("driver connection failed: {}", e),
> +                }
> +            }
> +            app_conn = app_listener.accept() => {
> +                info!("app connected");
> +                match app_conn {
> +                    Ok((stream, _addr)) => {
> +                        let device = VhostDeviceNet::from_unix_stream(stream).await?;
> +                        streams.insert(InterfaceId::App(app_num), Box::pin(device.tx().await?));
> +                        sinks.insert(InterfaceId::App(app_num), Box::pin(device.rx().await?));
> +                        app_num = app_num.checked_add(1).unwrap();
> +                    }
> +                    Err(e) => error!("app connection failed: {}", e),
> +                }
> +            }
> +            next_res = streams.next(), if !streams.is_empty() => {
> +                let Some((key, Ok(buf))) = next_res else {
> +                    info!("incoming other");
> +                    continue;
> +                };
> +                match &key {
> +                    InterfaceId::Driver => {
> +                        use io::Read;
> +                        let mut ether_bytes = buf.take(size_of::<EtherFrame>() as u64);
> +                        let mut ether_frame = EtherFrame::read_from_io(&mut ether_bytes)?;
> +                        let remaining_buf = ether_bytes.into_inner();
> +
> +                        if ether_frame.ether_type != 0x8100 {
> +                            warn!("untagged packet from driver");

Anything that is logged by default should be rate-limited to avoid
filling the host disk or consuming too much CPU.

> +                            //eprintln!("{:x?}", ether_frame);
> +                            //let mut ipv6_bytes = remaining_buf.take(size_of::<Ipv6Header>() as u64);
> +                            //let ipv6_hdr = Ipv6Header::read_from_io(&mut ipv6_bytes)?;
> +                            //let remaining_buf = ipv6_bytes.into_inner();
> +                            //eprintln!("{:x?}", ipv6_hdr);

Would it be possible to make these into proper logs at debug level?

> +                            continue;
> +                        }
> +
> +                        let mut vlan_bytes = remaining_buf.take(size_of::<VlanTag>() as u64);
> +                        let vlan_tag = VlanTag::read_from_io(&mut vlan_bytes)?;
> +                        let remaining_buf = vlan_bytes.into_inner();
> +
> +                        let vlan_id = u16::from(vlan_tag.tag_control_information) & 0xfff;
> +                        if vlan_id != args.upstream_interface {
> +                            debug!("dropping packet with vlan {}", vlan_id);
> +                            continue;
> +                        }
> +                        if !phys_mac.is_some() {
> +                            debug!("set phys mac");
> +                            phys_mac = Some(ether_frame.dst_addr);
> +                        }
> +
> +                        if vlan_tag.ether_type != 0x86dd {
> +                            continue;
> +                        }

Could this use a named constant instead of a magic number?

> +                        let mut ipv6_bytes = remaining_buf.take(size_of::<Ipv6Header>() as u64);
> +                        let ipv6_hdr = Ipv6Header::read_from_io(&mut ipv6_bytes)?;
> +                        let remaining_buf = ipv6_bytes.into_inner();
> +                        trace!("{:x?}", ipv6_hdr);
> +                        let dst_addr = Ipv6Addr::from(ipv6_hdr.dst_addr);
> +
> +                        let Some((dst_mac, if_idx)) = fib.get(&dst_addr) else {
> +                            warn!("dropped incoming message to {} because no fib match", dst_addr);
> +                            continue;
> +                        };
> +                        ether_frame.dst_addr = *dst_mac;
> +                        ether_frame.ether_type = vlan_tag.ether_type;
> +
> +                        sinks.get_mut(if_idx).unwrap().send(Box::new(io::Cursor::new(ether_frame).chain(io::Cursor::new(ipv6_hdr)).chain(remaining_buf))).await.unwrap();
> +                    }
> +                    InterfaceId::App(_) => {
> +                        use io::Read;
> +                        let mut ether_bytes = buf.take(size_of::<EtherFrame>() as u64);
> +                        let mut ether_frame = EtherFrame::read_from_io(&mut ether_bytes)?;
> +                        let remaining_buf = ether_bytes.into_inner();
> +                        trace!("{:x?}", ether_frame);
> +                        if ether_frame.ether_type != 0x86dd {
> +                            continue;
> +                        }

Same here.

> +                        let mut ipv6_bytes = remaining_buf.take(size_of::<Ipv6Header>() as u64);
> +                        let ipv6_hdr = Ipv6Header::read_from_io(&mut ipv6_bytes)?;
> +                        let remaining_buf = ipv6_bytes.into_inner();
> +                        trace!("{:x?}", ipv6_hdr);
> +                        let src_addr = Ipv6Addr::from(ipv6_hdr.src_addr);
> +                        trace!("{:x?}", src_addr);
> +                        if !src_addr.is_unspecified() && !src_addr.is_multicast() && !fib.contains_key(&src_addr) {
> +                            debug!("adding fib entry for {}", src_addr);
> +                            fib.insert(src_addr, (ether_frame.src_addr.clone(), key));
> +                        }

Does this break Duplicate Address Detection?

> +                        let Some(phys_mac) = &phys_mac else {
> +                            warn!("dropped message because phys mac is unknown");
> +                            continue;
> +                        };
> +                        let vlan_tag = VlanTag {
> +                            ether_type: ether_frame.ether_type,
> +                            tag_control_information: args.upstream_interface.into(),
> +                        };
> +                        ether_frame.src_addr = phys_mac.clone();
> +                        ether_frame.ether_type = 0x8100.into();
> +
> +                        let Some(sink) = sinks.get_mut(&InterfaceId::Driver) else {
> +                            warn!("dropped message because driver is not ready");
> +                            continue;
> +                        };
> +
> +                        sink.send(Box::new(io::Cursor::new(ether_frame)
> +                            .chain(io::Cursor::new(vlan_tag))
> +                            .chain(io::Cursor::new(ipv6_hdr))
> +                            .chain(remaining_buf)))
> +                            .await?;
> +                    }
> +                }
> +            }
> +        }
> +    }
> +}

Some general notes that do not need to be addressed (yet):

- The overall design looks somewhat inefficient.  There are multiple
  copies and lots of heap allocations involved.  Some optimization
  opportunities:

  - memcpy() headers between VM memory and on-stack buffers.

  - memcpy() packet bodies from one VM directly to another VM.

  - Batch operations so that each individual operation fits in the
    L1 I-cache if it doesn't already.  This amortizes the cost of
    filling up the L1 I-cache across the entire batch.

  - Batch any needed table lookups.  For instance, if the FIB is a
    longest-prefix lookup tree, one can prefetch all of the cache
    lines that must be accessed before actually loading any of them.

  - If there are a bunch of packets in the buffer and the buffer
    just became empty, spin for a small amount of time before going
    to sleep.

  - The general-purpose Tokio datastructures you are using are probably
    suboptimal performance-wise.

  With proper optimization, I expect that you should be able to beat
  both pasta and the Linux kernel, possibly by a substantial margin.

  This should *not* block merging, but I think it *should* be done at
  some point.  If nothing else, it will improve battery life during
  heavy network I/O.

- Many people (like myself) do not have IPv6 connectivity at all.
  Therefore, this completely breaks networking for them.  I think
  that the old path should remain as an option until IPv4 support
  is implemented.
-- 
Sincerely,
Demi Marie Obenour (she/her/hers)

[-- Attachment #1.1.2: OpenPGP public key --]
[-- Type: application/pgp-keys, Size: 7253 bytes --]

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

  reply	other threads:[~2025-11-24 19:05 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-11-24 16:35 [PATCH v1 RFC 0/4] spectrum-router Yureka Lilian
2025-11-24 16:35 ` [PATCH v1 RFC 1/4] vm/sys/net: remove connman & dbus Yureka Lilian
2025-11-25 10:15   ` Alyssa Ross
2025-11-25 11:37     ` Yureka
2025-11-26  1:25       ` Demi Marie Obenour
2025-11-24 16:35 ` [PATCH v1 RFC 2/4] vm/sys/net: integrate xdp-forwarder Yureka Lilian
2025-11-26 10:53   ` Alyssa Ross
2025-11-24 16:35 ` [PATCH v1 RFC 3/4] tools: add spectrum-router Yureka Lilian
2025-11-24 19:05   ` Demi Marie Obenour [this message]
2025-11-24 16:35 ` [PATCH v1 RFC 4/4] host: integrate router Yureka Lilian
2025-11-24 19:10   ` Demi Marie Obenour
2025-11-24 20:06     ` Yureka

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=ed5badb2-36dc-4069-9f28-8dc73d86d376@gmail.com \
    --to=demiobenour@gmail.com \
    --cc=devel@spectrum-os.org \
    --cc=yureka@cyberchaos.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://spectrum-os.org/git/crosvm
	https://spectrum-os.org/git/doc
	https://spectrum-os.org/git/mktuntap
	https://spectrum-os.org/git/nixpkgs
	https://spectrum-os.org/git/spectrum
	https://spectrum-os.org/git/ucspi-vsock
	https://spectrum-os.org/git/www

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).