From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from atuin.qyliss.net (localhost [IPv6:::1]) by atuin.qyliss.net (Postfix) with ESMTP id 9565C1DAC6; Mon, 24 Nov 2025 19:05:28 +0000 (UTC) Received: by atuin.qyliss.net (Postfix, from userid 993) id C91EB1DB21; Mon, 24 Nov 2025 19:05:25 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-26) on atuin.qyliss.net X-Spam-Level: X-Spam-Status: No, score=-0.1 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,DMARC_PASS,FREEMAIL_FROM,RCVD_IN_DNSWL_NONE, SPF_HELO_NONE autolearn=unavailable autolearn_force=no version=4.0.1 Received: from mail-yw1-x112b.google.com (mail-yw1-x112b.google.com [IPv6:2607:f8b0:4864:20::112b]) by atuin.qyliss.net (Postfix) with ESMTPS id 427901DABD for ; Mon, 24 Nov 2025 19:05:23 +0000 (UTC) Received: by mail-yw1-x112b.google.com with SMTP id 00721157ae682-78a7af9fe4fso48279447b3.2 for ; Mon, 24 Nov 2025 11:05:22 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1764011116; x=1764615916; darn=spectrum-os.org; h=in-reply-to:autocrypt:from:content-language:references:to:subject :user-agent:mime-version:date:message-id:from:to:cc:subject:date :message-id:reply-to; bh=qSsWb+t+/wUoa47flF6uUDhfEZL/PTJ2xlJURCCccTw=; b=kRHhpgZktEoSx4YJ5xvOW9qKdFlDKw92hRIoCw7xaL48VNQiHhjwn3Z0wp/NYUCgbC OhsbUQJ/UMHj+shqEcLfwCSVJpwNslW7EiXolgBSVMmmQU/qugSNJalhb/FRxKKShGXt 0B1YI+wnty1qEf/JpmjK1zkh/DEkPUKjkpoSYngN69NNxrDSFdfnvtrbrLbre3JfHEvd tHmw1s6ShI6B2HblxcEQtclvQI+x/4yHXjj/aL7+rA6g5kHftgTwhl8D/w5V/voo0uNN yPcOl7IJB+r4ZIH981NiScjRHCo0OMbecq/L1Fu2yLG3wW4knLUvI7nfV4qV8oAYFvnk T1jg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1764011116; x=1764615916; h=in-reply-to:autocrypt:from:content-language:references:to:subject :user-agent:mime-version:date:message-id:x-gm-gg:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; bh=qSsWb+t+/wUoa47flF6uUDhfEZL/PTJ2xlJURCCccTw=; b=RdfhKpr+cJ4Ni2G9hGetX2wXKcYV0G5YeVprL1MmQHxgcSZwt61uh9qXTn80mQ/HQ9 D3UVfOoSA9pfSY9YynQpk+6kfLknANG91sgT/E01XgQ/il0v6MZnTt/x78EFCmELaOvL Lo3Ex+Kkl97hJzwNO7VT7i1Qkz8fi9j7VhA2hqfvb/B8yqHiCrpxGy3WWTPnOcf8qIoK qLEyFML4b9lXDqHuYaxMd/ml66QPuenpuDdLKylCoeynTfrLfq28gKDbLaP26WsORRIG GkBe7rPJGw7nOwH7fbZkyLdGj3zEBF93ZC9a/RSnup4O1hMQOL6zjKRWhIac+y9Q1RqX wg9w== X-Forwarded-Encrypted: i=1; AJvYcCV08chjifKdJ6b0+/OYqlI9LhIs5wSe9XlZ4sbL9dv7ZYkdWZgH8PpM0+BM2BfPW5UBpUkHFg==@spectrum-os.org X-Gm-Message-State: AOJu0Yzn5jECC8xmGM90a1Ze3YpAbkuojkYQc1ehxVEYYK08WpWvX6m9 WANbx6i7a01pKde1bsWKUrEzmqd6ypYatrcHBlqv/V6XO2fvahkIori/sgBx4g== X-Gm-Gg: ASbGncu/fOgLxagcFq2ALwURAQ3YDj/8AJ9RmZm+sEbFJhLY42NHmrraZbKMMWUGmtq Lswxsxl9JTdRNo4rmtHxa3pVXGsLdckQ/QYgiNA5tms31BS891jURCW21jD7bXGqJ3Fdb7+ja5G h1OhSCAwvWEL69KhjuStvsL1Did8PioQrocrS0Fbx2+4CMdRZ/iiDwuGTpzUiuWt/ipI3rxwTtF xZO+vEYzWzc1UojtTS8M3umnNYvRskzorYf6DOB9kWXSnY32ITetc5FMtya9KDeKNIbANYOddk6 +ux4OngnBQxUpGdVg6D3TXXp/dfoz5Hm/NIa5DZQbJmStPY/IlUS+gfFYLwHb40NhInx+x8zSeD 8xjyTf5OFIbmsvmbUG8hDx3NK0V/WR3xkxcZ4fFzAgA45L+YRcJFslqdcYxIH0YBLDlDbkCZjp7 ntFvIkbAakdg2kFFb0pc7T13BxcahXQY4NQigrHOJWIQxO6J5uZUOQUc92liXGIydf9OdecaQnE s/K9y39jZbyKfU9cl5vzMBpZHw= X-Google-Smtp-Source: AGHT+IEsQw+tpl9fHrn+srz/HSUb/qkhs3A+rB8RD43LcodVTCg2mheDVBNVP0N2eqOYSLRh6/oPUg== X-Received: by 2002:a05:690c:4c13:b0:787:ce99:eaa0 with SMTP id 00721157ae682-78a8b580502mr115121607b3.70.1764011115497; Mon, 24 Nov 2025 11:05:15 -0800 (PST) Received: from [10.138.34.110] (h96-60-249-169.cncrtn.broadband.dynamic.tds.net. [96.60.249.169]) by smtp.gmail.com with ESMTPSA id 00721157ae682-78a7987f4e0sm47919977b3.4.2025.11.24.11.05.14 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Mon, 24 Nov 2025 11:05:14 -0800 (PST) Message-ID: Date: Mon, 24 Nov 2025 14:05:09 -0500 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PATCH v1 RFC 3/4] tools: add spectrum-router To: Yureka Lilian , devel@spectrum-os.org References: <20251124163531.91029-1-yureka@cyberchaos.dev> <20251124163531.91029-4-yureka@cyberchaos.dev> Content-Language: en-US From: Demi Marie Obenour Autocrypt: addr=demiobenour@gmail.com; keydata= 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 +MYSfkEjBz0E8CLOcAw7JIwAaeBT In-Reply-To: <20251124163531.91029-4-yureka@cyberchaos.dev> Content-Type: multipart/signed; micalg=pgp-sha256; protocol="application/pgp-signature"; boundary="------------3ESZLPXQq1bg0V0MhuL4iwbX" Message-ID-Hash: ZLJL3OXRINGPNCAPYSGA6DQIDFPGG7EC X-Message-ID-Hash: ZLJL3OXRINGPNCAPYSGA6DQIDFPGG7EC X-MailFrom: demiobenour@gmail.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-devel.spectrum-os.org-0; header-match-devel.spectrum-os.org-1; header-match-devel.spectrum-os.org-2; header-match-devel.spectrum-os.org-3; header-match-devel.spectrum-os.org-4; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.9 Precedence: list List-Id: Patches and low-level development discussion Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: This is an OpenPGP/MIME signed message (RFC 4880 and 3156) --------------3ESZLPXQq1bg0V0MhuL4iwbX Content-Type: multipart/mixed; boundary="------------yiksuYiBycRub3FUORAjVgO0"; protected-headers="v1" From: Demi Marie Obenour To: Yureka Lilian , devel@spectrum-os.org Message-ID: Subject: Re: [PATCH v1 RFC 3/4] tools: add spectrum-router References: <20251124163531.91029-1-yureka@cyberchaos.dev> <20251124163531.91029-4-yureka@cyberchaos.dev> In-Reply-To: <20251124163531.91029-4-yureka@cyberchaos.dev> --------------yiksuYiBycRub3FUORAjVgO0 Content-Type: multipart/mixed; boundary="------------eUBEA2iAU93AfuFYQJkBwpg0" --------------eUBEA2iAU93AfuFYQJkBwpg0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable On 11/24/25 11:35, Yureka Lilian wrote: > The tokio-vhost & vhost-device-net crates which we also wrote and depen= d > on are left external in the outlook of becoming a rust-vmm project soon= =2E >=20 > Signed-off-by: Yureka Lilian > --- > 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 >=20 > 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 =3D > self.callSpectrumPackage ../tools/xdg-desktop-portal-spectrum-ho= st {}; > =20 > + spectrum-router =3D 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 =3D 4 > + > +[[package]] > +name =3D "aho-corasick" > +version =3D "1.1.4" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f10028= 16174301" > +dependencies =3D [ > + "memchr", > +] > + > +[[package]] > +name =3D "anstream" > +version =3D "0.6.21" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a2= 6b8f4f4a" > +dependencies =3D [ > + "anstyle", > + "anstyle-parse", > + "anstyle-query", > + "anstyle-wincon", > + "colorchoice", > + "is_terminal_polyfill", > + "utf8parse", > +] > + > +[[package]] > +name =3D "anstyle" > +version =3D "1.0.13" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9= b0b45e78" > + > +[[package]] > +name =3D "anstyle-parse" > +version =3D "0.2.7" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594= dd9c15c2" > +dependencies =3D [ > + "utf8parse", > +] > + > +[[package]] > +name =3D "anstyle-query" > +version =3D "1.1.5" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65= af83dadc" > +dependencies =3D [ > + "windows-sys 0.61.2", > +] > + > +[[package]] > +name =3D "anstyle-wincon" > +version =3D "3.0.11" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353= ad55747d" > +dependencies =3D [ > + "anstyle", > + "once_cell_polyfill", > + "windows-sys 0.61.2", > +] > + > +[[package]] > +name =3D "anyhow" > +version =3D "1.0.100" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c08= 09af6e61" > + > +[[package]] > +name =3D "async-stream" > +version =3D "0.3.6" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc= 2cfe8476" > +dependencies =3D [ > + "async-stream-impl", > + "futures-core", > + "pin-project-lite", > +] > + > +[[package]] > +name =3D "async-stream-impl" > +version =3D "0.3.6" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b7755= 96a3574d" > +dependencies =3D [ > + "proc-macro2", > + "quote", > + "syn", > +] > + > +[[package]] > +name =3D "bitflags" > +version =3D "1.3.2" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb= 1037718a" > + > +[[package]] > +name =3D "bitvec" > +version =3D "1.0.1" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172c= a46f7e9c" > +dependencies =3D [ > + "funty", > + "radium", > + "tap", > + "wyz", > +] > + > +[[package]] > +name =3D "bytes" > +version =3D "1.11.0" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd= 960f9cb3" > + > +[[package]] > +name =3D "clap" > +version =3D "4.5.53" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e2= 3d2d03f8" > +dependencies =3D [ > + "clap_builder", > + "clap_derive", > +] > + > +[[package]] > +name =3D "clap_builder" > +version =3D "4.5.53" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922= b8569d00" > +dependencies =3D [ > + "anstream", > + "anstyle", > + "clap_lex", > + "strsim", > +] > + > +[[package]] > +name =3D "clap_derive" > +version =3D "4.5.49" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d8688444= 0c7f7671" > +dependencies =3D [ > + "heck", > + "proc-macro2", > + "quote", > + "syn", > +] > + > +[[package]] > +name =3D "clap_lex" > +version =3D "0.7.6" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c= 6ae29d8d" > + > +[[package]] > +name =3D "colorchoice" > +version =3D "1.0.4" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41e= feac1e75" > + > +[[package]] > +name =3D "env_filter" > +version =3D "0.1.4" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d= 89f568e2" > +dependencies =3D [ > + "log", > + "regex", > +] > + > +[[package]] > +name =3D "env_logger" > +version =3D "0.11.8" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aa= e4fa069f" > +dependencies =3D [ > + "anstream", > + "anstyle", > + "env_filter", > + "jiff", > + "log", > +] > + > +[[package]] > +name =3D "fastrand" > +version =3D "2.3.0" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0= 355411be" > + > +[[package]] > +name =3D "funty" > +version =3D "2.0.0" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba= 1001fc9c" > + > +[[package]] > +name =3D "futures-core" > +version =3D "0.3.31" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f8= 7af6663e" > + > +[[package]] > +name =3D "futures-io" > +version =3D "0.3.31" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe5= 7ce075c6" > + > +[[package]] > +name =3D "futures-lite" > +version =3D "2.6.1" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306d= c46b44ad" > +dependencies =3D [ > + "fastrand", > + "futures-core", > + "futures-io", > + "parking", > + "pin-project-lite", > +] > + > +[[package]] > +name =3D "futures-macro" > +version =3D "0.3.31" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fe= f13c3650" > +dependencies =3D [ > + "proc-macro2", > + "quote", > + "syn", > +] > + > +[[package]] > +name =3D "futures-sink" > +version =3D "0.3.31" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819ac= acf1d4f7" > + > +[[package]] > +name =3D "futures-task" > +version =3D "0.3.31" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd3890= 2cd99988" > + > +[[package]] > +name =3D "futures-util" > +version =3D "0.3.31" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85= b20a4a81" > +dependencies =3D [ > + "futures-core", > + "futures-macro", > + "futures-sink", > + "futures-task", > + "pin-project-lite", > + "pin-utils", > + "slab", > +] > + > +[[package]] > +name =3D "heck" > +version =3D "0.5.0" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe3344= 9bbe55ea" > + > +[[package]] > +name =3D "is_terminal_polyfill" > +version =3D "1.70.2" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d= 08a35695" > + > +[[package]] > +name =3D "jiff" > +version =3D "0.2.16" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f3= 2a6dbb35" > +dependencies =3D [ > + "jiff-static", > + "log", > + "portable-atomic", > + "portable-atomic-util", > + "serde_core", > +] > + > +[[package]] > +name =3D "jiff-static" > +version =3D "0.2.16" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe= 1850fa69" > +dependencies =3D [ > + "proc-macro2", > + "quote", > + "syn", > +] > + > +[[package]] > +name =3D "libc" > +version =3D "0.2.177" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf= 92b15976" > + > +[[package]] > +name =3D "log" > +version =3D "0.4.28" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97e= f3bf7432" > + > +[[package]] > +name =3D "memchr" > +version =3D "2.7.6" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6b= dc4cc273" > + > +[[package]] > +name =3D "mio" > +version =3D "1.1.0" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639= b0092873" > +dependencies =3D [ > + "libc", > + "wasi", > + "windows-sys 0.61.2", > +] > + > +[[package]] > +name =3D "once_cell_polyfill" > +version =3D "1.70.2" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b530387= 8451b4fe" > + > +[[package]] > +name =3D "parking" > +version =3D "2.2.1" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e37= 7f7dceba" > + > +[[package]] > +name =3D "pin-project-lite" > +version =3D "0.2.16" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76= fe619f9b" > + > +[[package]] > +name =3D "pin-utils" > +version =3D "0.1.0" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaa= f7c9f184" > + > +[[package]] > +name =3D "portable-atomic" > +version =3D "1.11.1" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe1= 53674483" > + > +[[package]] > +name =3D "portable-atomic-util" > +version =3D "0.2.4" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e10= 69e83507" > +dependencies =3D [ > + "portable-atomic", > +] > + > +[[package]] > +name =3D "proc-macro2" > +version =3D "1.0.103" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00e= c45f49e8" > +dependencies =3D [ > + "unicode-ident", > +] > + > +[[package]] > +name =3D "quote" > +version =3D "1.0.42" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd8= 0ef3067f" > +dependencies =3D [ > + "proc-macro2", > +] > + > +[[package]] > +name =3D "radium" > +version =3D "0.7.0" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f= 47749d09" > + > +[[package]] > +name =3D "regex" > +version =3D "1.12.2" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66e= defc24d4" > +dependencies =3D [ > + "aho-corasick", > + "memchr", > + "regex-automata", > + "regex-syntax", > +] > + > +[[package]] > +name =3D "regex-automata" > +version =3D "0.4.13" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b= 84041f1c" > +dependencies =3D [ > + "aho-corasick", > + "memchr", > + "regex-syntax", > +] > + > +[[package]] > +name =3D "regex-syntax" > +version =3D "0.8.8" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430= af7def58" > + > +[[package]] > +name =3D "serde_core" > +version =3D "1.0.228" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de= 458b67ad" > +dependencies =3D [ > + "serde_derive", > +] > + > +[[package]] > +name =3D "serde_derive" > +version =3D "1.0.228" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "d540f220d3187173da220f885ab66608367b6574e925011a9353e4ba= dda91d79" > +dependencies =3D [ > + "proc-macro2", > + "quote", > + "syn", > +] > + > +[[package]] > +name =3D "slab" > +version =3D "0.4.11" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c= 90267589" > + > +[[package]] > +name =3D "socket2" > +version =3D "0.6.1" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "17129e116933cf371d018bb80ae557e889637989d8638274fb256228= 27b03881" > +dependencies =3D [ > + "libc", > + "windows-sys 0.60.2", > +] > + > +[[package]] > +name =3D "spectrum-router" > +version =3D "0.1.0" > +dependencies =3D [ > + "anyhow", > + "clap", > + "env_logger", > + "futures-util", > + "log", > + "tokio", > + "tokio-stream", > + "vhost-device-net", > + "zerocopy", > +] > + > +[[package]] > +name =3D "strsim" > +version =3D "0.11.1" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b7788= 38c5fb4f" > + > +[[package]] > +name =3D "syn" > +version =3D "2.0.110" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e601= 5b280aea" > +dependencies =3D [ > + "proc-macro2", > + "quote", > + "unicode-ident", > +] > + > +[[package]] > +name =3D "tap" > +version =3D "1.0.1" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a= 097d8369" > + > +[[package]] > +name =3D "thiserror" > +version =3D "1.0.69" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904= bc923f52" > +dependencies =3D [ > + "thiserror-impl", > +] > + > +[[package]] > +name =3D "thiserror-impl" > +version =3D "1.0.69" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b= 6c7504c1" > +dependencies =3D [ > + "proc-macro2", > + "quote", > + "syn", > +] > + > +[[package]] > +name =3D "tokio" > +version =3D "1.48.0" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5b= d4c68408" > +dependencies =3D [ > + "bytes", > + "libc", > + "mio", > + "pin-project-lite", > + "socket2", > + "tokio-macros", > + "windows-sys 0.61.2", > +] > + > +[[package]] > +name =3D "tokio-eventfd" > +version =3D "0.2.2" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "7e66bd133670ac39baa1aca5c3a86709f4595c08ca4464a1e1400b83= d62c0639" > +dependencies =3D [ > + "futures-lite", > + "libc", > + "tokio", > +] > + > +[[package]] > +name =3D "tokio-macros" > +version =3D "2.6.0" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617= e9db9fa5" > +dependencies =3D [ > + "proc-macro2", > + "quote", > + "syn", > +] > + > +[[package]] > +name =3D "tokio-stream" > +version =3D "0.1.17" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07= c139a047" > +dependencies =3D [ > + "futures-core", > + "pin-project-lite", > + "tokio", > +] > + > +[[package]] > +name =3D "tokio-vhost" > +version =3D "0.1.0" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "21d64b3e4d573da90b2bb040d69a9c2d754e8a3ab9d9ecf04a268748= c99f1cd3" > +dependencies =3D [ > + "async-stream", > + "bitvec", > + "futures-util", > + "libc", > + "log", > + "tokio", > + "tokio-eventfd", > + "virtio-queue", > + "vm-memory", > + "zerocopy", > +] > + > +[[package]] > +name =3D "unicode-ident" > +version =3D "1.0.22" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27d= c2cf1df5" > + > +[[package]] > +name =3D "utf8parse" > +version =3D "0.2.2" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9= f1010821" > + > +[[package]] > +name =3D "vhost-device-net" > +version =3D "0.1.0" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "ac05caccd6d484f672551a187f7110ff9d32edd6a39bb16bb04f5301= 7b1e6fd0" > +dependencies =3D [ > + "futures-util", > + "log", > + "tokio", > + "tokio-vhost", > + "vm-memory", > +] > + > +[[package]] > +name =3D "virtio-bindings" > +version =3D "0.2.6" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "804f498a26d5a63be7bbb8bdcd3869c3f286c4c4a17108905276454d= a0caf8cb" > + > +[[package]] > +name =3D "virtio-queue" > +version =3D "0.16.0" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "fb0479158f863e59323771a1f684d843962f76960b86fecfec2bfa9c= 8f0f9180" > +dependencies =3D [ > + "log", > + "virtio-bindings", > + "vm-memory", > + "vmm-sys-util", > +] > + > +[[package]] > +name =3D "vm-memory" > +version =3D "0.16.2" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "1fd5e56d48353c5f54ef50bd158a0452fc82f5383da840f7b8efc316= 95dd3b9d" > +dependencies =3D [ > + "libc", > + "thiserror", > + "winapi", > +] > + > +[[package]] > +name =3D "vmm-sys-util" > +version =3D "0.14.0" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "d21f366bf22bfba3e868349978766a965cbe628c323d58e026be80b8= 357ab789" > +dependencies =3D [ > + "bitflags", > + "libc", > +] > + > +[[package]] > +name =3D "wasi" > +version =3D "0.11.1+wasi-snapshot-preview1" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd65= 4f4ea44b" > + > +[[package]] > +name =3D "winapi" > +version =3D "0.3.9" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d2= 8b00f419" > +dependencies =3D [ > + "winapi-i686-pc-windows-gnu", > + "winapi-x86_64-pc-windows-gnu", > +] > + > +[[package]] > +name =3D "winapi-i686-pc-windows-gnu" > +version =3D "0.4.0" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6= ab51b7b6" > + > +[[package]] > +name =3D "winapi-x86_64-pc-windows-gnu" > +version =3D "0.4.0" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc= 1931183f" > + > +[[package]] > +name =3D "windows-link" > +version =3D "0.2.1" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e7= 0d0a4fe5" > + > +[[package]] > +name =3D "windows-sys" > +version =3D "0.60.2" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c358= 82612bcb" > +dependencies =3D [ > + "windows-targets", > +] > + > +[[package]] > +name =3D "windows-sys" > +version =3D "0.61.2" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3= 921ff3fc" > +dependencies =3D [ > + "windows-link", > +] > + > +[[package]] > +name =3D "windows-targets" > +version =3D "0.53.5" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b1= 9f9069f3" > +dependencies =3D [ > + "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 =3D "windows_aarch64_gnullvm" > +version =3D "0.53.1" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a5610= 4a6d1b53" > + > +[[package]] > +name =3D "windows_aarch64_msvc" > +version =3D "0.53.1" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e= 9b125006" > + > +[[package]] > +name =3D "windows_i686_gnu" > +version =3D "0.53.1" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a= 506593a3" > + > +[[package]] > +name =3D "windows_i686_gnullvm" > +version =3D "0.53.1" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c2= 7057180c" > + > +[[package]] > +name =3D "windows_i686_msvc" > +version =3D "0.53.1" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a= 38d326c2" > + > +[[package]] > +name =3D "windows_x86_64_gnu" > +version =3D "0.53.1" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa= 7df4a499" > + > +[[package]] > +name =3D "windows_x86_64_gnullvm" > +version =3D "0.53.1" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47= f6bb7ae1" > + > +[[package]] > +name =3D "windows_x86_64_msvc" > +version =3D "0.53.1" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d= 0ebed650" > + > +[[package]] > +name =3D "wyz" > +version =3D "0.5.1" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f11= 8f77b9ed" > +dependencies =3D [ > + "tap", > +] > + > +[[package]] > +name =3D "zerocopy" > +version =3D "0.8.28" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "43fa6694ed34d6e57407afbccdeecfa268c470a7d2a5b0cf49ce9fcc= 345afb90" > +dependencies =3D [ > + "zerocopy-derive", > +] > + > +[[package]] > +name =3D "zerocopy-derive" > +version =3D "0.8.28" > +source =3D "registry+https://github.com/rust-lang/crates.io-index" > +checksum =3D "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27= ac2cbc26" > +dependencies =3D [ > + "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 > + > +[package] > +name =3D "spectrum-router" > +version =3D "0.1.0" > +edition =3D "2024" > + > +[dependencies] > +anyhow =3D "1.0.100" > +clap =3D { version =3D "4.5.45", features =3D ["derive"] } > +env_logger =3D "0.11.8" > +log =3D { version =3D "0.4.27", features =3D ["release_max_level_debug= "] } > +vhost-device-net =3D "0.1.0" > +tokio =3D { version =3D "1.48.0", features =3D ["macros", "rt"] } > +futures-util =3D "0.3.31" > +zerocopy =3D "0.8.27" > +tokio-stream =3D "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 > +# SPDX-FileCopyrightText: 2025 Yureka Lilian > +# SPDX-License-Identifier: MIT > + > +import ../../lib/call-package.nix ( > +{ src, lib, rustPlatform }: > + > +rustPlatform.buildRustPackage { > + name =3D "spectrum-router"; > + > + src =3D lib.fileset.toSource { > + root =3D ../..; > + fileset =3D lib.fileset.intersection src ./.; > + }; > + sourceRoot =3D "source/tools/router"; > + > + cargoLock.lockFile =3D ./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 > + > +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 =3D [u8; 6]; > + > +#[derive(Debug, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immu= table, 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, Immu= table, 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, Immu= table, 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 =3D None, about =3D None, long_about =3D None)]= > +struct Args { > + #[arg(long)] > + driver_listen_path: PathBuf, > + #[arg(long)] > + app_listen_path: PathBuf, > + #[arg(long)] > + upstream_interface: u16, > +} > + > +#[tokio::main(flavor =3D "current_thread")] > +async fn main() -> anyhow::Result<()> { > + env_logger::init(); > + let args =3D 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 =3D UnixListener::bind(&args.driver_listen_pat= h)?; > + let app_listener =3D UnixListener::bind(&args.app_listen_path)?; > + > + let fib: HashMap =3D Default::de= fault(); > + > + let mut fib =3D fib.clone(); > + let mut phys_mac =3D None; > + > + let mut streams =3D StreamMap::new(); This won't scale to large numbers of streams. See the documentation: > In general, StreamMap works best with a =E2=80=9Csmallish=E2=80=9D numb= er 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 =3D HashMap::::new(); > + > + let mut app_num =3D 0; > + > + loop { > + tokio::select! { tokio::select! isn't cancellation-safe in general. Would it make sense to use multiple tasks instead? > + driver_conn =3D driver_listener.accept() =3D> { > + info!("driver connected"); > + match driver_conn { > + Ok((stream, _addr)) =3D> { > + let device =3D VhostDeviceNet::from_unix_strea= m(stream).await?; > + streams.insert(InterfaceId::Driver, Box::pin(d= evice.tx().await?)); > + sinks.insert(InterfaceId::Driver, Box::pin(dev= ice.rx::>().await?)); > + phys_mac =3D None; > + } > + Err(e) =3D> error!("driver connection failed: {}",= e), > + } > + } > + app_conn =3D app_listener.accept() =3D> { > + info!("app connected"); > + match app_conn { > + Ok((stream, _addr)) =3D> { > + let device =3D VhostDeviceNet::from_unix_strea= m(stream).await?; > + streams.insert(InterfaceId::App(app_num), Box:= :pin(device.tx().await?)); > + sinks.insert(InterfaceId::App(app_num), Box::p= in(device.rx().await?)); > + app_num =3D app_num.checked_add(1).unwrap(); > + } > + Err(e) =3D> error!("app connection failed: {}", e)= , > + } > + } > + next_res =3D streams.next(), if !streams.is_empty() =3D> {= > + let Some((key, Ok(buf))) =3D next_res else { > + info!("incoming other"); > + continue; > + }; > + match &key { > + InterfaceId::Driver =3D> { > + use io::Read; > + let mut ether_bytes =3D buf.take(size_of::() as u64); > + let mut ether_frame =3D EtherFrame::read_from_= io(&mut ether_bytes)?; > + let remaining_buf =3D ether_bytes.into_inner()= ; > + > + if ether_frame.ether_type !=3D 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 =3D remaining_buf.tak= e(size_of::() as u64); > + //let ipv6_hdr =3D Ipv6Header::read_from_i= o(&mut ipv6_bytes)?; > + //let remaining_buf =3D ipv6_bytes.into_in= ner(); > + //eprintln!("{:x?}", ipv6_hdr); Would it be possible to make these into proper logs at debug level? > + continue; > + } > + > + let mut vlan_bytes =3D remaining_buf.take(size= _of::() as u64); > + let vlan_tag =3D VlanTag::read_from_io(&mut vl= an_bytes)?; > + let remaining_buf =3D vlan_bytes.into_inner();= > + > + let vlan_id =3D u16::from(vlan_tag.tag_control= _information) & 0xfff; > + if vlan_id !=3D args.upstream_interface { > + debug!("dropping packet with vlan {}", vla= n_id); > + continue; > + } > + if !phys_mac.is_some() { > + debug!("set phys mac"); > + phys_mac =3D Some(ether_frame.dst_addr); > + } > + > + if vlan_tag.ether_type !=3D 0x86dd { > + continue; > + } Could this use a named constant instead of a magic number? > + let mut ipv6_bytes =3D remaining_buf.take(size= _of::() as u64); > + let ipv6_hdr =3D Ipv6Header::read_from_io(&mut= ipv6_bytes)?; > + let remaining_buf =3D ipv6_bytes.into_inner();= > + trace!("{:x?}", ipv6_hdr); > + let dst_addr =3D Ipv6Addr::from(ipv6_hdr.dst_a= ddr); > + > + let Some((dst_mac, if_idx)) =3D fib.get(&dst_a= ddr) else { > + warn!("dropped incoming message to {} beca= use no fib match", dst_addr); > + continue; > + }; > + ether_frame.dst_addr =3D *dst_mac; > + ether_frame.ether_type =3D vlan_tag.ether_type= ; > + > + sinks.get_mut(if_idx).unwrap().send(Box::new(i= o::Cursor::new(ether_frame).chain(io::Cursor::new(ipv6_hdr)).chain(remain= ing_buf))).await.unwrap(); > + } > + InterfaceId::App(_) =3D> { > + use io::Read; > + let mut ether_bytes =3D buf.take(size_of::() as u64); > + let mut ether_frame =3D EtherFrame::read_from_= io(&mut ether_bytes)?; > + let remaining_buf =3D ether_bytes.into_inner()= ; > + trace!("{:x?}", ether_frame); > + if ether_frame.ether_type !=3D 0x86dd { > + continue; > + } Same here. > + let mut ipv6_bytes =3D remaining_buf.take(size= _of::() as u64); > + let ipv6_hdr =3D Ipv6Header::read_from_io(&mut= ipv6_bytes)?; > + let remaining_buf =3D ipv6_bytes.into_inner();= > + trace!("{:x?}", ipv6_hdr); > + let src_addr =3D Ipv6Addr::from(ipv6_hdr.src_a= ddr); > + 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= =2Eclone(), key)); > + } Does this break Duplicate Address Detection? > + let Some(phys_mac) =3D &phys_mac else { > + warn!("dropped message because phys mac is= unknown"); > + continue; > + }; > + let vlan_tag =3D VlanTag { > + ether_type: ether_frame.ether_type, > + tag_control_information: args.upstream_int= erface.into(), > + }; > + ether_frame.src_addr =3D phys_mac.clone(); > + ether_frame.ether_type =3D 0x8100.into(); > + > + let Some(sink) =3D sinks.get_mut(&InterfaceId:= :Driver) else { > + warn!("dropped message because driver is n= ot 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. --=20 Sincerely, Demi Marie Obenour (she/her/hers) --------------eUBEA2iAU93AfuFYQJkBwpg0 Content-Type: application/pgp-keys; name="OpenPGP_0xB288B55FFF9C22C1.asc" Content-Disposition: attachment; filename="OpenPGP_0xB288B55FFF9C22C1.asc" Content-Description: OpenPGP public key Content-Transfer-Encoding: quoted-printable -----BEGIN PGP PUBLIC KEY BLOCK----- xsFNBFp+A0oBEADffj6anl9/BHhUSxGTICeVl2tob7hPDdhHNgPR4C8xlYt5q49y B+l2nipdaq+4Gk6FZfqC825TKl7eRpUjMriwle4r3R0ydSIGcy4M6eb0IcxmuPYf bWpr/si88QKgyGSVZ7GeNW1UnzTdhYHuFlk8dBSmB1fzhEYEk0RcJqg4AKoq6/3/ UorR+FaSuVwT7rqzGrTlscnTDlPWgRzrQ3jssesI7sZLm82E3pJSgaUoCdCOlL7M MPCJwI8JpPlBedRpe9tfVyfu3euTPLPxwcV3L/cfWPGSL4PofBtB8NUU6QwYiQ9H zx4xOyn67zW73/G0Q2vPPRst8LBDqlxLjbtx/WLR6h3nBc3eyuZ+q62HS1pJ5EvU T1vjyJ1ySrqtUXWQ4XlZyoEFUfpJxJoN0A9HCxmHGVckzTRl5FMWo8TCniHynNXs BtDQbabt7aNEOaAJdE7to0AH3T/Bvwzcp0ZJtBk0EM6YeMLtotUut7h2Bkg1b//r 6bTBswMBXVJ5H44Qf0+eKeUg7whSC9qpYOzzrm7+0r9F5u3qF8ZTx55TJc2g656C 9a1P1MYVysLvkLvS4H+crmxA/i08Tc1h+x9RRvqba4lSzZ6/Tmt60DPM5Sc4R0nS m9BBff0Nm0bSNRS8InXdO1Aq3362QKX2NOwcL5YaStwODNyZUqF7izjK4QARAQAB zTxEZW1pIE9iZW5vdXIgKElUTCBFbWFpbCBLZXkpIDxhdGhlbmFAaW52aXNpYmxl dGhpbmdzbGFiLmNvbT7CwY4EEwEIADgWIQR2h02fEza6IlkHHHGyiLVf/5wiwQUC X6YJvQIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRCyiLVf/5wiwWRhD/0Y R+YYC5Kduv/2LBgQJIygMsFiRHbR4+tWXuTFqgrxxFSlMktZ6gQrQCWe38WnOXkB oY6n/5lSJdfnuGd2UagZ/9dkaGMUkqt+5WshLFly4BnP7pSsWReKgMP7etRTwn3S zk1OwFx2lzY1EnnconPLfPBc6rWG2moA6l0WX+3WNR1B1ndqpl2hPSjT2jUCBWDV rGOUSX7r5f1WgtBeNYnEXPBCUUM51pFGESmfHIXQrqFDA7nBNiIVFDJTmQzuEqIy Jl67pKNgooij5mKzRhFKHfjLRAH4mmWZlB9UjDStAfFBAoDFHwd1HL5VQCNQdqEc /9lZDApqWuCPadZN+pGouqLysesIYsNxUhJ7dtWOWHl0vs7/3qkWmWun/2uOJMQh ra2u8nA9g91FbOobWqjrDd6x3ZJoGQf4zLqjmn/P514gb697788e573WN/MpQ5XI Fl7aM2d6/GJiq6LC9T2gSUW4rbPBiqOCeiUx7Kd/sVm41p9TOA7fEG4bYddCfDsN xaQJH6VRK3NOuBUGeL+iQEVF5Xs6Yp+U+jwvv2M5Lel3EqAYo5xXTx4ls0xaxDCu fudcAh8CMMqx3fguSb7Mi31WlnZpk0fDuWQVNKyDP7lYpwc4nCCGNKCj622ZSocH AcQmX28L8pJdLYacv9pU3jPy4fHcQYvmTavTqowGnM08RGVtaSBNYXJpZSBPYmVu b3VyIChsb3ZlciBvZiBjb2RpbmcpIDxkZW1pb2Jlbm91ckBnbWFpbC5jb20+wsF4 BBMBAgAiBQJafgNKAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRCyiLVf /5wiwYa/EACv8a2+MMou9cSCNoZBQaU+fTmyzft9hUE+0d5W2UY1RY3OsjFIzm9R /4SVccfsqOYLEo+S0vQMIIIqFEq3FCpXXwPzyimotps05VA8U3Bd7yseojFygOgK sAMOAee2RCaDDOnoJue01dfZMzzHPO/TVdp3OvnpWipfv5G1Xg96rwbhMLE3tg6N xwAHa31Bv4/Xq8CJOoIWvx6fcmZQpz01/lSvsYn0KrfEbTKkuUf0vM9JrCTCP2oz VNN5BYzqaq2M4r+jmSyeXLim922VOWqGkUEQ85BSEemqrRS06IU6NtEMsF8EWt/b hWjk/9GDKTcnpdJHTrMxTspExBiNrvpI2t+YPU5B/dJJAUxvmhFrbSIbdB8umBZs I3AMYrEmpAbh5x7jEjoskUC7uN3o9vpg1oCLS2ePDLtAtyBtbHnkA4xGD7ar8mem xpH9lY/i+sC6CyyIUWcUDnnagKyJP0m9ks0GLsTeOCA0bft2XA6rD6aaCnMUsndT ctrab42CV5XypjmC4U1rPJ8JQJUh1/3P48/8sMH+3krxpJ06KNWNFaUbaMTGiltZ 7x9DngklSYrX0T+2G4kVXNmjaljwkoLahwLla2gUWwBSyofXdqyhQdwZsp01KXNQ UCyT/Pg+aDcm/E7OMV3d4lf7g/CSxiX2GSEe6BlhSz+Lmd7ZJ3g32M1ARGVtaSBN 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 EtJuZYM5blWncBOJCoWMnBEcTEo/viU3GgcVRw=3D=3D =3Dx94R -----END PGP PUBLIC KEY BLOCK----- --------------eUBEA2iAU93AfuFYQJkBwpg0-- --------------yiksuYiBycRub3FUORAjVgO0-- --------------3ESZLPXQq1bg0V0MhuL4iwbX Content-Type: application/pgp-signature; name="OpenPGP_signature.asc" Content-Description: OpenPGP digital signature Content-Disposition: attachment; filename="OpenPGP_signature.asc" -----BEGIN PGP SIGNATURE----- iQIzBAEBCgAdFiEEopQtqVJW1aeuo9/sszaHOrMp8lMFAmkkrGYACgkQszaHOrMp 8lNgqhAAlnDcMfjJBLDEJfSNVdaVgPeNTjrXXg0yytJ9pVw4ujQBFq6o+VwFP5y7 LAHBW35Mc0iaJLtESp4KnG2msqjR18+gGmz+gObvJJNRwnj6cVE6Zx+kZ2xa9lnN 4jR8BymisGyxpLZgvXtUVnYkajPsuNXHQWypwzx/R5Xowx9RwLx1n+Rujs4O/fMG SAYHvruuQRugDKGWvn07OoH7iJI1dOxsScwX87u8aQU7cnG/XV5PqQiHe7Az2UGS a/mHTK7SMf9sm6Zz5xHqqItlKEKoV3p7Ih53LQ/B8Hls3M1CxO1SYoWQ3mpAwhIG LehthFvYRNzkAOee0pG+aE6DuJ7lsWq0XlieX/HDfmgtGcrnI61Y9NXJKY+90bY/ fOvtILVM0IARhmN/iQhjlzU2vNcuKbaw+GyMIeijPbS43T0Owfj+Lq2w1hEoGw3d 848IPufFJMqwHXZ2FLHVlgvujppu390YyGZrjP7OPzXLCjMtk9UKhnR2X6HJ1Se2 e8el51rYh3loY9HFmybvsKO5hp3veiFE1Pww34J6uhORUqBU0do3odVPtXD7CRSF 0Ns7lv33qfsUf5tY2h1XDkkre2JbNmyuBmauJm7z6gAFJXOLrP38lwkfGrcrzjrc /kByPHgbXRSOjNgFV/mVrNqwPCBz3uHoJj223tU6Tf5JElpLr/M= =lLl3 -----END PGP SIGNATURE----- --------------3ESZLPXQq1bg0V0MhuL4iwbX--