From 481dcd21095646ac53d7ddd898a7ef520ed8f2a7 Mon Sep 17 00:00:00 2001 From: Jake Shadle Date: Wed, 8 Jan 2025 11:20:21 +0100 Subject: [PATCH] Add initial XDP skeleton (#1052) * These flags are arch specific * Add eBPF program This commit adds the eBPF program used to determine if packets should be routed to quilkin's AF_XDP sockets * Add first pass of an interface to abstract quilkin's interface to XDP sockets * Not used yet, don't impact compile times * . --- .cargo/config.toml | 2 +- .gitignore | 1 + Cargo.lock | 244 +++++++++++++++++++++++++------ Cargo.toml | 13 +- crates/ebpf/Cargo.lock | 189 ++++++++++++++++++++++++ crates/ebpf/Cargo.toml | 32 ++++ crates/ebpf/build.sh | 15 ++ crates/ebpf/rust-toolchain.toml | 24 +++ crates/ebpf/src/ebpf-main.rs | 167 +++++++++++++++++++++ crates/xdp/Cargo.toml | 18 +++ crates/xdp/bin/packet-router.bin | Bin 0 -> 2552 bytes crates/xdp/src/lib.rs | 190 ++++++++++++++++++++++++ 12 files changed, 850 insertions(+), 45 deletions(-) create mode 100644 crates/ebpf/Cargo.lock create mode 100644 crates/ebpf/Cargo.toml create mode 100755 crates/ebpf/build.sh create mode 100644 crates/ebpf/rust-toolchain.toml create mode 100644 crates/ebpf/src/ebpf-main.rs create mode 100644 crates/xdp/Cargo.toml create mode 100644 crates/xdp/bin/packet-router.bin create mode 100644 crates/xdp/src/lib.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 9dce589bb2..b58bdcfbc2 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,4 +1,4 @@ -[target.'cfg(all())'] +[target.'cfg(target_arch = "x86_64")'] rustflags = [ "-Ctarget-feature=+aes,+avx2", ] diff --git a/.gitignore b/.gitignore index 783b0160b4..29db141fa6 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ quilkin-*.zip # Generated by Cargo # will have compiled files and executables /target/ +crates/ebpf/target/ # These are backup files generated by rustfmt **/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock index 3be76e4ff4..55d1c26b49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,6 +130,12 @@ dependencies = [ "serde", ] +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + [[package]] name = "async-broadcast" version = "0.7.1" @@ -161,7 +167,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -172,7 +178,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -234,6 +240,61 @@ dependencies = [ "tower-service", ] +[[package]] +name = "aya" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d18bc4e506fbb85ab7392ed993a7db4d1a452c71b75a246af4a80ab8c9d2dd50" +dependencies = [ + "assert_matches", + "aya-obj", + "bitflags 2.6.0", + "bytes", + "libc", + "log", + "object 0.36.5", + "once_cell", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "aya-log" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b600d806c1d07d3b81ab5f4a2a95fd80f479a0d3f1d68f29064d660865f85f02" +dependencies = [ + "aya", + "aya-log-common", + "bytes", + "log", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "aya-log-common" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "befef9fe882e63164a2ba0161874e954648a72b0e1c4b361f532d590638c4eec" +dependencies = [ + "num_enum", +] + +[[package]] +name = "aya-obj" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51b96c5a8ed8705b40d655273bc4212cbbf38d4e3be2788f36306f154523ec7" +dependencies = [ + "bytes", + "core-error", + "hashbrown 0.15.1", + "log", + "object 0.36.5", + "thiserror 1.0.69", +] + [[package]] name = "backoff" version = "0.4.0" @@ -256,7 +317,7 @@ dependencies = [ "cfg-if", "libc", "miniz_oxide", - "object", + "object 0.32.2", "rustc-demangle", ] @@ -389,7 +450,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -419,6 +480,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af" +[[package]] +name = "core-error" +version = "0.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efcdb2972eb64230b4c50646d8498ff73f5128d196a90c7236eec4cbe8619b8f" +dependencies = [ + "version_check", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -508,7 +578,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.89", ] [[package]] @@ -519,7 +589,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -591,7 +661,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -616,7 +686,7 @@ checksum = "1b4464d46ce68bfc7cb76389248c7c254def7baca8bece0693b02b83842c4c88" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -634,7 +704,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -652,7 +722,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -672,7 +742,7 @@ checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -692,7 +762,7 @@ checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -704,7 +774,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -897,7 +967,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -1424,7 +1494,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -1737,7 +1807,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn", + "syn 2.0.89", ] [[package]] @@ -1786,9 +1856,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.164" +version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libflate" @@ -1984,6 +2054,31 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" +[[package]] +name = "neli" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1100229e06604150b3becd61a4965d5c70f3be1759544ea7274166f4be41ef43" +dependencies = [ + "byteorder", + "libc", + "log", + "neli-proc-macros", +] + +[[package]] +name = "neli-proc-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c168194d373b1e134786274020dae7fc5513d565ea2ebb9bc9ff17ffb69106d4" +dependencies = [ + "either", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", +] + [[package]] name = "nix" version = "0.27.1" @@ -2058,6 +2153,26 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "object" version = "0.32.2" @@ -2067,6 +2182,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "crc32fast", + "hashbrown 0.15.1", + "indexmap 2.6.0", + "memchr", +] + [[package]] name = "once_cell" version = "1.20.2" @@ -2096,7 +2223,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2208,7 +2335,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2249,7 +2376,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2326,7 +2453,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.89", ] [[package]] @@ -2379,7 +2506,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn", + "syn 2.0.89", "tempfile", ] @@ -2393,7 +2520,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2530,7 +2657,7 @@ version = "0.10.0-dev" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2542,6 +2669,18 @@ dependencies = [ "tonic", ] +[[package]] +name = "quilkin-xdp" +version = "0.1.0" +dependencies = [ + "aya", + "aya-log", + "libc", + "thiserror 2.0.3", + "tracing", + "xdp", +] + [[package]] name = "quilkin-xds" version = "0.10.0-dev" @@ -2882,7 +3021,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn", + "syn 2.0.89", ] [[package]] @@ -2972,7 +3111,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2983,7 +3122,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -3053,7 +3192,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -3191,7 +3330,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 2.0.89", ] [[package]] @@ -3223,6 +3362,17 @@ dependencies = [ "symbolic-common", ] +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.89" @@ -3254,7 +3404,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -3316,7 +3466,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -3327,7 +3477,7 @@ checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -3411,7 +3561,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -3578,7 +3728,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -3663,7 +3813,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -3832,7 +3982,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.89", "wasm-bindgen-shared", ] @@ -3854,7 +4004,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4091,6 +4241,16 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "xdp" +version = "0.0.1" +source = "git+https://github.com/Jake-Shadle/xdp?branch=impl#2c66b1abd3709194aa2a8eb97d02c6434679cacd" +dependencies = [ + "libc", + "memmap2", + "neli", +] + [[package]] name = "xxhash-rust" version = "0.8.12" @@ -4123,7 +4283,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", "synstructure", ] @@ -4145,7 +4305,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -4165,7 +4325,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", "synstructure", ] @@ -4194,5 +4354,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] diff --git a/Cargo.toml b/Cargo.toml index f7c2bd8cdd..faaf0a6d7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,7 +82,7 @@ test = false [dependencies] # Local quilkin-macros = { version = "0.10.0-dev", path = "./crates/macros" } -quilkin-xds = {version = "0.10.0-dev",path = "crates/xds" } +quilkin-xds = {version = "0.10.0-dev", path = "crates/xds" } quilkin-proto.workspace = true # Crates.io @@ -185,7 +185,16 @@ xxhash-rust = { version = "0.8", features = ["xxh3"] } debug = true [workspace] -members = [".", "crates/*"] +members = [ + ".", + "crates/agones", + "crates/macros", + "crates/proto-gen", + "crates/quilkin-proto", + "crates/test", + "crates/xdp", + "crates/xds", +] [workspace.package] license = "Apache-2.0" diff --git a/crates/ebpf/Cargo.lock b/crates/ebpf/Cargo.lock new file mode 100644 index 0000000000..10068ec68d --- /dev/null +++ b/crates/ebpf/Cargo.lock @@ -0,0 +1,189 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aya-ebpf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8dbaf5409a1a0982e5c9bdc0f499a55fe5ead39fe9c846012053faf0d404f73" +dependencies = [ + "aya-ebpf-bindings", + "aya-ebpf-cty", + "aya-ebpf-macros", + "rustversion", +] + +[[package]] +name = "aya-ebpf-bindings" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "783dc1a82a3d71d83286165381dcc1b1d41643f4b110733d135547527c000a9a" +dependencies = [ + "aya-ebpf-cty", +] + +[[package]] +name = "aya-ebpf-cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cce099aaf3abb89f9a1f8594ffe07fa53738ebc2882fac624d10d9ba31a1b10" + +[[package]] +name = "aya-ebpf-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f47f7b4a75eb5f1d7ba0fb5628d247b1cf20388658899177875dabdda66865" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "aya-log-common" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "befef9fe882e63164a2ba0161874e954648a72b0e1c4b361f532d590638c4eec" +dependencies = [ + "num_enum", +] + +[[package]] +name = "aya-log-ebpf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae348f459df78a79e5cd5e164b6562b927033b97ca3b033605b341a474f44510" +dependencies = [ + "aya-ebpf", + "aya-log-common", + "aya-log-ebpf-macros", +] + +[[package]] +name = "aya-log-ebpf-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d8251a75f56077db51892041aa6b77c70ef2723845d7a210979700b2f01bc4" +dependencies = [ + "aya-log-common", + "aya-log-parser", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "aya-log-parser" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b102eb5c88c9aa0b49102d3fbcee08ecb0dfa81014f39b373311de7a7032cb" +dependencies = [ + "aya-log-common", +] + +[[package]] +name = "network-types" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e82e9f64c09f56aa7c80c3fa087997bd99a913f91d9c74d36cf5fd75dd5773e6" + +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[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 = "quilkin-ebpf" +version = "0.1.0" +dependencies = [ + "aya-ebpf", + "aya-log-ebpf", + "network-types", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + +[[package]] +name = "syn" +version = "2.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" diff --git a/crates/ebpf/Cargo.toml b/crates/ebpf/Cargo.toml new file mode 100644 index 0000000000..dd17c429e5 --- /dev/null +++ b/crates/ebpf/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "quilkin-ebpf" +version = "0.1.0" +license = "Apache-2.0" +edition = "2021" + +[dependencies] +aya-ebpf = "0.1.1" +aya-log-ebpf = "0.1.1" +network-types = "0.0.7" + +[[bin]] +name = "packet-router" +path = "src/ebpf-main.rs" + +[profile.dev] +opt-level = 3 +debug = true +debug-assertions = false +overflow-checks = false +lto = true +panic = "abort" +incremental = false +codegen-units = 1 +rpath = false + +[profile.release] +lto = true +panic = "abort" +codegen-units = 1 + +[workspace] diff --git a/crates/ebpf/build.sh b/crates/ebpf/build.sh new file mode 100755 index 0000000000..6a1598420c --- /dev/null +++ b/crates/ebpf/build.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Builds the eBPF program that can be loaded by the kernel. Pass `--update` to +# update the binary embedded in quilkin + +set -e + +ROOT=$(git rev-parse --show-toplevel) +EBPF_ROOT="$ROOT/crates/ebpf" + +cargo +nightly build -Z build-std=core --release --target bpfel-unknown-none --manifest-path "$EBPF_ROOT/Cargo.toml" + +if [[ $1 == '--update' ]]; then + cp "$EBPF_ROOT/target/bpfel-unknown-none/release/packet-router" "$ROOT/crates/xdp/bin/packet-router.bin" +fi diff --git a/crates/ebpf/rust-toolchain.toml b/crates/ebpf/rust-toolchain.toml new file mode 100644 index 0000000000..4df1381de1 --- /dev/null +++ b/crates/ebpf/rust-toolchain.toml @@ -0,0 +1,24 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +[toolchain] +# Compiling eBPF requires nightly since we need to build the core library as it +# is not part of the target distribution +channel = "nightly" +# We need the std (actually core) library component since we need to build it from source +components = ["rust-std"] +# The actual target the `l` means little endian, we don't target big endian +targets = ["bpfel-unknown-none"] +# We don't need documentation +profile = "minimal" diff --git a/crates/ebpf/src/ebpf-main.rs b/crates/ebpf/src/ebpf-main.rs new file mode 100644 index 0000000000..8c0cb1dabe --- /dev/null +++ b/crates/ebpf/src/ebpf-main.rs @@ -0,0 +1,167 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#![no_std] +#![no_main] +#![allow(internal_features)] +#![feature(core_intrinsics)] + +use aya_ebpf::{ + bindings::xdp_action, + macros::{map, xdp}, + maps::XskMap, + programs::XdpContext, +}; +//use aya_log_ebpf::warn; +use network_types::{ + eth::{EthHdr, EtherType}, + ip::{IpProto, Ipv4Hdr, Ipv6Hdr}, + udp::UdpHdr, +}; + +type Action = xdp_action::Type; + +/// Map of sockets that packets can be redirected to +#[map] +static XSK: XskMap = XskMap::with_max_entries(128, 0); + +// Number of sockets in the `XSK` map +#[no_mangle] +static SOCKET_COUNT: u32 = 0; +static mut COUNTER: u32 = 0; + +/// The external port used by clients. Network order. +#[no_mangle] +static EXTERNAL_PORT_NO: u16 = u16::to_be(7777); + +/// The beginning of the port range quilkin will use for server sessions, we +/// take advantage of the fact that, by default, the range Linux uses for +/// assigning ephemeral ports is 32768–60999, so we can easily determine in eBPF +/// if a port is intended for quilkin or not without relying on extra state +const EPHEMERAL_PORT_START: u16 = 61000; + +// eBPF doesn't support 32-bit atomic operations, but AtomicU64 doesn't provide +// fetch_add when targeting eBPF for some reason, so we just roll our own +// struct Atomic(core::cell::UnsafeCell); +// unsafe impl Sync for Atomic {} + +// static COUNTER: Atomic = Atomic(core::cell::UnsafeCell::new(0)); + +#[inline(always)] +fn ptr_at(ctx: &XdpContext, offset: usize) -> Result<*mut T, ()> { + let start = ctx.data(); + let end = ctx.data_end(); + let len = core::mem::size_of::(); + + if start + offset + len > end { + return Err(()); + } + + Ok((start + offset) as *mut T) +} + +pub fn packet_router(ctx: &XdpContext) -> Result<(), ()> { + let eth_hdr = unsafe { &mut *ptr_at::(&ctx, 0)? }; + + // Get the destination UDP port, passing all packets we don't care about + let dest_port = unsafe { + match eth_hdr.ether_type { + EtherType::Ipv4 => { + let ipv4hdr = ptr_at::(&ctx, EthHdr::LEN)?; + let v4hdr = &*ipv4hdr; + + match v4hdr.proto { + IpProto::Udp => { + let udp_hdr = &*ptr_at::(&ctx, EthHdr::LEN + Ipv4Hdr::LEN)?; + udp_hdr.dest + } + _ => { + return Err(()); + } + } + } + EtherType::Ipv6 => { + let ipv6hdr = ptr_at::(&ctx, EthHdr::LEN)?; + let v6hdr = &*ipv6hdr; + + // Note this means that we ignore packets that have extensions + match v6hdr.next_hdr { + IpProto::Udp => { + let udp_hdr = &*ptr_at::(&ctx, EthHdr::LEN + Ipv6Hdr::LEN)?; + udp_hdr.dest + } + _ => { + return Err(()); + } + } + } + _ => { + return Err(()); + } + } + }; + + if dest_port == unsafe { core::ptr::read_volatile(&EXTERNAL_PORT_NO) } + || u16::from_be(dest_port) >= EPHEMERAL_PORT_START + { + Ok(()) + } else { + Err(()) + } +} + +/// The entrypoint used when there is a AF_XDP socket bound to every queue of +/// the NIC this program is attached to +#[xdp] +pub fn all_queues(ctx: XdpContext) -> Action { + if packet_router(&ctx).is_ok() { + let queue_id = unsafe { (*ctx.ctx).rx_queue_index }; + XSK.redirect(queue_id, 0).unwrap_or(xdp_action::XDP_PASS) + } else { + xdp_action::XDP_PASS + } +} + +/// The entrypoint used when the AF_XDP sockets bound do not match the number of +/// available NIC queues +#[xdp] +pub fn round_robin(ctx: XdpContext) -> Action { + if packet_router(&ctx).is_ok() { + // Due to a deficiency in Aya, we can't use an atomic here, even though they + // are supported. I believe this is because of atomics not being relocated + // to a writable section, which is what libbpf does, and should be fixed + // in aya, but we just take the hit for now that we'll get packets assigned + // to the same socket + // unsafe { + // let i = core::intrinsics::atomic_xadd_relaxed(COUNTER.0.get(), 1); + // let index = i % core::ptr::read_volatile(&SOCKET_COUNT); + // XSK.redirect(index as _, 0).map_err(|_| ()) + // } + unsafe { + COUNTER += 1; + let index = COUNTER % core::ptr::read_volatile(&SOCKET_COUNT); + XSK.redirect(index, 0).unwrap_or(xdp_action::XDP_PASS) + } + } else { + xdp_action::XDP_PASS + } +} + +/// We can't panic, but we still need to satisfy the linker +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + unsafe { core::hint::unreachable_unchecked() } +} diff --git a/crates/xdp/Cargo.toml b/crates/xdp/Cargo.toml new file mode 100644 index 0000000000..0d569c1ca1 --- /dev/null +++ b/crates/xdp/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "quilkin-xdp" +version = "0.1.0" +license.workspace = true +edition.workspace = true + +[dependencies] +# Used to load and attach eBPF programs +aya = "0.13.1" +# Allows forwarding trace messages from eBPF programs to a global logger +aya-log = "0.2.1" +libc = "0.2" +thiserror.workspace = true +tracing.workspace = true +xdp = { git = "https://github.com/Jake-Shadle/xdp", branch = "impl" } + +[lints] +workspace = true diff --git a/crates/xdp/bin/packet-router.bin b/crates/xdp/bin/packet-router.bin new file mode 100644 index 0000000000000000000000000000000000000000..32580878ec3a0d6874d4ced855085d7ee063764b GIT binary patch literal 2552 zcmcguO>Yxt5FW?g7$`L0*aB6M*mbDdNVaV380A1QjSNUcc9qyw)I*)My#$Mx#9pTf zQYCU})jxp5Eje=QF}?X%jvP2}ps2^5D)mI93Z?V)+RH*ERH+@wo|$Jp_I>A>b@HIJ zczrAo03!i-1tZU(06&LEc9G2@9LLO9aGbq^ki-Wu(Gw-n?nTjIj8os#!e2*(JQNt~ zkF)hp^@F%1P83CVMS*LHG3tBxM&ChH;?pytujD^qBRmFhgc8QXkT!V}dC42-c_-$? z@76@SRng&~)YXFWJPS^6;J>=Z0d~hDgB_6p+mmvRfu5J{{~ga1L>= zv~%KJxFmrWn06YWi(>(}@dtnfQBJTUgkrsK*$%Yd_ZXvA1C0+Nlnw9;_PdJurv$q& zt$Gdf;s|Z4rn%9?Vu5kiW{wtn*w-_^0_LS=un@)4?Kk_`os;v{tk*|o4xdwgQuf$_ zm%MkT|Bxq2Mj0qyG(YAkDFf<8m8VJN3FFMug4D%=v%*s_B|LfhFi%Nh1E3pKoyl5lyU_;Iy}RwDtaL zm>0*CZPj~P@9+D+@$Q);^PZ$zyo2|s@*Yy&F^zdo7~Y>T?^HWw1J9oJjXt~hd0*MZ z#1Q;B>}Bfq&OQvZqSebrP)Px;N`(-dy% zBTRhPDJ< zcUqg!+t|9-s`oY;?R%8p+4$ChwA=6X?FQ=m9oD^#mfg2uxw3GpRIM*mYUL`FR;s0? z^0meKmzAYzyS7_Q9sDE9~=HAG;%*{$m z*~@n_jd{C}$z}5SdB@JSoKiZY)A{%6I+l*MWz^?)UAd&^Q}JxToaWyJhvr@TA*{J74M6xEvhpQ9v{ FzX1UB%g+D+ literal 0 HcmV?d00001 diff --git a/crates/xdp/src/lib.rs b/crates/xdp/src/lib.rs new file mode 100644 index 0000000000..2d70e40917 --- /dev/null +++ b/crates/xdp/src/lib.rs @@ -0,0 +1,190 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use xdp::nic::NicIndex; + +const PROGRAM: &[u8] = include_bytes!("../bin/packet-router.bin"); + +#[derive(thiserror::Error, Debug)] +pub enum BindError { + #[error("'XSK' map not found in eBPF program")] + MissingXskMap, + #[error("failed to insert socket: {0}")] + Map(#[from] aya::maps::MapError), + #[error("failed to bind socket: {0}")] + Socket(#[from] xdp::socket::SocketError), + #[error("failed to determine queue count for NIC: {0}")] + UnknownQueueCount(#[from] std::io::Error), +} + +#[derive(thiserror::Error, Debug)] +pub enum LoadError { + #[error("eBPF load error")] + Ebpf(#[from] aya::EbpfError), + #[error("failed to read ephemeral port range")] + Io(#[from] std::io::Error), + #[error("the default Linux ephemeral port range 32768..=60999 has been modified to {0}..={1}")] + DefaultPortRangeModified(u16, u16), +} + +pub struct EbpfProgram { + bpf: aya::Ebpf, +} + +impl EbpfProgram { + /// Loads the XDP program. + /// + /// The external port, the port used by clients, must be passed in due to + /// how globals work in eBPF. + pub fn load(external_port: u16) -> Result { + let mut loader = aya::EbpfLoader::new(); + let port = external_port.to_be(); + loader.set_global("EXTERNAL_PORT_NO", &port, true); + + // We exploit the fact that Linux by default does not assign ephemeral + // ports in the full range allowed by IANA, but we want to sanity check + // it here, as otherwise something else could have been assigned an + // ephemeral port that we think we can use, which would lead to both + // quilkin and whatever program was assigned that port misbehaving + let port_range = std::fs::read_to_string("/proc/sys/net/ipv4/ip_local_port_range")?; + let (start, end) = + port_range + .split_once(char::is_whitespace) + .ok_or(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "expected 2 u16 integers", + ))?; + let start: u16 = start.parse().map_err(|_e| { + std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!("failed to parse range start '{start}'"), + ) + })?; + let end: u16 = end.parse().map_err(|_e| { + std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!("failed to parse range end '{end}'"), + ) + })?; + + if end > 60999 { + return Err(LoadError::DefaultPortRangeModified(start, end)); + } + + Ok(Self { + bpf: loader.load(PROGRAM)?, + }) + } + + /// Gets the information for the default NIC + pub fn get_default_nic() -> std::io::Result> { + let table = std::fs::read_to_string("/proc/net/route")?; + + // In most cases there will probably only be one NIC that talks to + // the rest of the network, but just in case, fail if there is + // more than one, so the user is forced to specify. We _could_ go + // further and use netlink to get the route for a global IP eg. 8.8.8.8, + // but the rtnetlink crate is...pretty bad to work with + let mut def_iface = None; + + // skip column headers + for line in table.lines().skip(1) { + let mut iter = line.split(char::is_whitespace).filter_map(|s| { + let s = s.trim(); + (!s.is_empty()).then_some(s) + }); + + let Some(name) = iter.next() else { + continue; + }; + let Some(flags) = iter.nth(2).and_then(|f| u16::from_str_radix(f, 16).ok()) else { + continue; + }; + + if flags & (libc::RTF_UP | libc::RTF_GATEWAY) != libc::RTF_UP | libc::RTF_GATEWAY { + continue; + } + + let Some(iface) = NicIndex::lookup_by_name(name)? else { + continue; + }; + + if let Some(def) = def_iface { + return Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + format!("unable to determine default interface, found {def:?} and {iface:?}"), + )); + } + + def_iface = Some(iface); + } + + Ok(def_iface) + } + + /// Gets the information of the NIC with the specified name + #[inline] + pub fn get_nic(name: &str) -> std::io::Result> { + NicIndex::lookup_by_name(name) + } + + /// Binds the specified sockets and inserts them into the eBPF map + pub fn bind_sockets( + &mut self, + nic: NicIndex, + sb: Vec<(xdp::socket::XdpSocketBuilder, xdp::socket::BindFlags)>, + ) -> Result, BindError> { + use std::os::fd::AsRawFd as _; + { + let q_count = nic.queue_count().map_err(BindError::UnknownQueueCount)?.1; + assert_eq!(sb.len() as u32, q_count, "shared Umem is not supported at the moment, we require there is an AF_XDP socket per NIC queue"); + } + + let mut xsk_map = aya::maps::XskMap::try_from( + self.bpf.map_mut("XSK").expect("failed to retrieve XSK map"), + )?; + + let mut sockets = Vec::with_capacity(sb.len()); + for (i, (sb, bf)) in sb.into_iter().enumerate() { + xsk_map.set(i as _, sb.as_raw_fd(), 0)?; + sockets.push(sb.bind(nic, i as _, bf)?); + } + + Ok(sockets) + } + + pub fn attach( + &mut self, + nic: NicIndex, + flags: aya::programs::XdpFlags, + ) -> Result { + if let Err(error) = aya_log::EbpfLogger::init(&mut self.bpf) { + tracing::warn!(%error, "failed to initialize eBPF logging"); + } + + // We use this entrypoint for now, but in the future we could also use + // a round robin mode when the xdp lib supports shared Umem + let program: &mut aya::programs::Xdp = self + .bpf + .program_mut("all_queues") + .expect("failed to locate 'all_queues' program") + .try_into() + .expect("'all_queues' is not an xdp program"); + program.load()?; + + program.attach_to_if_index(nic.into(), flags) + } +}