From e20698695bf7ed0b29411ec0f1d764c30e2aecc6 Mon Sep 17 00:00:00 2001 From: Harshavardhan Unnibhavi Date: Tue, 8 Jun 2021 07:15:08 +0200 Subject: [PATCH 01/16] vhost-user-vsock: virtio-vsock device emulation This commit introduces a vhost-user-vsock device that enables communicaton between an application running in the guest i.e inside a VM and an application running on the host i.e outside the VM. The device exposes unix sockets to which the VMM and host-side applications connect to. Applications in the guest communicate over VM sockets. Applicaitons on the host connect to the unix socket i.e communicate over AF_UNIX sockets. Signed-off-by: Harshavardhan Unnibhavi [sgarzare: rebased, updated Cargo.lock, updated clap version to avoid build issues, and fixed clap issues with the new version] Signed-off-by: Stefano Garzarella --- Cargo.lock | 328 ++++++++++++++++-- Cargo.toml | 1 + README.md | 1 + vsock/Cargo.toml | 27 ++ vsock/README.md | 108 ++++++ vsock/src/cli.yaml | 36 ++ vsock/src/main.rs | 87 +++++ vsock/src/packet.rs | 594 +++++++++++++++++++++++++++++++++ vsock/src/rxops.rs | 34 ++ vsock/src/rxqueue.rs | 155 +++++++++ vsock/src/thread_backend.rs | 236 +++++++++++++ vsock/src/txbuf.rs | 210 ++++++++++++ vsock/src/vhu_vsock.rs | 346 +++++++++++++++++++ vsock/src/vhu_vsock_thread.rs | 553 +++++++++++++++++++++++++++++++ vsock/src/vsock_conn.rs | 605 ++++++++++++++++++++++++++++++++++ 15 files changed, 3293 insertions(+), 28 deletions(-) create mode 100644 vsock/Cargo.toml create mode 100644 vsock/README.md create mode 100644 vsock/src/cli.yaml create mode 100644 vsock/src/main.rs create mode 100644 vsock/src/packet.rs create mode 100644 vsock/src/rxops.rs create mode 100644 vsock/src/rxqueue.rs create mode 100644 vsock/src/thread_backend.rs create mode 100644 vsock/src/txbuf.rs create mode 100644 vsock/src/vhu_vsock.rs create mode 100644 vsock/src/vhu_vsock_thread.rs create mode 100644 vsock/src/vsock_conn.rs diff --git a/Cargo.lock b/Cargo.lock index 116c1a7..103de3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,12 +28,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "cc" version = "1.0.73" @@ -46,6 +58,22 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "3.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" +dependencies = [ + "atty", + "bitflags", + "clap_lex 0.2.4", + "indexmap", + "strsim", + "termcolor", + "textwrap", + "yaml-rust", +] + [[package]] name = "clap" version = "4.0.11" @@ -55,7 +83,7 @@ dependencies = [ "atty", "bitflags", "clap_derive", - "clap_lex", + "clap_lex 0.3.0", "once_cell", "strsim", "termcolor", @@ -74,6 +102,15 @@ dependencies = [ "syn", ] +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "clap_lex" version = "0.3.0" @@ -115,6 +152,96 @@ dependencies = [ "instant", ] +[[package]] +name = "futures" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" + +[[package]] +name = "futures-executor" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", + "num_cpus", +] + +[[package]] +name = "futures-io" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" + +[[package]] +name = "futures-macro" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" + +[[package]] +name = "futures-task" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" + +[[package]] +name = "futures-util" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "getrandom" version = "0.2.7" @@ -126,6 +253,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "heck" version = "0.4.0" @@ -147,6 +280,16 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "instant" version = "0.1.12" @@ -170,7 +313,7 @@ dependencies = [ "libc", "libgpiod-sys", "thiserror", - "vmm-sys-util", + "vmm-sys-util 0.10.0", ] [[package]] @@ -181,6 +324,12 @@ dependencies = [ "cc", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "log" version = "0.4.17" @@ -196,6 +345,16 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "once_cell" version = "1.15.0" @@ -208,6 +367,18 @@ version = "6.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -321,6 +492,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + [[package]] name = "strsim" version = "0.10.0" @@ -361,6 +541,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textwrap" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" + [[package]] name = "thiserror" version = "1.0.37" @@ -393,6 +579,18 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vhost" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b56bf8f178fc500fe14505fca8b00dec76fc38f2304f461c8d9d7547982311d" +dependencies = [ + "bitflags", + "libc", + "vm-memory 0.9.0", + "vmm-sys-util 0.10.0", +] + [[package]] name = "vhost" version = "0.5.0" @@ -401,50 +599,50 @@ checksum = "79243657c76e5c90dcbf60187c842614f6dfc7123972c55bb3bcc446792aca93" dependencies = [ "bitflags", "libc", - "vm-memory", - "vmm-sys-util", + "vm-memory 0.9.0", + "vmm-sys-util 0.10.0", ] [[package]] name = "vhost-device-gpio" version = "0.1.0" dependencies = [ - "clap", + "clap 4.0.11", "env_logger", "libc", "libgpiod", "log", "thiserror", - "vhost", - "vhost-user-backend", + "vhost 0.5.0", + "vhost-user-backend 0.7.0", "virtio-bindings", - "virtio-queue", - "vm-memory", - "vmm-sys-util", + "virtio-queue 0.6.1", + "vm-memory 0.9.0", + "vmm-sys-util 0.10.0", ] [[package]] name = "vhost-device-i2c" version = "0.1.0" dependencies = [ - "clap", + "clap 4.0.11", "env_logger", "libc", "log", "thiserror", - "vhost", - "vhost-user-backend", + "vhost 0.5.0", + "vhost-user-backend 0.7.0", "virtio-bindings", - "virtio-queue", - "vm-memory", - "vmm-sys-util", + "virtio-queue 0.6.1", + "vm-memory 0.9.0", + "vmm-sys-util 0.10.0", ] [[package]] name = "vhost-device-rng" version = "0.1.0" dependencies = [ - "clap", + "clap 4.0.11", "env_logger", "epoll", "libc", @@ -452,12 +650,27 @@ dependencies = [ "rand", "tempfile", "thiserror", - "vhost", - "vhost-user-backend", + "vhost 0.5.0", + "vhost-user-backend 0.7.0", "virtio-bindings", - "virtio-queue", - "vm-memory", - "vmm-sys-util", + "virtio-queue 0.6.1", + "vm-memory 0.9.0", + "vmm-sys-util 0.10.0", +] + +[[package]] +name = "vhost-user-backend" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8db00e93514caa8987bb8b536fe962c9b66b4068583abc4c531eb97988477cd" +dependencies = [ + "libc", + "log", + "vhost 0.3.0", + "virtio-bindings", + "virtio-queue 0.1.0", + "vm-memory 0.7.0", + "vmm-sys-util 0.9.0", ] [[package]] @@ -468,11 +681,29 @@ checksum = "6a0fc7d5f8e2943cd9f2ecd58be3f2078add863a49573d14dd9d64e1ab26544c" dependencies = [ "libc", "log", - "vhost", + "vhost 0.5.0", "virtio-bindings", - "virtio-queue", - "vm-memory", - "vmm-sys-util", + "virtio-queue 0.6.1", + "vm-memory 0.9.0", + "vmm-sys-util 0.10.0", +] + +[[package]] +name = "vhost-user-vsock" +version = "0.1.0" +dependencies = [ + "byteorder", + "clap 3.2.22", + "epoll", + "futures", + "log", + "thiserror", + "vhost 0.3.0", + "vhost-user-backend 0.1.0", + "virtio-bindings", + "virtio-queue 0.1.0", + "vm-memory 0.7.0", + "vmm-sys-util 0.9.0", ] [[package]] @@ -481,6 +712,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ff512178285488516ed85f15b5d0113a7cdb89e9e8a760b269ae4f02b84bd6b" +[[package]] +name = "virtio-queue" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90da9e627f6aaf667cc7b6548a28be332d3e1f058f4ceeb46ab6bcee5c4b74d" +dependencies = [ + "log", + "vm-memory 0.7.0", + "vmm-sys-util 0.10.0", +] + [[package]] name = "virtio-queue" version = "0.6.1" @@ -489,8 +731,19 @@ checksum = "435dd49c7b38419729afd43675850c7b5dc4728f2fabd70c7a9079a331e4f8c6" dependencies = [ "log", "virtio-bindings", - "vm-memory", - "vmm-sys-util", + "vm-memory 0.9.0", + "vmm-sys-util 0.10.0", +] + +[[package]] +name = "vm-memory" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339d4349c126fdcd87e034631d7274370cf19eb0e87b33166bcd956589fc72c5" +dependencies = [ + "arc-swap", + "libc", + "winapi", ] [[package]] @@ -504,6 +757,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "vmm-sys-util" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733537bded03aaa93543f785ae997727b30d1d9f4a03b7861d23290474242e11" +dependencies = [ + "bitflags", + "libc", +] + [[package]] name = "vmm-sys-util" version = "0.10.0" @@ -550,3 +813,12 @@ 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 = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/Cargo.toml b/Cargo.toml index faee2f0..258a969 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,5 @@ members = [ "gpio", "i2c", "rng", + "vsock", ] diff --git a/README.md b/README.md index 1c59c98..aa4315b 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Here is the list of device backends that we support: - [GPIO](https://github.com/rust-vmm/vhost-device/blob/main/gpio/README.md) - [I2C](https://github.com/rust-vmm/vhost-device/blob/main/i2c/README.md) - [RNG](https://github.com/rust-vmm/vhost-device/blob/main/rng/README.md) +- [VSOCK](https://github.com/rust-vmm/vhost-device/blob/main/vsock/README.md) ## Testing and Code Coverage diff --git a/vsock/Cargo.toml b/vsock/Cargo.toml new file mode 100644 index 0000000..54b488b --- /dev/null +++ b/vsock/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "vhost-user-vsock" +version = "0.1.0" +authors = ["Harshavardhan Unnibhavi "] +description = "A virtio-vsock device using the vhost-user protocol." +repository = "https://github.com/rust-vmm/vhost-device" +readme = "README.md" +keywords = ["vhost", "vsock"] +license = "Apache-2.0 OR BSD-3-Clause" +edition = "2018" + +[dependencies] +byteorder = "1" +clap = { version = ">=3.0", features = ["yaml"] } +epoll = "4.3.1" +futures = { version = "0.3", features = ["thread-pool"] } +log = "0.4.14" +thiserror = "1.0" +vhost = { version = "0.3", features = ["vhost-user-slave"] } +vhost-user-backend = "0.1" +virtio-bindings = ">=0.1" +virtio-queue = "0.1" +vm-memory = "0.7" +vmm-sys-util = "=0.9.0" + +[dev-dependencies] +virtio-queue = { version = "0.1", features = ["test-utils"] } diff --git a/vsock/README.md b/vsock/README.md new file mode 100644 index 0000000..2847b65 --- /dev/null +++ b/vsock/README.md @@ -0,0 +1,108 @@ +# vhost-user-vsock + +## Design + +The crate introduces a vhost-user-vsock device that enables communication between an +application running in the guest i.e inside a VM and an application running on the +host i.e outside the VM. The application running in the guest communicates over VM +sockets i.e over AF_VSOCK sockets. The application running on the host connects to a +unix socket on the host i.e communicates over AF_UNIX sockets. The main components of +the crate are split into various files as described below: + +- [packet.rs](src/packet.rs) + - Introduces the **VsockPacket** structure that represents a single vsock packet + processing methods. +- [rxops.rs](src/rxops.rs) + - Introduces various vsock operations that are enqueued into the rxqueue to be sent to the + guest. Exposes a **RxOps** structure. +- [rxqueue.rs](src/rxqueue.rs) + - rxqueue contains the pending rx operations corresponding to that connection. The queue is + represented as a bitmap as we handle connection-oriented connections. The module contains + various queue manipulation methods. Exposes a **RxQueue** structure. +- [thread_backend.rs](src/thread_backend.rs) + - Multiplexes connections between host and guest and calls into per connection methods that + are responsible for processing data and packets corresponding to the connection. Exposes a + **VsockThreadBackend** structure. +- [txbuf.rs](src/txbuf.rs) + - Module to buffer data that is sent from the guest to the host. The module exposes a **LocalTxBuf** + structure. +- [vhost_user_vsock_thread.rs](src/vhost_user_vsock_thread.rs) + - Module exposes a **VhostUserVsockThread** structure. It also handles new host initiated + connections and provides interfaces for registering host connections with the epoll fd. Also + provides interfaces for iterating through the rx and tx queues. +- [vsock_conn.rs](src/vsock_conn.rs) + - Module introduces a **VsockConnection** structure that represents a single vsock connection + between the guest and the host. It also processes packets according to their type. +- [vhu_vsock.rs](src/lib.rs) + - exposes the main vhost user vsock backend interface. + +## Usage + +Run the vhost-user-vsock device: +``` +vhost-user-vsock --guest-cid=4 --uds-path=/tmp/vm4.vsock --socket=/tmp/vhost4.socket +``` + +Run qemu: + +``` +qemu-system-x86_64 -drive file=/path/to/disk.qcow2 -enable-kvm -m 512M \ + -smp 2 -vga virtio -chardev socket,id=char0,reconnect=0,path=/tmp/vhost4.socket \ + -device vhost-user-vsock-pci,chardev=char0 \ + -object memory-backend-file,share=on,id=mem,size="512M",mem-path="/dev/hugepages" \ + -numa node,memdev=mem -mem-prealloc +``` + +### Guest listening + +#### iperf + +```sh +# https://github.com/stefano-garzarella/iperf-vsock +guest$ iperf3 --vsock -s +host$ iperf3 --vsock -c /tmp/vm4.vsock +``` + +#### netcat + +```sh +guest$ nc --vsock -l 1234 + +host$ nc -U /tmp/vm4.vsock +CONNECT 1234 +``` + +### Host listening + +#### iperf + +```sh +# https://github.com/stefano-garzarella/iperf-vsock +host$ iperf3 --vsock -s -B /tmp/vm4.vsock +guest$ iperf3 --vsock -c 2 +``` + +#### netcat + +```sh +host$ nc -l -U /tmp/vm4.vsock_1234 + +guest$ nc --vsock 2 1234 +``` + +```rust +use my_crate; + +... +``` + +## License + +**!!!NOTICE**: The BSD-3-Clause license is not included in this template. +The license needs to be manually added because the text of the license file +also includes the copyright. The copyright can be different for different +crates. If the crate contains code from CrosVM, the crate must add the +CrosVM copyright which can be found +[here](https://chromium.googlesource.com/chromiumos/platform/crosvm/+/master/LICENSE). +For crates developed from scratch, the copyright is different and depends on +the contributors. diff --git a/vsock/src/cli.yaml b/vsock/src/cli.yaml new file mode 100644 index 0000000..ef172a3 --- /dev/null +++ b/vsock/src/cli.yaml @@ -0,0 +1,36 @@ +name: vhost-device-vsock +version: "0.1.0" +author: "Harshavardhan Unnibhavi " +about: Virtio VSOCK backend daemon. + +settings: + - ArgRequiredElseHelp + +args: + # CID of the guest + - guest_cid: + long: guest-cid + value_name: INT + takes_value: true + help: Context identifier of the guest which uniquely identifies the device for its lifetime. Defaults to 3 if not specified. + # Socket paths + - uds_path: + long: uds-path + value_name: FILE + takes_value: true + help: Unix socket to which a host-side application connects to. + - socket: + long: socket + value_name: FILE + takes_value: true + help: Unix socket to which a hypervisor conencts to and sets up the control path with the device. + +groups: + - required_args: + args: + - guest_cid + args: + - uds_path + args: + - socket + required: true diff --git a/vsock/src/main.rs b/vsock/src/main.rs new file mode 100644 index 0000000..cc3b8b1 --- /dev/null +++ b/vsock/src/main.rs @@ -0,0 +1,87 @@ +mod packet; +mod rxops; +mod rxqueue; +mod thread_backend; +mod txbuf; +mod vhu_vsock; +mod vhu_vsock_thread; +mod vsock_conn; + +use clap::{load_yaml, App}; +use std::{ + convert::TryFrom, + process, + sync::{Arc, RwLock}, +}; +use vhost::{vhost_user, vhost_user::Listener}; +use vhost_user_backend::VhostUserDaemon; +use vhu_vsock::{VhostUserVsockBackend, VsockConfig}; +use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; + +/// This is the public API through which an external program starts the +/// vhost-user-vsock backend server. +pub(crate) fn start_backend_server(vsock_config: VsockConfig) { + loop { + let vsock_backend = Arc::new(RwLock::new( + VhostUserVsockBackend::new(vsock_config.clone()).unwrap(), + )); + + let listener = Listener::new(vsock_config.get_socket_path(), true).unwrap(); + + let mut vsock_daemon = VhostUserDaemon::new( + String::from("vhost-user-vsock"), + vsock_backend.clone(), + GuestMemoryAtomic::new(GuestMemoryMmap::new()), + ) + .unwrap(); + + let mut vring_workers = vsock_daemon.get_epoll_handlers(); + + if vring_workers.len() != vsock_backend.read().unwrap().threads.len() { + println!("Number of vring workers must be identical to number of backend threads"); + } + + for thread in vsock_backend.read().unwrap().threads.iter() { + thread + .lock() + .unwrap() + .set_vring_worker(Some(vring_workers.remove(0))); + } + if let Err(e) = vsock_daemon.start(listener) { + dbg!("Failed to start vsock daemon: {:?}", e); + process::exit(1); + } + + match vsock_daemon.wait() { + Ok(()) => { + println!("Stopping cleanly"); + process::exit(0); + } + Err(vhost_user_backend::Error::HandleRequest(vhost_user::Error::PartialMessage)) => { + println!("vhost-user connection closed with partial message. If the VM is shutting down, this is expected behavior; otherwise, it might be a bug."); + continue; + } + Err(e) => { + println!("Error running daemon: {:?}", e); + } + } + + vsock_backend + .read() + .unwrap() + .exit_event + .write(1) + .expect("Shutting down worker thread"); + + println!("Vsock daemon is finished"); + } +} + +fn main() { + let yaml = load_yaml!("cli.yaml"); + let vsock_args = App::from_yaml(yaml).get_matches(); + + let vsock_config = VsockConfig::try_from(vsock_args).unwrap(); + + start_backend_server(vsock_config); +} diff --git a/vsock/src/packet.rs b/vsock/src/packet.rs new file mode 100644 index 0000000..a321faf --- /dev/null +++ b/vsock/src/packet.rs @@ -0,0 +1,594 @@ +#![deny(missing_docs)] +use byteorder::{ByteOrder, LittleEndian}; +use thiserror::Error as ThisError; +use virtio_queue::DescriptorChain; +use vm_memory::{ + GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, GuestMemoryLoadGuard, + GuestMemoryMmap, +}; + +pub(crate) type Result = std::result::Result; + +/// Below enum defines custom error types for vsock packet operations. +#[derive(Debug, PartialEq, ThisError)] +pub(crate) enum Error { + #[error("Descriptor not writable")] + UnwritableDescriptor, + #[error("Missing descriptor in queue")] + QueueMissingDescriptor, + #[error("Small header descriptor: {0}")] + HdrDescTooSmall(u32), + #[error("Chained guest memory error")] + GuestMemory, + #[error("Descriptor not readable")] + UnreadableDescriptor, + #[error("Extra descriptors in the descriptor chain")] + ExtraDescrInChain, + #[error("Data buffer size less than size in packet header")] + DataDescTooSmall, +} + +// TODO: Replace below with bindgen generated struct +// vsock packet header size when packed +pub const VSOCK_PKT_HDR_SIZE: usize = 44; + +// Offset into header for source cid +const HDROFF_SRC_CID: usize = 0; + +// Offset into header for destination cid +const HDROFF_DST_CID: usize = 8; + +// Offset into header for source port +const HDROFF_SRC_PORT: usize = 16; + +// Offset into header for destination port +const HDROFF_DST_PORT: usize = 20; + +// Offset into the header for data length +const HDROFF_LEN: usize = 24; + +// Offset into header for packet type +const HDROFF_TYPE: usize = 28; + +// Offset into header for operation kind +const HDROFF_OP: usize = 30; + +// Offset into header for additional flags +// only for VSOCK_OP_SHUTDOWN +const HDROFF_FLAGS: usize = 32; + +// Offset into header for tx buf alloc +const HDROFF_BUF_ALLOC: usize = 36; + +// Offset into header for forward count +const HDROFF_FWD_CNT: usize = 40; + +/// Vsock packet structure implemented as a wrapper around a virtq descriptor chain: +/// - chain head holds the packet header +/// - optional data descriptor, only present for data packets (VSOCK_OP_RW) +#[derive(Debug)] +pub struct VsockPacket { + hdr: *mut u8, + buf: Option<*mut u8>, + buf_size: usize, +} + +impl VsockPacket { + /// Create a vsock packet wrapper around a chain in the rx virtqueue. + /// Perform bounds checking before creating the wrapper. + pub(crate) fn from_rx_virtq_head( + chain: &mut DescriptorChain>>, + mem: GuestMemoryAtomic, + ) -> Result { + // head is at 0, next is at 1, max of two descriptors + // head contains the packet header + // next contains the optional packet data + let mut descr_vec = Vec::with_capacity(2); + + for descr in chain { + if !descr.is_write_only() { + return Err(Error::UnwritableDescriptor); + } + + descr_vec.push(descr); + } + + if descr_vec.len() < 2 { + // We expect a head and a data descriptor + return Err(Error::QueueMissingDescriptor); + } + + let head_descr = descr_vec[0]; + let data_descr = descr_vec[1]; + + if head_descr.len() < VSOCK_PKT_HDR_SIZE as u32 { + return Err(Error::HdrDescTooSmall(head_descr.len())); + } + + Ok(Self { + hdr: VsockPacket::guest_to_host_address( + &mem.memory(), + head_descr.addr(), + VSOCK_PKT_HDR_SIZE, + ) + .ok_or(Error::GuestMemory)? as *mut u8, + buf: Some( + VsockPacket::guest_to_host_address( + &mem.memory(), + data_descr.addr(), + data_descr.len() as usize, + ) + .ok_or(Error::GuestMemory)? as *mut u8, + ), + buf_size: data_descr.len() as usize, + }) + } + + /// Create a vsock packet wrapper around a chain in the tx virtqueue + /// Bounds checking before creating the wrapper. + pub(crate) fn from_tx_virtq_head( + chain: &mut DescriptorChain>>, + mem: GuestMemoryAtomic, + ) -> Result { + // head is at 0, next is at 1, max of two descriptors + // head contains the packet header + // next contains the optional packet data + let mut descr_vec = Vec::with_capacity(2); + // let mut num_descr = 0; + + for descr in chain { + if descr.is_write_only() { + return Err(Error::UnreadableDescriptor); + } + + descr_vec.push(descr); + } + + if descr_vec.len() > 2 { + return Err(Error::ExtraDescrInChain); + } + + let head_descr = descr_vec[0]; + + if head_descr.len() < VSOCK_PKT_HDR_SIZE as u32 { + return Err(Error::HdrDescTooSmall(head_descr.len())); + } + + let mut pkt = Self { + hdr: VsockPacket::guest_to_host_address( + &mem.memory(), + head_descr.addr(), + VSOCK_PKT_HDR_SIZE, + ) + .ok_or(Error::GuestMemory)? as *mut u8, + buf: None, + buf_size: 0, + }; + + // Zero length packet + if pkt.is_empty() { + return Ok(pkt); + } + + // There exists packet data as well + let data_descr = descr_vec[1]; + + // Data buffer should be as large as described in the header + if data_descr.len() < pkt.len() { + return Err(Error::DataDescTooSmall); + } + + pkt.buf_size = data_descr.len() as usize; + pkt.buf = Some( + VsockPacket::guest_to_host_address( + &mem.memory(), + data_descr.addr(), + data_descr.len() as usize, + ) + .ok_or(Error::GuestMemory)? as *mut u8, + ); + + Ok(pkt) + } + + /// Convert an absolute address in guest address space to a host + /// pointer and verify that the provided size defines a valid + /// range within a single memory region. + fn guest_to_host_address( + mem: &GuestMemoryLoadGuard, + addr: GuestAddress, + size: usize, + ) -> Option<*mut u8> { + if mem.check_range(addr, size) { + Some(mem.get_host_address(addr).unwrap()) + } else { + None + } + } + + /// In place byte slice access to vsock packet header. + pub fn hdr(&self) -> &[u8] { + // Safe as bound checks performed in from_*_virtq_head + unsafe { std::slice::from_raw_parts(self.hdr as *const u8, VSOCK_PKT_HDR_SIZE) } + } + + /// In place mutable slice access to vsock packet header. + pub fn hdr_mut(&mut self) -> &mut [u8] { + // Safe as bound checks performed in from_*_virtq_head + unsafe { std::slice::from_raw_parts_mut(self.hdr, VSOCK_PKT_HDR_SIZE) } + } + + /// Size of vsock packet data, found by accessing len field + /// of virtio_vsock_hdr struct. + pub fn len(&self) -> u32 { + LittleEndian::read_u32(&self.hdr()[HDROFF_LEN..]) + } + + /// Set the source cid. + pub fn set_src_cid(&mut self, cid: u64) -> &mut Self { + LittleEndian::write_u64(&mut self.hdr_mut()[HDROFF_SRC_CID..], cid); + self + } + + /// Set the destination cid. + pub fn set_dst_cid(&mut self, cid: u64) -> &mut Self { + LittleEndian::write_u64(&mut self.hdr_mut()[HDROFF_DST_CID..], cid); + self + } + + /// Set source port. + pub fn set_src_port(&mut self, port: u32) -> &mut Self { + LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_SRC_PORT..], port); + self + } + + /// Set destination port. + pub fn set_dst_port(&mut self, port: u32) -> &mut Self { + LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_DST_PORT..], port); + self + } + + /// Set type of connection. + pub fn set_type(&mut self, type_: u16) -> &mut Self { + LittleEndian::write_u16(&mut self.hdr_mut()[HDROFF_TYPE..], type_); + self + } + + /// Set size of tx buf. + pub fn set_buf_alloc(&mut self, buf_alloc: u32) -> &mut Self { + LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_BUF_ALLOC..], buf_alloc); + self + } + + /// Set amount of tx buf data written to stream. + pub fn set_fwd_cnt(&mut self, fwd_cnt: u32) -> &mut Self { + LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_FWD_CNT..], fwd_cnt); + self + } + + /// Set packet operation ID. + pub fn set_op(&mut self, op: u16) -> &mut Self { + LittleEndian::write_u16(&mut self.hdr_mut()[HDROFF_OP..], op); + self + } + + /// Check if the packet has no data. + fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Get destination port from packet. + pub fn dst_port(&self) -> u32 { + LittleEndian::read_u32(&self.hdr()[HDROFF_DST_PORT..]) + } + + /// Get source port from packet. + pub fn src_port(&self) -> u32 { + LittleEndian::read_u32(&self.hdr()[HDROFF_SRC_PORT..]) + } + + /// Get source cid from packet. + pub fn src_cid(&self) -> u64 { + LittleEndian::read_u64(&self.hdr()[HDROFF_SRC_CID..]) + } + + /// Get destination cid from packet. + pub fn dst_cid(&self) -> u64 { + LittleEndian::read_u64(&self.hdr()[HDROFF_DST_CID..]) + } + + /// Get packet type. + pub fn pkt_type(&self) -> u16 { + LittleEndian::read_u16(&self.hdr()[HDROFF_TYPE..]) + } + + /// Get operation requested in the packet. + pub fn op(&self) -> u16 { + LittleEndian::read_u16(&self.hdr()[HDROFF_OP..]) + } + + /// Byte slice mutable access to vsock packet data buffer. + pub fn buf_mut(&mut self) -> Option<&mut [u8]> { + // Safe as bound checks performed while creating packet + self.buf + .map(|ptr| unsafe { std::slice::from_raw_parts_mut(ptr, self.buf_size) }) + } + + /// Byte slice access to vsock packet data buffer. + pub fn buf(&self) -> Option<&[u8]> { + // Safe as bound checks performed while creating packet + self.buf + .map(|ptr| unsafe { std::slice::from_raw_parts(ptr as *const u8, self.buf_size) }) + } + + /// Set data buffer length. + pub fn set_len(&mut self, len: u32) -> &mut Self { + LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_LEN..], len); + self + } + + /// Read buf alloc. + pub fn buf_alloc(&self) -> u32 { + LittleEndian::read_u32(&self.hdr()[HDROFF_BUF_ALLOC..]) + } + + /// Get fwd cnt from packet header. + pub fn fwd_cnt(&self) -> u32 { + LittleEndian::read_u32(&self.hdr()[HDROFF_FWD_CNT..]) + } + + /// Read flags from the packet header. + pub fn flags(&self) -> u32 { + LittleEndian::read_u32(&self.hdr()[HDROFF_FLAGS..]) + } + + /// Set packet header flag to flags. + pub fn set_flags(&mut self, flags: u32) -> &mut Self { + LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_FLAGS..], flags); + self + } + + /// Set OP specific flags. + pub fn set_flag(&mut self, flag: u32) -> &mut Self { + self.set_flags(self.flags() | flag); + self + } +} + +#[cfg(test)] +pub mod tests { + use crate::vhu_vsock::{VSOCK_OP_RW, VSOCK_TYPE_STREAM}; + + use super::*; + use virtio_queue::{ + defs::{VIRTQ_DESC_F_NEXT, VIRTQ_DESC_F_WRITE}, + mock::MockSplitQueue, + Descriptor, + }; + use vm_memory::{Address, Bytes, GuestAddress, GuestMemoryAtomic, GuestMemoryMmap}; + + pub struct HeadParams { + head_len: usize, + data_len: u32, + } + + impl HeadParams { + pub fn new(head_len: usize, data_len: u32) -> Self { + Self { head_len, data_len } + } + fn construct_head(&self) -> Vec { + let mut header = vec![0_u8; self.head_len]; + if self.head_len == VSOCK_PKT_HDR_SIZE { + LittleEndian::write_u32(&mut header[HDROFF_LEN..], self.data_len); + } + header + } + } + + pub fn prepare_desc_chain_vsock( + write_only: bool, + head_params: &HeadParams, + data_chain_len: u16, + head_data_len: u32, + ) -> ( + GuestMemoryAtomic, + DescriptorChain>, + ) { + let mem = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(); + let virt_queue = MockSplitQueue::new(&mem, 16); + let mut next_addr = virt_queue.desc_table().total_size() + 0x100; + let mut flags = 0; + let mut head_flags; + + if write_only { + flags |= VIRTQ_DESC_F_WRITE; + } + + if data_chain_len > 0 { + head_flags = flags | VIRTQ_DESC_F_NEXT + } else { + head_flags = flags; + } + + // vsock packet header + // let header = vec![0 as u8; head_params.head_len]; + let header = head_params.construct_head(); + let head_desc = Descriptor::new(next_addr, head_params.head_len as u32, head_flags, 1); + mem.write(&header, head_desc.addr()).unwrap(); + virt_queue.desc_table().store(0, head_desc); + next_addr += head_params.head_len as u64; + + // Put the descriptor index 0 in the first available ring position. + mem.write_obj(0u16, virt_queue.avail_addr().unchecked_add(4)) + .unwrap(); + + // Set `avail_idx` to 1. + mem.write_obj(1u16, virt_queue.avail_addr().unchecked_add(2)) + .unwrap(); + + // chain len excludes the head + for i in 0..(data_chain_len) { + // last descr in chain + if i == data_chain_len - 1 { + head_flags &= !VIRTQ_DESC_F_NEXT; + } + // vsock data + let data = vec![0_u8; head_data_len as usize]; + let data_desc = Descriptor::new(next_addr, data.len() as u32, head_flags, i + 2); + mem.write(&data, data_desc.addr()).unwrap(); + virt_queue.desc_table().store(i + 1, data_desc); + next_addr += head_data_len as u64; + } + + // Create descriptor chain from pre-filled memory + ( + GuestMemoryAtomic::new(mem.clone()), + virt_queue + .create_queue(GuestMemoryAtomic::::new(mem.clone())) + .iter() + .unwrap() + .next() + .unwrap(), + ) + } + + #[test] + fn test_guest_to_host_address() { + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 1000)]).unwrap(), + ); + assert!(VsockPacket::guest_to_host_address(&mem.memory(), GuestAddress(0), 1000).is_some()); + assert!(VsockPacket::guest_to_host_address(&mem.memory(), GuestAddress(0), 500).is_some()); + assert!( + VsockPacket::guest_to_host_address(&mem.memory(), GuestAddress(500), 500).is_some() + ); + assert!( + VsockPacket::guest_to_host_address(&mem.memory(), GuestAddress(501), 500).is_none() + ); + } + + #[test] + fn test_from_rx_virtq_head() { + // parameters for packet head construction + let head_params = HeadParams::new(VSOCK_PKT_HDR_SIZE, 10); + + // write only descriptor chain + let (mem, mut descr_chain) = prepare_desc_chain_vsock(true, &head_params, 2, 10); + assert!(VsockPacket::from_rx_virtq_head(&mut descr_chain, mem).is_ok()); + + // read only descriptor chain + let (mem, mut descr_chain) = prepare_desc_chain_vsock(false, &head_params, 1, 10); + assert_eq!( + VsockPacket::from_rx_virtq_head(&mut descr_chain, mem).unwrap_err(), + Error::UnwritableDescriptor + ); + + // less than two descriptors + let (mem, mut descr_chain) = prepare_desc_chain_vsock(true, &head_params, 0, 10); + assert_eq!( + VsockPacket::from_rx_virtq_head(&mut descr_chain, mem).unwrap_err(), + Error::QueueMissingDescriptor + ); + + // incorrect header length + let head_params = HeadParams::new(22, 10); + let (mem, mut descr_chain) = prepare_desc_chain_vsock(true, &head_params, 1, 10); + assert_eq!( + VsockPacket::from_rx_virtq_head(&mut descr_chain, mem).unwrap_err(), + Error::HdrDescTooSmall(22) + ); + } + + #[test] + fn test_vsock_packet_header_ops() { + // parameters for head construction + let head_params = HeadParams::new(VSOCK_PKT_HDR_SIZE, 10); + + let (mem, mut descr_chain) = prepare_desc_chain_vsock(true, &head_params, 2, 10); + let mut vsock_packet = VsockPacket::from_rx_virtq_head(&mut descr_chain, mem).unwrap(); + + // Check packet data's length + assert!(!vsock_packet.is_empty()); + assert_eq!(vsock_packet.len(), 10); + + // Set and get the source CID in the packet header + vsock_packet.set_src_cid(1); + assert_eq!(vsock_packet.src_cid(), 1); + + // Set and get the destination CID in the packet header + vsock_packet.set_dst_cid(1); + assert_eq!(vsock_packet.dst_cid(), 1); + + // Set and get the source port in the packet header + vsock_packet.set_src_port(5000); + assert_eq!(vsock_packet.src_port(), 5000); + + // Set and get the destination port in the packet header + vsock_packet.set_dst_port(5000); + assert_eq!(vsock_packet.dst_port(), 5000); + + // Set and get packet type + vsock_packet.set_type(VSOCK_TYPE_STREAM); + assert_eq!(vsock_packet.pkt_type(), VSOCK_TYPE_STREAM); + + // Set and get tx buffer size + vsock_packet.set_buf_alloc(10); + assert_eq!(vsock_packet.buf_alloc(), 10); + + // Set and get fwd_cnt of packet's data + vsock_packet.set_fwd_cnt(100); + assert_eq!(vsock_packet.fwd_cnt(), 100); + + // Set and get packet operation type + vsock_packet.set_op(VSOCK_OP_RW); + assert_eq!(vsock_packet.op(), VSOCK_OP_RW); + + // Set and get length of packet's data buffer + // this is a dummy test + vsock_packet.set_len(20); + assert_eq!(vsock_packet.len(), 20); + assert!(!vsock_packet.is_empty()); + + // Set and get packet's flags + vsock_packet.set_flags(1); + assert_eq!(vsock_packet.flags(), 1); + } + + #[test] + fn test_from_tx_virtq_head() { + // parameters for head construction + let head_params = HeadParams::new(VSOCK_PKT_HDR_SIZE, 0); + + // read only descriptor chain no data + let (mem, mut descr_chain) = prepare_desc_chain_vsock(false, &head_params, 0, 0); + assert!(VsockPacket::from_tx_virtq_head(&mut descr_chain, mem).is_ok()); + + // parameters for head construction + let head_params = HeadParams::new(VSOCK_PKT_HDR_SIZE, 10); + + // read only descriptor chain + let (mem, mut descr_chain) = prepare_desc_chain_vsock(false, &head_params, 1, 10); + assert!(VsockPacket::from_tx_virtq_head(&mut descr_chain, mem).is_ok()); + + // write only descriptor chain + let (mem, mut descr_chain) = prepare_desc_chain_vsock(true, &head_params, 1, 10); + assert_eq!( + VsockPacket::from_tx_virtq_head(&mut descr_chain, mem).unwrap_err(), + Error::UnreadableDescriptor + ); + + // more than 2 descriptors in chain + let (mem, mut descr_chain) = prepare_desc_chain_vsock(false, &head_params, 2, 10); + assert_eq!( + VsockPacket::from_tx_virtq_head(&mut descr_chain, mem).unwrap_err(), + Error::ExtraDescrInChain + ); + + // length of data descriptor does not match the value in head + let (mem, mut descr_chain) = prepare_desc_chain_vsock(false, &head_params, 1, 5); + assert_eq!( + VsockPacket::from_tx_virtq_head(&mut descr_chain, mem).unwrap_err(), + Error::DataDescTooSmall + ); + } +} diff --git a/vsock/src/rxops.rs b/vsock/src/rxops.rs new file mode 100644 index 0000000..b79e62b --- /dev/null +++ b/vsock/src/rxops.rs @@ -0,0 +1,34 @@ +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum RxOps { + /// VSOCK_OP_REQUEST + Request = 0, + /// VSOCK_OP_RW + Rw = 1, + /// VSOCK_OP_RESPONSE + Response = 2, + /// VSOCK_OP_CREDIT_UPDATE + CreditUpdate = 3, + /// VSOCK_OP_RST + Reset = 4, +} + +impl RxOps { + /// Convert enum value into bitmask. + pub fn bitmask(self) -> u8 { + 1u8 << (self as u8) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_bitmask() { + assert_eq!(1, RxOps::Request.bitmask()); + assert_eq!(2, RxOps::Rw.bitmask()); + assert_eq!(4, RxOps::Response.bitmask()); + assert_eq!(8, RxOps::CreditUpdate.bitmask()); + assert_eq!(16, RxOps::Reset.bitmask()); + } +} diff --git a/vsock/src/rxqueue.rs b/vsock/src/rxqueue.rs new file mode 100644 index 0000000..9b033b9 --- /dev/null +++ b/vsock/src/rxqueue.rs @@ -0,0 +1,155 @@ +use super::rxops::RxOps; + +#[derive(Debug, PartialEq)] +pub struct RxQueue { + /// Bitmap of rx operations. + queue: u8, +} + +impl RxQueue { + /// New instance of RxQueue. + pub fn new() -> Self { + RxQueue { queue: 0_u8 } + } + + /// Enqueue a new rx operation into the queue. + pub fn enqueue(&mut self, op: RxOps) { + self.queue |= op.bitmask(); + } + + /// Dequeue an rx operation from the queue. + pub fn dequeue(&mut self) -> Option { + match self.peek() { + Some(req) => { + self.queue &= !req.bitmask(); + Some(req) + } + None => None, + } + } + + /// Peek into the queue to check if it contains an rx operation. + pub fn peek(&self) -> Option { + if self.contains(RxOps::Request.bitmask()) { + return Some(RxOps::Request); + } + if self.contains(RxOps::Rw.bitmask()) { + return Some(RxOps::Rw); + } + if self.contains(RxOps::Response.bitmask()) { + return Some(RxOps::Response); + } + if self.contains(RxOps::CreditUpdate.bitmask()) { + return Some(RxOps::CreditUpdate); + } + if self.contains(RxOps::Reset.bitmask()) { + Some(RxOps::Reset) + } else { + None + } + } + + /// Check if the queue contains a particular rx operation. + pub fn contains(&self, op: u8) -> bool { + (self.queue & op) != 0 + } + + /// Check if there are any pending rx operations in the queue. + pub fn pending_rx(&self) -> bool { + self.queue != 0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_contains() { + let mut rxqueue = RxQueue::new(); + rxqueue.queue = 31; + + assert!(rxqueue.contains(RxOps::Request.bitmask())); + assert!(rxqueue.contains(RxOps::Rw.bitmask())); + assert!(rxqueue.contains(RxOps::Response.bitmask())); + assert!(rxqueue.contains(RxOps::CreditUpdate.bitmask())); + assert!(rxqueue.contains(RxOps::Reset.bitmask())); + + rxqueue.queue = 0; + assert!(!rxqueue.contains(RxOps::Request.bitmask())); + assert!(!rxqueue.contains(RxOps::Rw.bitmask())); + assert!(!rxqueue.contains(RxOps::Response.bitmask())); + assert!(!rxqueue.contains(RxOps::CreditUpdate.bitmask())); + assert!(!rxqueue.contains(RxOps::Reset.bitmask())); + } + + #[test] + fn test_enqueue() { + let mut rxqueue = RxQueue::new(); + + rxqueue.enqueue(RxOps::Request); + assert!(rxqueue.contains(RxOps::Request.bitmask())); + + rxqueue.enqueue(RxOps::Rw); + assert!(rxqueue.contains(RxOps::Rw.bitmask())); + + rxqueue.enqueue(RxOps::Response); + assert!(rxqueue.contains(RxOps::Response.bitmask())); + + rxqueue.enqueue(RxOps::CreditUpdate); + assert!(rxqueue.contains(RxOps::CreditUpdate.bitmask())); + + rxqueue.enqueue(RxOps::Reset); + assert!(rxqueue.contains(RxOps::Reset.bitmask())); + } + + #[test] + fn test_peek() { + let mut rxqueue = RxQueue::new(); + + rxqueue.queue = 31; + assert_eq!(rxqueue.peek(), Some(RxOps::Request)); + + rxqueue.queue = 30; + assert_eq!(rxqueue.peek(), Some(RxOps::Rw)); + + rxqueue.queue = 28; + assert_eq!(rxqueue.peek(), Some(RxOps::Response)); + + rxqueue.queue = 24; + assert_eq!(rxqueue.peek(), Some(RxOps::CreditUpdate)); + + rxqueue.queue = 16; + assert_eq!(rxqueue.peek(), Some(RxOps::Reset)); + } + + #[test] + fn test_dequeue() { + let mut rxqueue = RxQueue::new(); + rxqueue.queue = 31; + + assert_eq!(rxqueue.dequeue(), Some(RxOps::Request)); + assert!(!rxqueue.contains(RxOps::Request.bitmask())); + + assert_eq!(rxqueue.dequeue(), Some(RxOps::Rw)); + assert!(!rxqueue.contains(RxOps::Rw.bitmask())); + + assert_eq!(rxqueue.dequeue(), Some(RxOps::Response)); + assert!(!rxqueue.contains(RxOps::Response.bitmask())); + + assert_eq!(rxqueue.dequeue(), Some(RxOps::CreditUpdate)); + assert!(!rxqueue.contains(RxOps::CreditUpdate.bitmask())); + + assert_eq!(rxqueue.dequeue(), Some(RxOps::Reset)); + assert!(!rxqueue.contains(RxOps::Reset.bitmask())); + } + + #[test] + fn test_pending_rx() { + let mut rxqueue = RxQueue::new(); + assert!(!rxqueue.pending_rx()); + + rxqueue.queue = 1; + assert!(rxqueue.pending_rx()); + } +} diff --git a/vsock/src/thread_backend.rs b/vsock/src/thread_backend.rs new file mode 100644 index 0000000..18c5e37 --- /dev/null +++ b/vsock/src/thread_backend.rs @@ -0,0 +1,236 @@ +#![deny(missing_docs)] + +use super::{ + packet::*, + rxops::*, + vhu_vsock::{ + ConnMapKey, Error, Result, VSOCK_HOST_CID, VSOCK_OP_REQUEST, VSOCK_OP_RST, + VSOCK_TYPE_STREAM, + }, + vhu_vsock_thread::VhostUserVsockThread, + vsock_conn::*, +}; +use log::{info, warn}; +use std::{ + collections::{HashMap, HashSet, VecDeque}, + os::unix::{ + net::UnixStream, + prelude::{AsRawFd, FromRawFd, RawFd}, + }, +}; + +// TODO: convert UnixStream to Arc> +pub struct VsockThreadBackend { + /// Map of ConnMapKey objects indexed by raw file descriptors. + pub listener_map: HashMap, + /// Map of vsock connection objects indexed by ConnMapKey objects. + pub conn_map: HashMap>, + /// Queue of ConnMapKey objects indicating pending rx operations. + pub backend_rxq: VecDeque, + /// Map of host-side unix streams indexed by raw file descriptors. + pub stream_map: HashMap, + /// Host side socket for listening to new connections from the host. + host_socket_path: String, + /// epoll for registering new host-side connections. + epoll_fd: i32, + /// Set of allocated local ports. + pub local_port_set: HashSet, +} + +impl VsockThreadBackend { + /// New instance of VsockThreadBackend. + pub fn new(host_socket_path: String, epoll_fd: i32) -> Self { + Self { + listener_map: HashMap::new(), + conn_map: HashMap::new(), + backend_rxq: VecDeque::new(), + // Need this map to prevent connected stream from closing + // TODO: think of a better solution + stream_map: HashMap::new(), + host_socket_path, + epoll_fd, + local_port_set: HashSet::new(), + } + } + + /// Checks if there are pending rx requests in the backend rxq. + pub fn pending_rx(&self) -> bool { + !self.backend_rxq.is_empty() + } + + /// Deliver a vsock packet to the guest vsock driver. + /// + /// Returns: + /// - `Ok(())` if the packet was successfully filled in + /// - `Err(Error::EmptyBackendRxQ) if there was no available data + pub(crate) fn recv_pkt(&mut self, pkt: &mut VsockPacket) -> Result<()> { + // Pop an event from the backend_rxq + let key = self.backend_rxq.pop_front().ok_or(Error::EmptyBackendRxQ)?; + let conn = match self.conn_map.get_mut(&key) { + Some(conn) => conn, + None => { + // assume that the connection does not exist + return Ok(()); + } + }; + + if conn.rx_queue.peek() == Some(RxOps::Reset) { + // Handle RST events here + let conn = self.conn_map.remove(&key).unwrap(); + self.listener_map.remove(&conn.stream.as_raw_fd()); + self.stream_map.remove(&conn.stream.as_raw_fd()); + self.local_port_set.remove(&conn.local_port); + VhostUserVsockThread::epoll_unregister(conn.epoll_fd, conn.stream.as_raw_fd()) + .unwrap_or_else(|err| { + warn!( + "Could not remove epoll listener for fd {:?}: {:?}", + conn.stream.as_raw_fd(), + err + ) + }); + + // Initialize the packet header to contain a VSOCK_OP_RST operation + pkt.set_op(VSOCK_OP_RST) + .set_src_cid(VSOCK_HOST_CID) + .set_dst_cid(conn.guest_cid) + .set_src_port(conn.local_port) + .set_dst_port(conn.peer_port) + .set_len(0) + .set_type(VSOCK_TYPE_STREAM) + .set_flags(0) + .set_buf_alloc(0) + .set_fwd_cnt(0); + + return Ok(()); + } + + // Handle other packet types per connection + conn.recv_pkt(pkt)?; + + Ok(()) + } + + /// Deliver a guest generated packet to its destination in the backend. + /// + /// Absorbs unexpected packets, handles rest to respective connection + /// object. + /// + /// Returns: + /// - always `Ok(())` if packet has been consumed correctly + pub(crate) fn send_pkt(&mut self, pkt: &VsockPacket) -> Result<()> { + let key = ConnMapKey::new(pkt.dst_port(), pkt.src_port()); + + // TODO: Rst if packet has unsupported type + if pkt.pkt_type() != VSOCK_TYPE_STREAM { + info!("vsock: dropping packet of unknown type"); + return Ok(()); + } + + // TODO: Handle packets to other CIDs as well + if pkt.dst_cid() != VSOCK_HOST_CID { + info!( + "vsock: dropping packet for cid other than host: {:?}", + pkt.hdr() + ); + + return Ok(()); + } + + // TODO: Handle cases where connection does not exist and packet op + // is not VSOCK_OP_REQUEST + if !self.conn_map.contains_key(&key) { + // The packet contains a new connection request + if pkt.op() == VSOCK_OP_REQUEST { + self.handle_new_guest_conn(&pkt); + } else { + // TODO: send back RST + } + return Ok(()); + } + + if pkt.op() == VSOCK_OP_RST { + // Handle an RST packet from the guest here + let conn = self.conn_map.get(&key).unwrap(); + if conn.rx_queue.contains(RxOps::Reset.bitmask()) { + return Ok(()); + } + let conn = self.conn_map.remove(&key).unwrap(); + self.listener_map.remove(&conn.stream.as_raw_fd()); + self.stream_map.remove(&conn.stream.as_raw_fd()); + self.local_port_set.remove(&conn.local_port); + VhostUserVsockThread::epoll_unregister(conn.epoll_fd, conn.stream.as_raw_fd()) + .unwrap_or_else(|err| { + warn!( + "Could not remove epoll listener for fd {:?}: {:?}", + conn.stream.as_raw_fd(), + err + ) + }); + return Ok(()); + } + + // Forward this packet to its listening connection + let conn = self.conn_map.get_mut(&key).unwrap(); + conn.send_pkt(pkt)?; + + if conn.rx_queue.pending_rx() { + // Required if the connection object adds new rx operations + self.backend_rxq.push_back(key); + } + + Ok(()) + } + + /// Handle a new guest initiated connection, i.e from the peer, the guest driver. + /// + /// Attempts to connect to a host side unix socket listening on a path + /// corresponding to the destination port as follows: + /// - "{self.host_sock_path}_{local_port}"" + fn handle_new_guest_conn(&mut self, pkt: &VsockPacket) { + let port_path = format!("{}_{}", self.host_socket_path, pkt.dst_port()); + + UnixStream::connect(port_path) + .and_then(|stream| stream.set_nonblocking(true).map(|_| stream)) + .map_err(Error::UnixConnect) + .and_then(|stream| self.add_new_guest_conn(stream, pkt)) + .unwrap_or_else(|_| self.enq_rst()); + } + + /// Wrapper to add new connection to relevant HashMaps. + fn add_new_guest_conn(&mut self, stream: UnixStream, pkt: &VsockPacket) -> Result<()> { + let stream_fd = stream.as_raw_fd(); + self.listener_map + .insert(stream_fd, ConnMapKey::new(pkt.dst_port(), pkt.src_port())); + + let vsock_conn = VsockConnection::new_peer_init( + stream, + pkt.dst_cid(), + pkt.dst_port(), + pkt.src_cid(), + pkt.src_port(), + self.epoll_fd, + pkt.buf_alloc(), + ); + + self.conn_map + .insert(ConnMapKey::new(pkt.dst_port(), pkt.src_port()), vsock_conn); + self.backend_rxq + .push_back(ConnMapKey::new(pkt.dst_port(), pkt.src_port())); + self.stream_map + .insert(stream_fd, unsafe { UnixStream::from_raw_fd(stream_fd) }); + self.local_port_set.insert(pkt.dst_port()); + + VhostUserVsockThread::epoll_register( + self.epoll_fd, + stream_fd, + epoll::Events::EPOLLIN | epoll::Events::EPOLLOUT, + )?; + Ok(()) + } + + /// Enqueue RST packets to be sent to guest. + fn enq_rst(&mut self) { + // TODO + dbg!("New guest conn error: Enqueue RST"); + } +} diff --git a/vsock/src/txbuf.rs b/vsock/src/txbuf.rs new file mode 100644 index 0000000..c429585 --- /dev/null +++ b/vsock/src/txbuf.rs @@ -0,0 +1,210 @@ +use super::vhu_vsock::{Error, Result, CONN_TX_BUF_SIZE}; +use std::{io::Write, num::Wrapping}; + +#[derive(Debug)] +pub struct LocalTxBuf { + /// Buffer holding data to be forwarded to a host-side application + buf: Vec, + /// Index into buffer from which data can be consumed from the buffer + head: Wrapping, + /// Index into buffer from which data can be added to the buffer + tail: Wrapping, +} + +impl LocalTxBuf { + /// Create a new instance of LocalTxBuf. + pub fn new() -> Self { + Self { + buf: vec![0; CONN_TX_BUF_SIZE as usize], + head: Wrapping(0), + tail: Wrapping(0), + } + } + + /// Check if the buf is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Add new data to the tx buffer, push all or none. + /// Returns LocalTxBufFull error if space not sufficient. + pub(crate) fn push(&mut self, data_buf: &[u8]) -> Result<()> { + if CONN_TX_BUF_SIZE as usize - self.len() < data_buf.len() { + // Tx buffer is full + return Err(Error::LocalTxBufFull); + } + + // Get index into buffer at which data can be inserted + let tail_idx = self.tail.0 as usize % CONN_TX_BUF_SIZE as usize; + + // Check if we can fit the data buffer between head and end of buffer + let len = std::cmp::min(CONN_TX_BUF_SIZE as usize - tail_idx, data_buf.len()); + self.buf[tail_idx..tail_idx + len].copy_from_slice(&data_buf[..len]); + + // Check if there is more data to be wrapped around + if len < data_buf.len() { + self.buf[..(data_buf.len() - len)].copy_from_slice(&data_buf[len..]); + } + + // Increment tail by the amount of data that has been added to the buffer + self.tail += Wrapping(data_buf.len() as u32); + + Ok(()) + } + + /// Flush buf data to stream. + pub(crate) fn flush_to(&mut self, stream: &mut S) -> Result { + if self.is_empty() { + // No data to be flushed + return Ok(0); + } + + // Get index into buffer from which data can be read + let head_idx = self.head.0 as usize % CONN_TX_BUF_SIZE as usize; + + // First write from head to end of buffer + let len = std::cmp::min(CONN_TX_BUF_SIZE as usize - head_idx, self.len()); + let written = stream + .write(&self.buf[head_idx..(head_idx + len)]) + .map_err(Error::LocalTxBufFlush)?; + + // Increment head by amount of data that has been flushed to the stream + self.head += Wrapping(written as u32); + + // If written length is less than the expected length we can try again in the future + if written < len { + return Ok(written); + } + + // The head index has wrapped around the end of the buffer, we call self again + Ok(written + self.flush_to(stream).unwrap_or(0)) + } + + /// Return amount of data in the buffer. + fn len(&self) -> usize { + (self.tail - self.head).0 as usize + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_txbuf_len() { + let mut loc_tx_buf = LocalTxBuf::new(); + + // Zero length tx buf + assert_eq!(loc_tx_buf.len(), 0); + + // finite length tx buf + loc_tx_buf.head = Wrapping(0); + loc_tx_buf.tail = Wrapping(CONN_TX_BUF_SIZE); + assert_eq!(loc_tx_buf.len(), CONN_TX_BUF_SIZE as usize); + + loc_tx_buf.tail = Wrapping(CONN_TX_BUF_SIZE / 2); + assert_eq!(loc_tx_buf.len(), (CONN_TX_BUF_SIZE / 2) as usize); + + loc_tx_buf.head = Wrapping(256); + assert_eq!(loc_tx_buf.len(), 32512); + } + + #[test] + fn test_txbuf_is_empty() { + let mut loc_tx_buf = LocalTxBuf::new(); + + // empty tx buffer + assert!(loc_tx_buf.is_empty()); + + // non empty tx buffer + loc_tx_buf.tail = Wrapping(CONN_TX_BUF_SIZE); + assert!(!loc_tx_buf.is_empty()); + } + + #[test] + fn test_txbuf_push() { + let mut loc_tx_buf = LocalTxBuf::new(); + let data = [0; CONN_TX_BUF_SIZE as usize]; + + // push data into empty tx buffer + let res_push = loc_tx_buf.push(&data); + assert!(res_push.is_ok()); + assert_eq!(loc_tx_buf.head, Wrapping(0)); + assert_eq!(loc_tx_buf.tail, Wrapping(CONN_TX_BUF_SIZE)); + + // push data into full tx buffer + let res_push = loc_tx_buf.push(&data); + assert!(res_push.is_err()); + + // head and tail wrap at full + loc_tx_buf.head = Wrapping(CONN_TX_BUF_SIZE); + let res_push = loc_tx_buf.push(&data); + assert!(res_push.is_ok()); + assert_eq!(loc_tx_buf.tail, Wrapping(CONN_TX_BUF_SIZE * 2)); + + // only tail wraps at full + let data = vec![1; 4]; + let mut cmp_data = vec![1; 4]; + cmp_data.append(&mut vec![0; (CONN_TX_BUF_SIZE - 4) as usize]); + loc_tx_buf.head = Wrapping(4); + loc_tx_buf.tail = Wrapping(CONN_TX_BUF_SIZE); + let res_push = loc_tx_buf.push(&data); + assert!(res_push.is_ok()); + assert_eq!(loc_tx_buf.head, Wrapping(4)); + assert_eq!(loc_tx_buf.tail, Wrapping(CONN_TX_BUF_SIZE + 4)); + assert_eq!(loc_tx_buf.buf, cmp_data); + } + + #[test] + fn test_txbuf_flush_to() { + let mut loc_tx_buf = LocalTxBuf::new(); + + // data to be flushed + let data = vec![1; CONN_TX_BUF_SIZE as usize]; + + // target to which data is flushed + let mut cmp_vec = Vec::with_capacity(data.len()); + + // flush no data + let res_flush = loc_tx_buf.flush_to(&mut cmp_vec); + assert!(res_flush.is_ok()); + assert_eq!(res_flush.unwrap(), 0); + + // flush data of CONN_TX_BUF_SIZE amount + let res_push = loc_tx_buf.push(&data); + assert_eq!(res_push.is_ok(), true); + let res_flush = loc_tx_buf.flush_to(&mut cmp_vec); + if let Ok(n) = res_flush { + assert_eq!(loc_tx_buf.head, Wrapping(n as u32)); + assert_eq!(loc_tx_buf.tail, Wrapping(CONN_TX_BUF_SIZE)); + assert_eq!(n, cmp_vec.len()); + assert_eq!(cmp_vec, data[..n]); + } + + // wrapping head flush + let mut data = vec![0; (CONN_TX_BUF_SIZE / 2) as usize]; + data.append(&mut vec![1; (CONN_TX_BUF_SIZE / 2) as usize]); + loc_tx_buf.head = Wrapping(0); + loc_tx_buf.tail = Wrapping(0); + let res_push = loc_tx_buf.push(&data); + assert!(res_push.is_ok()); + cmp_vec.clear(); + loc_tx_buf.head = Wrapping(CONN_TX_BUF_SIZE / 2); + loc_tx_buf.tail = Wrapping(CONN_TX_BUF_SIZE + (CONN_TX_BUF_SIZE / 2)); + let res_flush = loc_tx_buf.flush_to(&mut cmp_vec); + if let Ok(n) = res_flush { + assert_eq!( + loc_tx_buf.head, + Wrapping(CONN_TX_BUF_SIZE + (CONN_TX_BUF_SIZE / 2)) + ); + assert_eq!( + loc_tx_buf.tail, + Wrapping(CONN_TX_BUF_SIZE + (CONN_TX_BUF_SIZE / 2)) + ); + assert_eq!(n, cmp_vec.len()); + let mut data = vec![1; (CONN_TX_BUF_SIZE / 2) as usize]; + data.append(&mut vec![0; (CONN_TX_BUF_SIZE / 2) as usize]); + assert_eq!(cmp_vec, data[..n]); + } + } +} diff --git a/vsock/src/vhu_vsock.rs b/vsock/src/vhu_vsock.rs new file mode 100644 index 0000000..eb50c39 --- /dev/null +++ b/vsock/src/vhu_vsock.rs @@ -0,0 +1,346 @@ +use super::vhu_vsock_thread::*; +use clap::ArgMatches; +use core::slice; +use std::convert::TryFrom; +use std::{io, mem, result, sync::Mutex, u16, u32, u64, u8}; +use thiserror::Error as ThisError; +use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; +use vhost_user_backend::{VhostUserBackendMut, VringRwLock}; +use virtio_bindings::bindings::{ + virtio_blk::__u64, virtio_net::VIRTIO_F_NOTIFY_ON_EMPTY, virtio_net::VIRTIO_F_VERSION_1, + virtio_ring::VIRTIO_RING_F_EVENT_IDX, +}; +use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; +use vmm_sys_util::{ + epoll::EventSet, + eventfd::{EventFd, EFD_NONBLOCK}, +}; + +const NUM_QUEUES: usize = 2; +const QUEUE_SIZE: usize = 256; + +// New descriptors pending on the rx queue +const RX_QUEUE_EVENT: u16 = 0; +// New descriptors are pending on the tx queue. +const TX_QUEUE_EVENT: u16 = 1; +// New descriptors are pending on the event queue. +const EVT_QUEUE_EVENT: u16 = 2; +// Notification coming from the backend. +pub const BACKEND_EVENT: u16 = 3; + +// Vsock connection TX buffer capacity +// TODO: Make this value configurable +pub const CONN_TX_BUF_SIZE: u32 = 64 * 1024; + +// CID of the host +pub const VSOCK_HOST_CID: u64 = 2; + +// Connection oriented packet +pub const VSOCK_TYPE_STREAM: u16 = 1; + +// Vsock packet operation ID +// +// Connection request +pub const VSOCK_OP_REQUEST: u16 = 1; +// Connection response +pub const VSOCK_OP_RESPONSE: u16 = 2; +// Connection reset +pub const VSOCK_OP_RST: u16 = 3; +// Shutdown connection +pub const VSOCK_OP_SHUTDOWN: u16 = 4; +// Data read/write +pub const VSOCK_OP_RW: u16 = 5; +// Flow control credit update +pub const VSOCK_OP_CREDIT_UPDATE: u16 = 6; +// Flow control credit request +pub const VSOCK_OP_CREDIT_REQUEST: u16 = 7; + +// Vsock packet flags +// +// VSOCK_OP_SHUTDOWN: Packet sender will receive no more data +pub const VSOCK_FLAGS_SHUTDOWN_RCV: u32 = 1; +// VSOCK_OP_SHUTDOWN: Packet sender will send no more data +pub const VSOCK_FLAGS_SHUTDOWN_SEND: u32 = 2; + +// Queue mask to select vrings. +const QUEUE_MASK: u64 = 0b11; + +pub(crate) type Result = std::result::Result; + +/// Below enum defines custom error types. +#[derive(Debug, ThisError)] +pub(crate) enum Error { + #[error("Failed to handle event other than EPOLLIN event")] + HandleEventNotEpollIn, + #[error("Failed to handle unknown event")] + HandleUnknownEvent, + #[error("Failed to accept new local socket connection")] + UnixAccept(std::io::Error), + #[error("Failed to create an epoll fd")] + EpollFdCreate(std::io::Error), + #[error("Failed to add to epoll")] + EpollAdd(std::io::Error), + #[error("Failed to modify evset associated with epoll")] + EpollModify(std::io::Error), + #[error("Failed to read from unix stream")] + UnixRead(std::io::Error), + #[error("Failed to convert byte array to string")] + ConvertFromUtf8(std::str::Utf8Error), + #[error("Invalid vsock connection request from host")] + InvalidPortRequest, + #[error("Unable to convert string to integer")] + ParseInteger(std::num::ParseIntError), + #[error("Error reading stream port")] + ReadStreamPort(Box), + #[error("Failed to de-register fd from epoll")] + EpollRemove(std::io::Error), + #[error("No memory configured")] + NoMemoryConfigured, + #[error("Unable to iterate queue")] + IterateQueue, + #[error("No rx request available")] + NoRequestRx, + #[error("Unable to create thread pool")] + CreateThreadPool(std::io::Error), + #[error("Packet missing data buffer")] + PktBufMissing, + #[error("Failed to connect to unix socket")] + UnixConnect(std::io::Error), + #[error("Unable to write to unix stream")] + UnixWrite, + #[error("Unable to push data to local tx buffer")] + LocalTxBufFull, + #[error("Unable to flush data from local tx buffer")] + LocalTxBufFlush(std::io::Error), + #[error("No free local port available for new host inititated connection")] + NoFreeLocalPort, + #[error("Backend rx queue is empty")] + EmptyBackendRxQ, + #[error("Invalid socket path as cmd line argument")] + SocketPathMissing, + #[error("Invalid UDS path as cmd line argument")] + UDSPathMissing, +} + +impl std::convert::From for std::io::Error { + fn from(e: Error) -> Self { + std::io::Error::new(io::ErrorKind::Other, e) + } +} + +#[derive(Debug, Clone)] +/// This structure is the public API through which an external program +/// is allowed to configure the backend. +pub(crate) struct VsockConfig { + guest_cid: u64, + socket: String, + uds_path: String, +} + +impl VsockConfig { + /// Create a new instance of the VsockConfig struct, containing the + /// parameters to be fed into the vsock-backend server. + pub fn new(guest_cid: u64, socket: String, uds_path: String) -> Self { + Self { + guest_cid, + socket, + uds_path, + } + } + + /// Return the guest's current CID. + pub fn get_guest_cid(&self) -> u64 { + self.guest_cid + } + + /// Return the path of the unix domain socket which is listening to + /// requests from the host side application. + pub fn get_uds_path(&self) -> String { + String::from(&self.uds_path) + } + + /// Return the path of the unix domain socket which is listening to + /// requests from the guest. + pub fn get_socket_path(&self) -> String { + String::from(&self.socket) + } +} + +impl TryFrom for VsockConfig { + type Error = Error; + + fn try_from(cmd_args: ArgMatches) -> Result { + let guest_cid = cmd_args + .value_of("guest_cid") + .unwrap_or("3") + .parse::() + .unwrap_or(3); + let socket = cmd_args + .value_of("socket") + .ok_or(Error::SocketPathMissing)? + .to_string(); + let uds_path = cmd_args + .value_of("uds_path") + .ok_or(Error::UDSPathMissing)? + .to_string(); + + Ok(VsockConfig::new(guest_cid, socket, uds_path)) + } +} + +/// A local port and peer port pair used to retrieve +/// the corresponding connection. +#[derive(Hash, PartialEq, Eq, Debug, Clone)] +pub struct ConnMapKey { + local_port: u32, + peer_port: u32, +} + +impl ConnMapKey { + pub fn new(local_port: u32, peer_port: u32) -> Self { + Self { + local_port, + peer_port, + } + } +} + +pub struct VhostUserVsockBackend { + guest_cid: __u64, + pub threads: Vec>, + queues_per_thread: Vec, + pub exit_event: EventFd, +} + +impl VhostUserVsockBackend { + pub(crate) fn new(vsock_config: VsockConfig) -> Result { + let thread = Mutex::new( + VhostUserVsockThread::new(vsock_config.get_uds_path(), vsock_config.get_guest_cid()) + .unwrap(), + ); + let queues_per_thread = vec![QUEUE_MASK]; + + Ok(Self { + guest_cid: vsock_config.get_guest_cid(), + threads: vec![thread], + queues_per_thread, + exit_event: EventFd::new(EFD_NONBLOCK).expect("Creating exit eventfd"), + }) + } +} + +impl VhostUserBackendMut for VhostUserVsockBackend { + fn num_queues(&self) -> usize { + NUM_QUEUES + } + + fn max_queue_size(&self) -> usize { + QUEUE_SIZE + } + + fn features(&self) -> u64 { + 1 << VIRTIO_F_VERSION_1 + | 1 << VIRTIO_F_NOTIFY_ON_EMPTY + | 1 << VIRTIO_RING_F_EVENT_IDX + | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() + } + + fn protocol_features(&self) -> VhostUserProtocolFeatures { + VhostUserProtocolFeatures::CONFIG + } + + fn set_event_idx(&mut self, enabled: bool) { + for thread in self.threads.iter() { + thread.lock().unwrap().event_idx = enabled; + } + } + + fn update_memory( + &mut self, + atomic_mem: GuestMemoryAtomic, + ) -> result::Result<(), io::Error> { + for thread in self.threads.iter() { + thread.lock().unwrap().mem = Some(atomic_mem.clone()); + } + Ok(()) + } + + fn handle_event( + &mut self, + device_event: u16, + evset: EventSet, + vrings: &[VringRwLock], + thread_id: usize, + ) -> result::Result { + let vring_rx = &vrings[0]; + let vring_tx = &vrings[1]; + + if evset == EventSet::OUT { + dbg!("received epollout"); + } + + if evset != EventSet::IN { + return Err(Error::HandleEventNotEpollIn.into()); + } + + let mut thread = self.threads[thread_id].lock().unwrap(); + let evt_idx = thread.event_idx; + + match device_event { + RX_QUEUE_EVENT => {} + TX_QUEUE_EVENT => { + thread.process_tx(vring_tx, evt_idx)?; + } + EVT_QUEUE_EVENT => {} + BACKEND_EVENT => { + thread.process_backend_evt(evset); + thread.process_tx(vring_tx, evt_idx)?; + } + _ => { + return Err(Error::HandleUnknownEvent.into()); + } + } + + if device_event != EVT_QUEUE_EVENT && thread.thread_backend.pending_rx() { + thread.process_rx(vring_rx, evt_idx)?; + } + + Ok(false) + } + + fn get_config(&self, _offset: u32, _size: u32) -> Vec { + let buf = unsafe { + slice::from_raw_parts( + &self.guest_cid as *const __u64 as *const _, + mem::size_of::<__u64>(), + ) + }; + buf.to_vec() + } + + fn queues_per_thread(&self) -> Vec { + self.queues_per_thread.clone() + } + + fn exit_event(&self, _thread_index: usize) -> Option { + Some(self.exit_event.try_clone().expect("Cloning exit eventfd")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_vsock_config_setup() { + let vsock_config = VsockConfig::new( + 3, + "/tmp/vhost4.socket".to_string(), + "/tmp/vm4.vsock".to_string(), + ); + + assert_eq!(vsock_config.get_guest_cid(), 3); + assert_eq!(vsock_config.get_socket_path(), "/tmp/vhost4.socket"); + assert_eq!(vsock_config.get_uds_path(), "/tmp/vm4.vsock"); + } +} diff --git a/vsock/src/vhu_vsock_thread.rs b/vsock/src/vhu_vsock_thread.rs new file mode 100644 index 0000000..e77e2f1 --- /dev/null +++ b/vsock/src/vhu_vsock_thread.rs @@ -0,0 +1,553 @@ +use super::{ + packet::*, + rxops::*, + thread_backend::*, + vhu_vsock::{ConnMapKey, Error, Result, VhostUserVsockBackend, BACKEND_EVENT, VSOCK_HOST_CID}, + vsock_conn::*, +}; +use futures::executor::{ThreadPool, ThreadPoolBuilder}; +use log::warn; +use std::{ + fs::File, + io, + io::Read, + num::Wrapping, + os::unix::{ + net::{UnixListener, UnixStream}, + prelude::{AsRawFd, FromRawFd, RawFd}, + }, + sync::{Arc, RwLock}, +}; +use vhost_user_backend::{VringEpollHandler, VringRwLock, VringT}; +use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; +use vmm_sys_util::{ + epoll::EventSet, + eventfd::{EventFd, EFD_NONBLOCK}, +}; + +type ArcVhostBknd = Arc>; + +pub struct VhostUserVsockThread { + /// Guest memory map. + pub mem: Option>, + /// VIRTIO_RING_F_EVENT_IDX. + pub event_idx: bool, + /// Host socket raw file descriptor. + host_sock: RawFd, + /// Listener listening for new connections on the host. + host_listener: UnixListener, + /// Used to kill the thread. + pub kill_evt: EventFd, + /// Instance of VringWorker. + vring_worker: Option>>, + /// epoll fd to which new host connections are added. + epoll_file: File, + /// VsockThreadBackend instance. + pub thread_backend: VsockThreadBackend, + /// CID of the guest. + guest_cid: u64, + /// Thread pool to handle event idx. + pool: ThreadPool, + /// host side port on which application listens. + local_port: Wrapping, +} + +impl VhostUserVsockThread { + /// Create a new instance of VhostUserVsockThread. + pub(crate) fn new(uds_path: String, guest_cid: u64) -> Result { + // TODO: better error handling + if let Ok(()) = std::fs::remove_file(uds_path.clone()) {} + let host_sock = UnixListener::bind(&uds_path) + .and_then(|sock| sock.set_nonblocking(true).map(|_| sock)) + .unwrap(); + + let epoll_fd = epoll::create(true).map_err(Error::EpollFdCreate)?; + let epoll_file = unsafe { File::from_raw_fd(epoll_fd) }; + + let host_raw_fd = host_sock.as_raw_fd(); + + let thread = VhostUserVsockThread { + mem: None, + event_idx: false, + host_sock: host_sock.as_raw_fd(), + host_listener: host_sock, + kill_evt: EventFd::new(EFD_NONBLOCK).unwrap(), + vring_worker: None, + epoll_file, + thread_backend: VsockThreadBackend::new(uds_path, epoll_fd), + guest_cid, + pool: ThreadPoolBuilder::new() + .pool_size(1) + .create() + .map_err(Error::CreateThreadPool)?, + local_port: Wrapping(0), + }; + + VhostUserVsockThread::epoll_register(epoll_fd, host_raw_fd, epoll::Events::EPOLLIN)?; + + Ok(thread) + } + + /// Register a file with an epoll to listen for events in evset. + pub(crate) fn epoll_register(epoll_fd: RawFd, fd: RawFd, evset: epoll::Events) -> Result<()> { + epoll::ctl( + epoll_fd, + epoll::ControlOptions::EPOLL_CTL_ADD, + fd, + epoll::Event::new(evset, fd as u64), + ) + .map_err(Error::EpollAdd)?; + + Ok(()) + } + + /// Remove a file from the epoll. + pub(crate) fn epoll_unregister(epoll_fd: RawFd, fd: RawFd) -> Result<()> { + epoll::ctl( + epoll_fd, + epoll::ControlOptions::EPOLL_CTL_DEL, + fd, + epoll::Event::new(epoll::Events::empty(), 0), + ) + .map_err(Error::EpollRemove)?; + + Ok(()) + } + + /// Modify the events we listen to for the fd in the epoll. + pub(crate) fn epoll_modify(epoll_fd: RawFd, fd: RawFd, evset: epoll::Events) -> Result<()> { + epoll::ctl( + epoll_fd, + epoll::ControlOptions::EPOLL_CTL_MOD, + fd, + epoll::Event::new(evset, fd as u64), + ) + .map_err(Error::EpollModify)?; + + Ok(()) + } + + /// Return raw file descriptor of the epoll file. + fn get_epoll_fd(&self) -> RawFd { + self.epoll_file.as_raw_fd() + } + + /// Set self's VringWorker. + pub fn set_vring_worker( + &mut self, + vring_worker: Option>>, + ) { + self.vring_worker = vring_worker; + self.vring_worker + .as_ref() + .unwrap() + .register_listener(self.get_epoll_fd(), EventSet::IN, u64::from(BACKEND_EVENT)) + .unwrap(); + } + + /// Process a BACKEND_EVENT received by VhostUserVsockBackend. + pub fn process_backend_evt(&mut self, _evset: EventSet) { + let mut epoll_events = vec![epoll::Event::new(epoll::Events::empty(), 0); 32]; + 'epoll: loop { + match epoll::wait(self.epoll_file.as_raw_fd(), 0, epoll_events.as_mut_slice()) { + Ok(ev_cnt) => { + for evt in epoll_events.iter().take(ev_cnt) { + self.handle_event( + evt.data as RawFd, + epoll::Events::from_bits(evt.events).unwrap(), + ); + } + } + Err(e) => { + if e.kind() == io::ErrorKind::Interrupted { + continue; + } + warn!("failed to consume new epoll event"); + } + } + break 'epoll; + } + } + + /// Handle a BACKEND_EVENT by either accepting a new connection or + /// forwarding a request to the appropriate connection object. + fn handle_event(&mut self, fd: RawFd, evset: epoll::Events) { + if fd == self.host_sock { + // This is a new connection initiated by an application running on the host + self.host_listener + .accept() + .map_err(Error::UnixAccept) + .and_then(|(stream, _)| { + stream + .set_nonblocking(true) + .map(|_| stream) + .map_err(Error::UnixAccept) + }) + .and_then(|stream| self.add_stream_listener(stream)) + .unwrap_or_else(|err| { + warn!("Unable to accept new local connection: {:?}", err); + }); + } else { + // Check if the stream represented by fd has already established a + // connection with the application running in the guest + if let std::collections::hash_map::Entry::Vacant(_) = + self.thread_backend.listener_map.entry(fd) + { + // New connection from the host + if evset != epoll::Events::EPOLLIN { + // Has to be EPOLLIN as it was not connected previously + return; + } + let mut unix_stream = self.thread_backend.stream_map.remove(&fd).unwrap(); + + // Local peer is sending a "connect PORT\n" command + let peer_port = Self::read_local_stream_port(&mut unix_stream).unwrap(); + + // Allocate a local port number + let local_port = match self.allocate_local_port() { + Ok(lp) => lp, + Err(_) => { + return; + } + }; + + // Insert the fd into the backend's maps + self.thread_backend + .listener_map + .insert(fd, ConnMapKey::new(local_port, peer_port)); + + // Create a new connection object an enqueue a connection request + // packet to be sent to the guest + let conn_map_key = ConnMapKey::new(local_port, peer_port); + let mut new_vsock_conn = VsockConnection::new_local_init( + unix_stream, + VSOCK_HOST_CID, + local_port, + self.guest_cid, + peer_port, + self.get_epoll_fd(), + ); + new_vsock_conn.rx_queue.enqueue(RxOps::Request); + new_vsock_conn.set_peer_port(peer_port); + + // Add connection object into the backend's maps + self.thread_backend + .conn_map + .insert(conn_map_key, new_vsock_conn); + + self.thread_backend + .backend_rxq + .push_back(ConnMapKey::new(local_port, peer_port)); + + // Re-register the fd to listen for EPOLLIN and EPOLLOUT events + Self::epoll_modify( + self.get_epoll_fd(), + fd, + epoll::Events::EPOLLIN | epoll::Events::EPOLLOUT, + ) + .unwrap(); + } else { + // Previously connected connection + let key = self.thread_backend.listener_map.get(&fd).unwrap(); + let vsock_conn = self.thread_backend.conn_map.get_mut(&key).unwrap(); + + if evset == epoll::Events::EPOLLOUT { + // Flush any remaining data from the tx buffer + match vsock_conn.tx_buf.flush_to(&mut vsock_conn.stream) { + Ok(cnt) => { + vsock_conn.fwd_cnt += Wrapping(cnt as u32); + vsock_conn.rx_queue.enqueue(RxOps::CreditUpdate); + self.thread_backend.backend_rxq.push_back(ConnMapKey::new( + vsock_conn.local_port, + vsock_conn.peer_port, + )); + } + Err(e) => { + dbg!("Error: {:?}", e); + } + } + return; + } + + // Unregister stream from the epoll, register when connection is + // established with the guest + Self::epoll_unregister(self.epoll_file.as_raw_fd(), fd).unwrap(); + + // Enqueue a read request + vsock_conn.rx_queue.enqueue(RxOps::Rw); + self.thread_backend + .backend_rxq + .push_back(ConnMapKey::new(vsock_conn.local_port, vsock_conn.peer_port)); + } + } + } + + /// Allocate a new local port number. + fn allocate_local_port(&mut self) -> Result { + // TODO: Improve space efficiency of this operation + // TODO: Reuse the conn_map HashMap + // TODO: Test this. + let mut alloc_local_port = self.local_port.0; + loop { + if !self + .thread_backend + .local_port_set + .contains(&alloc_local_port) + { + // The port set doesn't contain the newly allocated port number. + self.local_port = Wrapping(alloc_local_port + 1); + self.thread_backend.local_port_set.insert(alloc_local_port); + return Ok(alloc_local_port); + } else { + if alloc_local_port == self.local_port.0 { + // We have exhausted our search and wrapped back to the current port number + return Err(Error::NoFreeLocalPort); + } + alloc_local_port += 1; + } + } + } + + /// Read `CONNECT PORT_NUM\n` from the connected stream. + fn read_local_stream_port(stream: &mut UnixStream) -> Result { + let mut buf = [0u8; 32]; + + // Minimum number of bytes we should be able to read + // Corresponds to 'CONNECT 0\n' + const MIN_READ_LEN: usize = 10; + + // Read in the minimum number of bytes we can read + stream + .read_exact(&mut buf[..MIN_READ_LEN]) + .map_err(Error::UnixRead)?; + + let mut read_len = MIN_READ_LEN; + while buf[read_len - 1] != b'\n' && read_len < buf.len() { + stream + .read_exact(&mut buf[read_len..read_len + 1]) + .map_err(Error::UnixRead)?; + read_len += 1; + } + + let mut word_iter = std::str::from_utf8(&buf[..read_len]) + .map_err(Error::ConvertFromUtf8)? + .split_whitespace(); + + word_iter + .next() + .ok_or(Error::InvalidPortRequest) + .and_then(|word| { + if word.to_lowercase() == "connect" { + Ok(()) + } else { + Err(Error::InvalidPortRequest) + } + }) + .and_then(|_| word_iter.next().ok_or(Error::InvalidPortRequest)) + .and_then(|word| word.parse::().map_err(Error::ParseInteger)) + .map_err(|e| Error::ReadStreamPort(Box::new(e))) + } + + /// Add a stream to epoll to listen for EPOLLIN events. + fn add_stream_listener(&mut self, stream: UnixStream) -> Result<()> { + let stream_fd = stream.as_raw_fd(); + self.thread_backend.stream_map.insert(stream_fd, stream); + VhostUserVsockThread::epoll_register( + self.get_epoll_fd(), + stream_fd, + epoll::Events::EPOLLIN, + )?; + + // self.register_listener(stream_fd, BACKEND_EVENT); + Ok(()) + } + + /// Iterate over the rx queue and process rx requests. + fn process_rx_queue(&mut self, vring: &VringRwLock) -> Result { + let mut used_any = false; + let atomic_mem = match &self.mem { + Some(m) => m, + None => return Err(Error::NoMemoryConfigured), + }; + + let mut vring_mut = vring.get_mut(); + + let queue = vring_mut.get_queue_mut(); + + while let Some(mut avail_desc) = queue.iter().map_err(|_| Error::IterateQueue)?.next() { + used_any = true; + let atomic_mem = atomic_mem.clone(); + + let head_idx = avail_desc.head_index(); + let used_len = + match VsockPacket::from_rx_virtq_head(&mut avail_desc, atomic_mem.clone()) { + Ok(mut pkt) => { + if self.thread_backend.recv_pkt(&mut pkt).is_ok() { + pkt.hdr().len() + pkt.len() as usize + } else { + queue.iter().unwrap().go_to_previous_position(); + break; + } + } + Err(e) => { + warn!("vsock: RX queue error: {:?}", e); + 0 + } + }; + + let vring = vring.clone(); + let event_idx = self.event_idx; + + self.pool.spawn_ok(async move { + // TODO: Understand why doing the following in the pool works + if event_idx { + if vring.add_used(head_idx, used_len as u32).is_err() { + warn!("Could not return used descriptors to ring"); + } + match vring.needs_notification() { + Err(_) => { + warn!("Could not check if queue needs to be notified"); + vring.signal_used_queue().unwrap(); + } + Ok(needs_notification) => { + if needs_notification { + vring.signal_used_queue().unwrap(); + } + } + } + } else { + if vring.add_used(head_idx, used_len as u32).is_err() { + warn!("Could not return used descriptors to ring"); + } + vring.signal_used_queue().unwrap(); + } + }); + + if !self.thread_backend.pending_rx() { + break; + } + } + Ok(used_any) + } + + /// Wrapper to process rx queue based on whether event idx is enabled or not. + pub(crate) fn process_rx(&mut self, vring: &VringRwLock, event_idx: bool) -> Result { + if event_idx { + // To properly handle EVENT_IDX we need to keep calling + // process_rx_queue until it stops finding new requests + // on the queue, as vm-virtio's Queue implementation + // only checks avail_index once + loop { + if !self.thread_backend.pending_rx() { + break; + } + vring.disable_notification().unwrap(); + + self.process_rx_queue(vring)?; + if !vring.enable_notification().unwrap() { + break; + } + // TODO: This may not be required because of + // previous pending_rx check + // if !work { + // break; + // } + } + } else { + self.process_rx_queue(vring)?; + } + Ok(false) + } + + /// Process tx queue and send requests to the backend for processing. + fn process_tx_queue(&mut self, vring: &VringRwLock) -> Result { + let mut used_any = false; + + let atomic_mem = match &self.mem { + Some(m) => m, + None => return Err(Error::NoMemoryConfigured), + }; + + while let Some(mut avail_desc) = vring + .get_mut() + .get_queue_mut() + .iter() + .map_err(|_| Error::IterateQueue)? + .next() + { + used_any = true; + let atomic_mem = atomic_mem.clone(); + + let head_idx = avail_desc.head_index(); + let pkt = match VsockPacket::from_tx_virtq_head(&mut avail_desc, atomic_mem.clone()) { + Ok(pkt) => pkt, + Err(e) => { + dbg!("vsock: error reading TX packet: {:?}", e); + continue; + } + }; + + if self.thread_backend.send_pkt(&pkt).is_err() { + vring + .get_mut() + .get_queue_mut() + .iter() + .unwrap() + .go_to_previous_position(); + break; + } + + // TODO: Check if the protocol requires read length to be correct + let used_len = 0; + + let vring = vring.clone(); + let event_idx = self.event_idx; + + self.pool.spawn_ok(async move { + if event_idx { + if vring.add_used(head_idx, used_len as u32).is_err() { + warn!("Could not return used descriptors to ring"); + } + match vring.needs_notification() { + Err(_) => { + warn!("Could not check if queue needs to be notified"); + vring.signal_used_queue().unwrap(); + } + Ok(needs_notification) => { + if needs_notification { + vring.signal_used_queue().unwrap(); + } + } + } + } else { + if vring.add_used(head_idx, used_len as u32).is_err() { + warn!("Could not return used descriptors to ring"); + } + vring.signal_used_queue().unwrap(); + } + }); + } + + Ok(used_any) + } + + /// Wrapper to process tx queue based on whether event idx is enabled or not. + pub(crate) fn process_tx(&mut self, vring_lock: &VringRwLock, event_idx: bool) -> Result { + if event_idx { + // To properly handle EVENT_IDX we need to keep calling + // process_rx_queue until it stops finding new requests + // on the queue, as vm-virtio's Queue implementation + // only checks avail_index once + loop { + vring_lock.disable_notification().unwrap(); + self.process_tx_queue(vring_lock)?; + if !vring_lock.enable_notification().unwrap() { + break; + } + } + } else { + self.process_tx_queue(vring_lock)?; + } + Ok(false) + } +} diff --git a/vsock/src/vsock_conn.rs b/vsock/src/vsock_conn.rs new file mode 100644 index 0000000..cc0170c --- /dev/null +++ b/vsock/src/vsock_conn.rs @@ -0,0 +1,605 @@ +use super::{ + packet::*, + rxops::*, + rxqueue::*, + txbuf::*, + vhu_vsock::{ + Error, Result, CONN_TX_BUF_SIZE, VSOCK_FLAGS_SHUTDOWN_RCV, VSOCK_FLAGS_SHUTDOWN_SEND, + VSOCK_OP_CREDIT_REQUEST, VSOCK_OP_CREDIT_UPDATE, VSOCK_OP_REQUEST, VSOCK_OP_RESPONSE, + VSOCK_OP_RST, VSOCK_OP_RW, VSOCK_OP_SHUTDOWN, VSOCK_TYPE_STREAM, + }, + vhu_vsock_thread::VhostUserVsockThread, +}; +use log::info; +use std::{ + io::{ErrorKind, Read, Write}, + num::Wrapping, + os::unix::prelude::{AsRawFd, RawFd}, +}; + +#[derive(Debug)] +pub struct VsockConnection { + /// Host-side stream corresponding to this vsock connection. + pub stream: S, + /// Specifies if the stream is connected to a listener on the host. + pub connect: bool, + /// Port at which a guest application is listening to. + pub peer_port: u32, + /// Queue holding pending rx operations per connection. + pub rx_queue: RxQueue, + /// CID of the host. + local_cid: u64, + /// Port on the host at which a host-side application listens to. + pub local_port: u32, + /// CID of the guest. + pub guest_cid: u64, + /// Total number of bytes written to stream from tx buffer. + pub fwd_cnt: Wrapping, + /// Total number of bytes previously forwarded to stream. + last_fwd_cnt: Wrapping, + /// Size of buffer the guest has allocated for this connection. + peer_buf_alloc: u32, + /// Number of bytes the peer has forwarded to a connection. + peer_fwd_cnt: Wrapping, + /// The total number of bytes sent to the guest vsock driver. + rx_cnt: Wrapping, + /// epoll fd to which this connection's stream has to be added. + pub epoll_fd: RawFd, + /// Local tx buffer. + pub tx_buf: LocalTxBuf, +} + +impl VsockConnection { + /// Create a new vsock connection object for locally i.e host-side + /// inititated connections. + pub fn new_local_init( + stream: S, + local_cid: u64, + local_port: u32, + guest_cid: u64, + guest_port: u32, + epoll_fd: RawFd, + ) -> Self { + Self { + stream, + connect: false, + peer_port: guest_port, + rx_queue: RxQueue::new(), + local_cid, + local_port, + guest_cid, + fwd_cnt: Wrapping(0), + last_fwd_cnt: Wrapping(0), + peer_buf_alloc: 0, + peer_fwd_cnt: Wrapping(0), + rx_cnt: Wrapping(0), + epoll_fd, + tx_buf: LocalTxBuf::new(), + } + } + + /// Create a new vsock connection object for connections initiated by + /// an application running in the guest. + pub fn new_peer_init( + stream: S, + local_cid: u64, + local_port: u32, + guest_cid: u64, + guest_port: u32, + epoll_fd: RawFd, + peer_buf_alloc: u32, + ) -> Self { + let mut rx_queue = RxQueue::new(); + rx_queue.enqueue(RxOps::Response); + Self { + stream, + connect: false, + peer_port: guest_port, + rx_queue, + local_cid, + local_port, + guest_cid, + fwd_cnt: Wrapping(0), + last_fwd_cnt: Wrapping(0), + peer_buf_alloc, + peer_fwd_cnt: Wrapping(0), + rx_cnt: Wrapping(0), + epoll_fd, + tx_buf: LocalTxBuf::new(), + } + } + + /// Set the peer port to the guest side application's port. + pub fn set_peer_port(&mut self, peer_port: u32) { + self.peer_port = peer_port; + } + + /// Process a vsock packet that is meant for this connection. + /// Forward data to the host-side application if the vsock packet + /// contains a RW operation. + pub(crate) fn recv_pkt(&mut self, pkt: &mut VsockPacket) -> Result<()> { + // Initialize all fields in the packet header + self.init_pkt(pkt); + + match self.rx_queue.dequeue() { + Some(RxOps::Request) => { + // Send a connection request to the guest-side application + pkt.set_op(VSOCK_OP_REQUEST); + Ok(()) + } + Some(RxOps::Rw) => { + if !self.connect { + // There is no host-side application listening for this + // packet, hence send back an RST. + pkt.set_op(VSOCK_OP_RST); + return Ok(()); + } + + // Check if peer has space for receiving data + if self.need_credit_update_from_peer() { + self.last_fwd_cnt = self.fwd_cnt; + pkt.set_op(VSOCK_OP_CREDIT_REQUEST); + return Ok(()); + } + let buf = pkt.buf_mut().ok_or(Error::PktBufMissing)?; + + // Perform a credit check to find the maximum read size. The read + // data must fit inside a packet buffer and be within peer's + // available buffer space + let max_read_len = std::cmp::min(buf.len(), self.peer_avail_credit()); + + // Read data from the stream directly into the buffer + if let Ok(read_cnt) = self.stream.read(&mut buf[..max_read_len]) { + if read_cnt == 0 { + // If no data was read then the stream was closed down unexpectedly. + // Send a shutdown packet to the guest-side application. + pkt.set_op(VSOCK_OP_SHUTDOWN) + .set_flag(VSOCK_FLAGS_SHUTDOWN_RCV) + .set_flag(VSOCK_FLAGS_SHUTDOWN_SEND); + } else { + // If data was read, then set the length field in the packet header + // to the amount of data that was read. + pkt.set_op(VSOCK_OP_RW).set_len(read_cnt as u32); + + // Re-register the stream file descriptor for read and write events + VhostUserVsockThread::epoll_register( + self.epoll_fd, + self.stream.as_raw_fd(), + epoll::Events::EPOLLIN | epoll::Events::EPOLLOUT, + )?; + } + + // Update the rx_cnt with the amount of data in the vsock packet. + self.rx_cnt += Wrapping(pkt.len()); + self.last_fwd_cnt = self.fwd_cnt; + } + Ok(()) + } + Some(RxOps::Response) => { + // A response has been received to a newly initiated host-side connection + self.connect = true; + pkt.set_op(VSOCK_OP_RESPONSE); + Ok(()) + } + Some(RxOps::CreditUpdate) => { + // Request credit update from the guest. + if !self.rx_queue.pending_rx() { + // Waste an rx buffer if no rx is pending + pkt.set_op(VSOCK_OP_CREDIT_UPDATE); + self.last_fwd_cnt = self.fwd_cnt; + } + Ok(()) + } + _ => Err(Error::NoRequestRx), + } + } + + /// Deliver a guest generated packet to this connection. + /// + /// Returns: + /// - always `Ok(())` to indicate that the packet has been consumed + pub(crate) fn send_pkt(&mut self, pkt: &VsockPacket) -> Result<()> { + // Update peer credit information + self.peer_buf_alloc = pkt.buf_alloc(); + self.peer_fwd_cnt = Wrapping(pkt.fwd_cnt()); + + match pkt.op() { + VSOCK_OP_RESPONSE => { + // Confirmation for a host initiated connection + // TODO: Handle stream write error in a better manner + let response = format!("OK {}\n", self.peer_port); + self.stream.write_all(response.as_bytes()).unwrap(); + self.connect = true; + } + VSOCK_OP_RW => { + // Data has to be written to the host-side stream + if pkt.buf().is_none() { + info!( + "Dropping empty packet from guest (lp={}, pp={})", + self.local_port, self.peer_port + ); + return Ok(()); + } + + let buf_slice = &pkt.buf().unwrap()[..(pkt.len() as usize)]; + if let Err(err) = self.send_bytes(buf_slice) { + // TODO: Terminate this connection + dbg!("err:{:?}", err); + return Ok(()); + } + } + VSOCK_OP_CREDIT_UPDATE => { + // Already updated the credit + + // Re-register the stream file descriptor for read and write events + if VhostUserVsockThread::epoll_modify( + self.epoll_fd, + self.stream.as_raw_fd(), + epoll::Events::EPOLLIN | epoll::Events::EPOLLOUT, + ) + .is_err() + { + VhostUserVsockThread::epoll_register( + self.epoll_fd, + self.stream.as_raw_fd(), + epoll::Events::EPOLLIN | epoll::Events::EPOLLOUT, + ) + .unwrap(); + }; + } + VSOCK_OP_CREDIT_REQUEST => { + // Send back this connection's credit information + self.rx_queue.enqueue(RxOps::CreditUpdate); + } + VSOCK_OP_SHUTDOWN => { + // Shutdown this connection + let recv_off = pkt.flags() & VSOCK_FLAGS_SHUTDOWN_RCV != 0; + let send_off = pkt.flags() & VSOCK_FLAGS_SHUTDOWN_SEND != 0; + + if recv_off && send_off && self.tx_buf.is_empty() { + self.rx_queue.enqueue(RxOps::Reset); + } + } + _ => {} + } + + Ok(()) + } + + /// Write data to the host-side stream. + /// + /// Returns: + /// - Ok(cnt) where cnt is the number of bytes written to the stream + /// - Err(Error::UnixWrite) if there was an error writing to the stream + fn send_bytes(&mut self, buf: &[u8]) -> Result<()> { + if !self.tx_buf.is_empty() { + // Data is already present in the buffer and the backend + // is waiting for a EPOLLOUT event to flush it + return self.tx_buf.push(buf); + } + + // Write data to the stream + let written_count = match self.stream.write(buf) { + Ok(cnt) => cnt, + Err(e) => { + if e.kind() == ErrorKind::WouldBlock { + 0 + } else { + println!("send_bytes error: {:?}", e); + return Err(Error::UnixWrite); + } + } + }; + + // Increment forwarded count by number of bytes written to the stream + self.fwd_cnt += Wrapping(written_count as u32); + // TODO: https://github.com/torvalds/linux/commit/c69e6eafff5f725bc29dcb8b52b6782dca8ea8a2 + self.rx_queue.enqueue(RxOps::CreditUpdate); + + if written_count != buf.len() { + return self.tx_buf.push(&buf[written_count..]); + } + + Ok(()) + } + + /// Initialize all header fields in the vsock packet. + fn init_pkt<'a>(&self, pkt: &'a mut VsockPacket) -> &'a mut VsockPacket { + // Zero out the packet header + for b in pkt.hdr_mut() { + *b = 0; + } + + pkt.set_src_cid(self.local_cid) + .set_dst_cid(self.guest_cid) + .set_src_port(self.local_port) + .set_dst_port(self.peer_port) + .set_type(VSOCK_TYPE_STREAM) + .set_buf_alloc(CONN_TX_BUF_SIZE) + .set_fwd_cnt(self.fwd_cnt.0) + } + + /// Get max number of bytes we can send to peer without overflowing + /// the peer's buffer. + fn peer_avail_credit(&self) -> usize { + (Wrapping(self.peer_buf_alloc as u32) - (self.rx_cnt - self.peer_fwd_cnt)).0 as usize + } + + /// Check if we need a credit update from the peer before sending + /// more data to it. + fn need_credit_update_from_peer(&self) -> bool { + self.peer_avail_credit() == 0 + } +} + +#[cfg(test)] +mod tests { + use byteorder::{ByteOrder, LittleEndian}; + + use super::*; + use crate::packet::tests::{prepare_desc_chain_vsock, HeadParams}; + use crate::vhu_vsock::VSOCK_HOST_CID; + use std::io::Result as IoResult; + + struct VsockDummySocket { + data: Vec, + } + + impl VsockDummySocket { + fn new() -> Self { + Self { data: Vec::new() } + } + } + + impl Write for VsockDummySocket { + fn write(&mut self, buf: &[u8]) -> std::result::Result { + self.data.clear(); + self.data.extend_from_slice(buf); + + Ok(buf.len()) + } + fn flush(&mut self) -> IoResult<()> { + Ok(()) + } + } + + impl Read for VsockDummySocket { + fn read(&mut self, buf: &mut [u8]) -> IoResult { + buf[..self.data.len()].copy_from_slice(&self.data); + Ok(self.data.len()) + } + } + + impl AsRawFd for VsockDummySocket { + fn as_raw_fd(&self) -> RawFd { + -1 + } + } + + #[test] + fn test_vsock_conn_init() { + // new locally inititated connection + let dummy_file = VsockDummySocket::new(); + let mut vsock_conn_local = + VsockConnection::new_local_init(dummy_file, VSOCK_HOST_CID, 5000, 3, 5001, -1); + + assert!(!vsock_conn_local.connect); + assert_eq!(vsock_conn_local.peer_port, 5001); + assert_eq!(vsock_conn_local.rx_queue, RxQueue::new()); + assert_eq!(vsock_conn_local.local_cid, VSOCK_HOST_CID); + assert_eq!(vsock_conn_local.local_port, 5000); + assert_eq!(vsock_conn_local.guest_cid, 3); + + // set peer port + vsock_conn_local.set_peer_port(5002); + assert_eq!(vsock_conn_local.peer_port, 5002); + + // New connection initiated by the peer/guest + let dummy_file = VsockDummySocket::new(); + let mut vsock_conn_peer = + VsockConnection::new_peer_init(dummy_file, VSOCK_HOST_CID, 5000, 3, 5001, -1, 65536); + + assert!(!vsock_conn_peer.connect); + assert_eq!(vsock_conn_peer.peer_port, 5001); + assert_eq!(vsock_conn_peer.rx_queue.dequeue().unwrap(), RxOps::Response); + assert!(!vsock_conn_peer.rx_queue.pending_rx()); + assert_eq!(vsock_conn_peer.local_cid, VSOCK_HOST_CID); + assert_eq!(vsock_conn_peer.local_port, 5000); + assert_eq!(vsock_conn_peer.guest_cid, 3); + assert_eq!(vsock_conn_peer.peer_buf_alloc, 65536); + } + + #[test] + fn test_vsock_conn_credit() { + // new locally inititated connection + let dummy_file = VsockDummySocket::new(); + let mut vsock_conn_local = + VsockConnection::new_local_init(dummy_file, VSOCK_HOST_CID, 5000, 3, 5001, -1); + + assert_eq!(vsock_conn_local.peer_avail_credit(), 0); + assert!(vsock_conn_local.need_credit_update_from_peer()); + + vsock_conn_local.peer_buf_alloc = 65536; + assert_eq!(vsock_conn_local.peer_avail_credit(), 65536); + assert!(!vsock_conn_local.need_credit_update_from_peer()); + + vsock_conn_local.rx_cnt = Wrapping(32768); + assert_eq!(vsock_conn_local.peer_avail_credit(), 32768); + assert!(!vsock_conn_local.need_credit_update_from_peer()); + + vsock_conn_local.rx_cnt = Wrapping(65536); + assert_eq!(vsock_conn_local.peer_avail_credit(), 0); + assert!(vsock_conn_local.need_credit_update_from_peer()); + } + + #[test] + fn test_vsock_conn_init_pkt() { + // parameters for packet head construction + let head_params = HeadParams::new(VSOCK_PKT_HDR_SIZE, 10); + + // new locally inititated connection + let dummy_file = VsockDummySocket::new(); + let vsock_conn_local = + VsockConnection::new_local_init(dummy_file, VSOCK_HOST_CID, 5000, 3, 5001, -1); + + // write only descriptor chain + let (mem, mut descr_chain) = prepare_desc_chain_vsock(true, &head_params, 2, 10); + let mut vsock_pkt = VsockPacket::from_rx_virtq_head(&mut descr_chain, mem).unwrap(); + + // initialize a vsock packet for the guest + vsock_conn_local.init_pkt(&mut vsock_pkt); + + assert_eq!(vsock_pkt.src_cid(), VSOCK_HOST_CID); + assert_eq!(vsock_pkt.dst_cid(), 3); + assert_eq!(vsock_pkt.src_port(), 5000); + assert_eq!(vsock_pkt.dst_port(), 5001); + assert_eq!(vsock_pkt.pkt_type(), VSOCK_TYPE_STREAM); + assert_eq!(vsock_pkt.buf_alloc(), CONN_TX_BUF_SIZE); + assert_eq!(vsock_pkt.fwd_cnt(), 0); + } + + #[test] + fn test_vsock_conn_recv_pkt() { + // parameters for packet head construction + let head_params = HeadParams::new(VSOCK_PKT_HDR_SIZE, 5); + + // new locally inititated connection + let dummy_file = VsockDummySocket::new(); + let mut vsock_conn_local = + VsockConnection::new_local_init(dummy_file, VSOCK_HOST_CID, 5000, 3, 5001, -1); + + // write only descriptor chain + let (mem, mut descr_chain) = prepare_desc_chain_vsock(true, &head_params, 1, 5); + let mut vsock_pkt = VsockPacket::from_rx_virtq_head(&mut descr_chain, mem).unwrap(); + + // VSOCK_OP_REQUEST: new local conn request + vsock_conn_local.rx_queue.enqueue(RxOps::Request); + let vsock_op_req = vsock_conn_local.recv_pkt(&mut vsock_pkt); + assert!(vsock_op_req.is_ok()); + assert!(!vsock_conn_local.rx_queue.pending_rx()); + assert_eq!(vsock_pkt.op(), VSOCK_OP_REQUEST); + + // VSOCK_OP_RST: reset if connection not established + vsock_conn_local.rx_queue.enqueue(RxOps::Rw); + let vsock_op_rst = vsock_conn_local.recv_pkt(&mut vsock_pkt); + assert!(vsock_op_rst.is_ok()); + assert!(!vsock_conn_local.rx_queue.pending_rx()); + assert_eq!(vsock_pkt.op(), VSOCK_OP_RST); + + // VSOCK_OP_CREDIT_UPDATE: need credit update from peer/guest + vsock_conn_local.connect = true; + vsock_conn_local.rx_queue.enqueue(RxOps::Rw); + vsock_conn_local.fwd_cnt = Wrapping(1024); + let vsock_op_credit_update = vsock_conn_local.recv_pkt(&mut vsock_pkt); + assert!(vsock_op_credit_update.is_ok()); + assert!(!vsock_conn_local.rx_queue.pending_rx()); + assert_eq!(vsock_pkt.op(), VSOCK_OP_CREDIT_REQUEST); + assert_eq!(vsock_conn_local.last_fwd_cnt, Wrapping(1024)); + + // VSOCK_OP_SHUTDOWN: zero data read from stream/file + vsock_conn_local.peer_buf_alloc = 65536; + vsock_conn_local.rx_queue.enqueue(RxOps::Rw); + let vsock_op_zero_read_shutdown = vsock_conn_local.recv_pkt(&mut vsock_pkt); + assert!(vsock_op_zero_read_shutdown.is_ok()); + assert!(!vsock_conn_local.rx_queue.pending_rx()); + assert_eq!(vsock_conn_local.rx_cnt, Wrapping(0)); + assert_eq!(vsock_conn_local.last_fwd_cnt, Wrapping(1024)); + assert_eq!(vsock_pkt.op(), VSOCK_OP_SHUTDOWN); + assert_eq!( + vsock_pkt.flags(), + VSOCK_FLAGS_SHUTDOWN_RCV | VSOCK_FLAGS_SHUTDOWN_SEND + ); + + // VSOCK_OP_RW: finite data read from stream/file + vsock_conn_local.stream.write_all(b"hello").unwrap(); + vsock_conn_local.rx_queue.enqueue(RxOps::Rw); + let vsock_op_zero_read = vsock_conn_local.recv_pkt(&mut vsock_pkt); + // below error due to epoll add + assert!(vsock_op_zero_read.is_err()); + assert_eq!(vsock_pkt.op(), VSOCK_OP_RW); + assert!(!vsock_conn_local.rx_queue.pending_rx()); + assert_eq!(vsock_pkt.len(), 5); + assert_eq!(vsock_pkt.buf().unwrap(), b"hello"); + + // VSOCK_OP_RESPONSE: response from a locally initiated connection + vsock_conn_local.rx_queue.enqueue(RxOps::Response); + let vsock_op_response = vsock_conn_local.recv_pkt(&mut vsock_pkt); + assert!(vsock_op_response.is_ok()); + assert!(!vsock_conn_local.rx_queue.pending_rx()); + assert_eq!(vsock_pkt.op(), VSOCK_OP_RESPONSE); + assert!(vsock_conn_local.connect); + + // VSOCK_OP_CREDIT_UPDATE: guest needs credit update + vsock_conn_local.rx_queue.enqueue(RxOps::CreditUpdate); + let vsock_op_credit_update = vsock_conn_local.recv_pkt(&mut vsock_pkt); + assert!(!vsock_conn_local.rx_queue.pending_rx()); + assert!(vsock_op_credit_update.is_ok()); + assert_eq!(vsock_pkt.op(), VSOCK_OP_CREDIT_UPDATE); + assert_eq!(vsock_conn_local.last_fwd_cnt, Wrapping(1024)); + + // non-existent request + let vsock_op_error = vsock_conn_local.recv_pkt(&mut vsock_pkt); + assert!(vsock_op_error.is_err()); + } + + #[test] + fn test_vsock_conn_send_pkt() { + // parameters for packet head construction + let head_params = HeadParams::new(VSOCK_PKT_HDR_SIZE, 5); + + // new locally inititated connection + let dummy_file = VsockDummySocket::new(); + let mut vsock_conn_local = + VsockConnection::new_local_init(dummy_file, VSOCK_HOST_CID, 5000, 3, 5001, -1); + + // write only descriptor chain + let (mem, mut descr_chain) = prepare_desc_chain_vsock(false, &head_params, 1, 5); + let mut vsock_pkt = VsockPacket::from_tx_virtq_head(&mut descr_chain, mem).unwrap(); + + // peer credit information + const HDROFF_BUF_ALLOC: usize = 36; + const HDROFF_FWD_CNT: usize = 40; + LittleEndian::write_u32(&mut vsock_pkt.hdr_mut()[HDROFF_BUF_ALLOC..], 65536); + LittleEndian::write_u32(&mut vsock_pkt.hdr_mut()[HDROFF_FWD_CNT..], 1024); + + // check if peer credit information is updated currently + let credit_check = vsock_conn_local.send_pkt(&vsock_pkt); + assert!(credit_check.is_ok()); + assert_eq!(vsock_conn_local.peer_buf_alloc, 65536); + assert_eq!(vsock_conn_local.peer_fwd_cnt, Wrapping(1024)); + + // VSOCK_OP_RESPONSE + vsock_pkt.set_op(VSOCK_OP_RESPONSE); + let peer_response = vsock_conn_local.send_pkt(&vsock_pkt); + assert!(peer_response.is_ok()); + assert!(vsock_conn_local.connect); + let mut resp_buf = vec![0; 8]; + vsock_conn_local.stream.read_exact(&mut resp_buf).unwrap(); + assert_eq!(resp_buf, b"OK 5001\n"); + + // VSOCK_OP_RW + vsock_pkt.set_op(VSOCK_OP_RW); + vsock_pkt.buf_mut().unwrap().copy_from_slice(b"hello"); + let rw_response = vsock_conn_local.send_pkt(&vsock_pkt); + assert!(rw_response.is_ok()); + let mut resp_buf = vec![0; 5]; + vsock_conn_local.stream.read_exact(&mut resp_buf).unwrap(); + assert_eq!(resp_buf, b"hello"); + + // VSOCK_OP_CREDIT_REQUEST + vsock_pkt.set_op(VSOCK_OP_CREDIT_REQUEST); + let credit_response = vsock_conn_local.send_pkt(&vsock_pkt); + assert!(credit_response.is_ok()); + assert_eq!( + vsock_conn_local.rx_queue.peek().unwrap(), + RxOps::CreditUpdate + ); + + // VSOCK_OP_SHUTDOWN + vsock_pkt.set_op(VSOCK_OP_SHUTDOWN); + vsock_pkt.set_flags(VSOCK_FLAGS_SHUTDOWN_RCV | VSOCK_FLAGS_SHUTDOWN_SEND); + let shutdown_response = vsock_conn_local.send_pkt(&vsock_pkt); + assert!(shutdown_response.is_ok()); + assert!(vsock_conn_local.rx_queue.contains(RxOps::Reset.bitmask())); + } +} From 69882e1dfc5a8657091ef7cfb6cc16a7bbfedd28 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Mon, 26 Sep 2022 18:54:47 +0200 Subject: [PATCH 02/16] vsock: fix clippy warnings clippy complains of some minor issues, probably because the new version has been improved. Let's fix them. Signed-off-by: Stefano Garzarella --- vsock/src/packet.rs | 9 ++++----- vsock/src/rxops.rs | 2 +- vsock/src/rxqueue.rs | 2 +- vsock/src/thread_backend.rs | 2 +- vsock/src/txbuf.rs | 2 +- vsock/src/vhu_vsock_thread.rs | 2 +- vsock/src/vsock_conn.rs | 2 +- 7 files changed, 10 insertions(+), 11 deletions(-) diff --git a/vsock/src/packet.rs b/vsock/src/packet.rs index a321faf..b3b9164 100644 --- a/vsock/src/packet.rs +++ b/vsock/src/packet.rs @@ -398,17 +398,16 @@ pub mod tests { let virt_queue = MockSplitQueue::new(&mem, 16); let mut next_addr = virt_queue.desc_table().total_size() + 0x100; let mut flags = 0; - let mut head_flags; if write_only { flags |= VIRTQ_DESC_F_WRITE; } - if data_chain_len > 0 { - head_flags = flags | VIRTQ_DESC_F_NEXT + let mut head_flags = if data_chain_len > 0 { + flags | VIRTQ_DESC_F_NEXT } else { - head_flags = flags; - } + flags + }; // vsock packet header // let header = vec![0 as u8; head_params.head_len]; diff --git a/vsock/src/rxops.rs b/vsock/src/rxops.rs index b79e62b..3460a8d 100644 --- a/vsock/src/rxops.rs +++ b/vsock/src/rxops.rs @@ -1,4 +1,4 @@ -#[derive(Clone, Copy, PartialEq, Debug)] +#[derive(Clone, Copy, Eq, PartialEq, Debug)] pub enum RxOps { /// VSOCK_OP_REQUEST Request = 0, diff --git a/vsock/src/rxqueue.rs b/vsock/src/rxqueue.rs index 9b033b9..0a1dbda 100644 --- a/vsock/src/rxqueue.rs +++ b/vsock/src/rxqueue.rs @@ -1,6 +1,6 @@ use super::rxops::RxOps; -#[derive(Debug, PartialEq)] +#[derive(Debug, Eq, PartialEq)] pub struct RxQueue { /// Bitmap of rx operations. queue: u8, diff --git a/vsock/src/thread_backend.rs b/vsock/src/thread_backend.rs index 18c5e37..8351003 100644 --- a/vsock/src/thread_backend.rs +++ b/vsock/src/thread_backend.rs @@ -141,7 +141,7 @@ impl VsockThreadBackend { if !self.conn_map.contains_key(&key) { // The packet contains a new connection request if pkt.op() == VSOCK_OP_REQUEST { - self.handle_new_guest_conn(&pkt); + self.handle_new_guest_conn(pkt); } else { // TODO: send back RST } diff --git a/vsock/src/txbuf.rs b/vsock/src/txbuf.rs index c429585..012110b 100644 --- a/vsock/src/txbuf.rs +++ b/vsock/src/txbuf.rs @@ -172,7 +172,7 @@ mod tests { // flush data of CONN_TX_BUF_SIZE amount let res_push = loc_tx_buf.push(&data); - assert_eq!(res_push.is_ok(), true); + assert!(res_push.is_ok()); let res_flush = loc_tx_buf.flush_to(&mut cmp_vec); if let Ok(n) = res_flush { assert_eq!(loc_tx_buf.head, Wrapping(n as u32)); diff --git a/vsock/src/vhu_vsock_thread.rs b/vsock/src/vhu_vsock_thread.rs index e77e2f1..aacae72 100644 --- a/vsock/src/vhu_vsock_thread.rs +++ b/vsock/src/vhu_vsock_thread.rs @@ -249,7 +249,7 @@ impl VhostUserVsockThread { } else { // Previously connected connection let key = self.thread_backend.listener_map.get(&fd).unwrap(); - let vsock_conn = self.thread_backend.conn_map.get_mut(&key).unwrap(); + let vsock_conn = self.thread_backend.conn_map.get_mut(key).unwrap(); if evset == epoll::Events::EPOLLOUT { // Flush any remaining data from the tx buffer diff --git a/vsock/src/vsock_conn.rs b/vsock/src/vsock_conn.rs index cc0170c..f3b590b 100644 --- a/vsock/src/vsock_conn.rs +++ b/vsock/src/vsock_conn.rs @@ -322,7 +322,7 @@ impl VsockConnection { /// Get max number of bytes we can send to peer without overflowing /// the peer's buffer. fn peer_avail_credit(&self) -> usize { - (Wrapping(self.peer_buf_alloc as u32) - (self.rx_cnt - self.peer_fwd_cnt)).0 as usize + (Wrapping(self.peer_buf_alloc) - (self.rx_cnt - self.peer_fwd_cnt)).0 as usize } /// Check if we need a credit update from the peer before sending From a1daeec3bcbec53590a13da0acd2ab167791c154 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Wed, 12 Oct 2022 11:05:14 +0200 Subject: [PATCH 03/16] vsock: update Cargo.toml dependencies Update the dependencies aligning with the other crates in this workspace. Signed-off-by: Stefano Garzarella --- Cargo.lock | 117 +++++++++------------------------- vsock/Cargo.toml | 14 ++-- vsock/src/packet.rs | 27 ++++---- vsock/src/vhu_vsock_thread.rs | 18 ++++-- 4 files changed, 62 insertions(+), 114 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 103de3b..11fe8a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -313,7 +313,7 @@ dependencies = [ "libc", "libgpiod-sys", "thiserror", - "vmm-sys-util 0.10.0", + "vmm-sys-util", ] [[package]] @@ -579,18 +579,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "vhost" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b56bf8f178fc500fe14505fca8b00dec76fc38f2304f461c8d9d7547982311d" -dependencies = [ - "bitflags", - "libc", - "vm-memory 0.9.0", - "vmm-sys-util 0.10.0", -] - [[package]] name = "vhost" version = "0.5.0" @@ -599,8 +587,8 @@ checksum = "79243657c76e5c90dcbf60187c842614f6dfc7123972c55bb3bcc446792aca93" dependencies = [ "bitflags", "libc", - "vm-memory 0.9.0", - "vmm-sys-util 0.10.0", + "vm-memory", + "vmm-sys-util", ] [[package]] @@ -613,12 +601,12 @@ dependencies = [ "libgpiod", "log", "thiserror", - "vhost 0.5.0", - "vhost-user-backend 0.7.0", + "vhost", + "vhost-user-backend", "virtio-bindings", - "virtio-queue 0.6.1", - "vm-memory 0.9.0", - "vmm-sys-util 0.10.0", + "virtio-queue", + "vm-memory", + "vmm-sys-util", ] [[package]] @@ -630,12 +618,12 @@ dependencies = [ "libc", "log", "thiserror", - "vhost 0.5.0", - "vhost-user-backend 0.7.0", + "vhost", + "vhost-user-backend", "virtio-bindings", - "virtio-queue 0.6.1", - "vm-memory 0.9.0", - "vmm-sys-util 0.10.0", + "virtio-queue", + "vm-memory", + "vmm-sys-util", ] [[package]] @@ -650,27 +638,12 @@ dependencies = [ "rand", "tempfile", "thiserror", - "vhost 0.5.0", - "vhost-user-backend 0.7.0", + "vhost", + "vhost-user-backend", "virtio-bindings", - "virtio-queue 0.6.1", - "vm-memory 0.9.0", - "vmm-sys-util 0.10.0", -] - -[[package]] -name = "vhost-user-backend" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8db00e93514caa8987bb8b536fe962c9b66b4068583abc4c531eb97988477cd" -dependencies = [ - "libc", - "log", - "vhost 0.3.0", - "virtio-bindings", - "virtio-queue 0.1.0", - "vm-memory 0.7.0", - "vmm-sys-util 0.9.0", + "virtio-queue", + "vm-memory", + "vmm-sys-util", ] [[package]] @@ -681,11 +654,11 @@ checksum = "6a0fc7d5f8e2943cd9f2ecd58be3f2078add863a49573d14dd9d64e1ab26544c" dependencies = [ "libc", "log", - "vhost 0.5.0", + "vhost", "virtio-bindings", - "virtio-queue 0.6.1", - "vm-memory 0.9.0", - "vmm-sys-util 0.10.0", + "virtio-queue", + "vm-memory", + "vmm-sys-util", ] [[package]] @@ -698,12 +671,12 @@ dependencies = [ "futures", "log", "thiserror", - "vhost 0.3.0", - "vhost-user-backend 0.1.0", + "vhost", + "vhost-user-backend", "virtio-bindings", - "virtio-queue 0.1.0", - "vm-memory 0.7.0", - "vmm-sys-util 0.9.0", + "virtio-queue", + "vm-memory", + "vmm-sys-util", ] [[package]] @@ -712,17 +685,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ff512178285488516ed85f15b5d0113a7cdb89e9e8a760b269ae4f02b84bd6b" -[[package]] -name = "virtio-queue" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90da9e627f6aaf667cc7b6548a28be332d3e1f058f4ceeb46ab6bcee5c4b74d" -dependencies = [ - "log", - "vm-memory 0.7.0", - "vmm-sys-util 0.10.0", -] - [[package]] name = "virtio-queue" version = "0.6.1" @@ -731,19 +693,8 @@ checksum = "435dd49c7b38419729afd43675850c7b5dc4728f2fabd70c7a9079a331e4f8c6" dependencies = [ "log", "virtio-bindings", - "vm-memory 0.9.0", - "vmm-sys-util 0.10.0", -] - -[[package]] -name = "vm-memory" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339d4349c126fdcd87e034631d7274370cf19eb0e87b33166bcd956589fc72c5" -dependencies = [ - "arc-swap", - "libc", - "winapi", + "vm-memory", + "vmm-sys-util", ] [[package]] @@ -757,16 +708,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "vmm-sys-util" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "733537bded03aaa93543f785ae997727b30d1d9f4a03b7861d23290474242e11" -dependencies = [ - "bitflags", - "libc", -] - [[package]] name = "vmm-sys-util" version = "0.10.0" diff --git a/vsock/Cargo.toml b/vsock/Cargo.toml index 54b488b..282c116 100644 --- a/vsock/Cargo.toml +++ b/vsock/Cargo.toml @@ -14,14 +14,14 @@ byteorder = "1" clap = { version = ">=3.0", features = ["yaml"] } epoll = "4.3.1" futures = { version = "0.3", features = ["thread-pool"] } -log = "0.4.14" +log = ">=0.4.6" thiserror = "1.0" -vhost = { version = "0.3", features = ["vhost-user-slave"] } -vhost-user-backend = "0.1" +vhost = { version = "0.5", features = ["vhost-user-slave"] } +vhost-user-backend = "0.7.0" virtio-bindings = ">=0.1" -virtio-queue = "0.1" -vm-memory = "0.7" -vmm-sys-util = "=0.9.0" +virtio-queue = "0.6" +vm-memory = ">=0.8" +vmm-sys-util = "=0.10.0" [dev-dependencies] -virtio-queue = { version = "0.1", features = ["test-utils"] } +virtio-queue = { version = "0.6", features = ["test-utils"] } diff --git a/vsock/src/packet.rs b/vsock/src/packet.rs index b3b9164..b30686a 100644 --- a/vsock/src/packet.rs +++ b/vsock/src/packet.rs @@ -360,11 +360,8 @@ pub mod tests { use crate::vhu_vsock::{VSOCK_OP_RW, VSOCK_TYPE_STREAM}; use super::*; - use virtio_queue::{ - defs::{VIRTQ_DESC_F_NEXT, VIRTQ_DESC_F_WRITE}, - mock::MockSplitQueue, - Descriptor, - }; + use virtio_bindings::bindings::virtio_ring::{VRING_DESC_F_NEXT, VRING_DESC_F_WRITE}; + use virtio_queue::{mock::MockSplitQueue, Descriptor, Queue, QueueOwnedT}; use vm_memory::{Address, Bytes, GuestAddress, GuestMemoryAtomic, GuestMemoryMmap}; pub struct HeadParams { @@ -400,11 +397,11 @@ pub mod tests { let mut flags = 0; if write_only { - flags |= VIRTQ_DESC_F_WRITE; + flags |= VRING_DESC_F_WRITE; } let mut head_flags = if data_chain_len > 0 { - flags | VIRTQ_DESC_F_NEXT + flags | VRING_DESC_F_NEXT } else { flags }; @@ -412,9 +409,10 @@ pub mod tests { // vsock packet header // let header = vec![0 as u8; head_params.head_len]; let header = head_params.construct_head(); - let head_desc = Descriptor::new(next_addr, head_params.head_len as u32, head_flags, 1); + let head_desc = + Descriptor::new(next_addr, head_params.head_len as u32, head_flags as u16, 1); mem.write(&header, head_desc.addr()).unwrap(); - virt_queue.desc_table().store(0, head_desc); + assert!(virt_queue.desc_table().store(0, head_desc).is_ok()); next_addr += head_params.head_len as u64; // Put the descriptor index 0 in the first available ring position. @@ -429,13 +427,13 @@ pub mod tests { for i in 0..(data_chain_len) { // last descr in chain if i == data_chain_len - 1 { - head_flags &= !VIRTQ_DESC_F_NEXT; + head_flags &= !VRING_DESC_F_NEXT; } // vsock data let data = vec![0_u8; head_data_len as usize]; - let data_desc = Descriptor::new(next_addr, data.len() as u32, head_flags, i + 2); + let data_desc = Descriptor::new(next_addr, data.len() as u32, head_flags as u16, i + 2); mem.write(&data, data_desc.addr()).unwrap(); - virt_queue.desc_table().store(i + 1, data_desc); + assert!(virt_queue.desc_table().store(i + 1, data_desc).is_ok()); next_addr += head_data_len as u64; } @@ -443,8 +441,9 @@ pub mod tests { ( GuestMemoryAtomic::new(mem.clone()), virt_queue - .create_queue(GuestMemoryAtomic::::new(mem.clone())) - .iter() + .create_queue::() + .unwrap() + .iter(GuestMemoryAtomic::new(mem.clone()).memory()) .unwrap() .next() .unwrap(), diff --git a/vsock/src/vhu_vsock_thread.rs b/vsock/src/vhu_vsock_thread.rs index aacae72..796f6ce 100644 --- a/vsock/src/vhu_vsock_thread.rs +++ b/vsock/src/vhu_vsock_thread.rs @@ -19,7 +19,8 @@ use std::{ sync::{Arc, RwLock}, }; use vhost_user_backend::{VringEpollHandler, VringRwLock, VringT}; -use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; +use virtio_queue::QueueOwnedT; +use vm_memory::{GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap}; use vmm_sys_util::{ epoll::EventSet, eventfd::{EventFd, EFD_NONBLOCK}, @@ -374,7 +375,11 @@ impl VhostUserVsockThread { let queue = vring_mut.get_queue_mut(); - while let Some(mut avail_desc) = queue.iter().map_err(|_| Error::IterateQueue)?.next() { + while let Some(mut avail_desc) = queue + .iter(atomic_mem.memory()) + .map_err(|_| Error::IterateQueue)? + .next() + { used_any = true; let atomic_mem = atomic_mem.clone(); @@ -385,7 +390,10 @@ impl VhostUserVsockThread { if self.thread_backend.recv_pkt(&mut pkt).is_ok() { pkt.hdr().len() + pkt.len() as usize } else { - queue.iter().unwrap().go_to_previous_position(); + queue + .iter(atomic_mem.memory()) + .unwrap() + .go_to_previous_position(); break; } } @@ -471,7 +479,7 @@ impl VhostUserVsockThread { while let Some(mut avail_desc) = vring .get_mut() .get_queue_mut() - .iter() + .iter(atomic_mem.memory()) .map_err(|_| Error::IterateQueue)? .next() { @@ -491,7 +499,7 @@ impl VhostUserVsockThread { vring .get_mut() .get_queue_mut() - .iter() + .iter(atomic_mem.memory()) .unwrap() .go_to_previous_position(); break; From 896d346135dbaa2621d1d95147b0685fb06b621c Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Wed, 12 Oct 2022 11:04:00 +0200 Subject: [PATCH 04/16] vsock: replace the deprecated App::from_yaml clap::App::from_yaml() is deprecated since clap-3.0.0, let's switch to clap::Parser. Signed-off-by: Stefano Garzarella --- Cargo.lock | 33 ++++++++++++++++---------------- vsock/Cargo.toml | 2 +- vsock/src/cli.yaml | 36 ----------------------------------- vsock/src/main.rs | 9 +++------ vsock/src/vhu_vsock.rs | 43 +++++++++++++++++++++--------------------- 5 files changed, 42 insertions(+), 81 deletions(-) delete mode 100644 vsock/src/cli.yaml diff --git a/Cargo.lock b/Cargo.lock index 11fe8a3..af2a32d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,12 +66,13 @@ checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" dependencies = [ "atty", "bitflags", + "clap_derive 3.2.18", "clap_lex 0.2.4", "indexmap", + "once_cell", "strsim", "termcolor", "textwrap", - "yaml-rust", ] [[package]] @@ -82,13 +83,26 @@ checksum = "4ed45cc2c62a3eff523e718d8576ba762c83a3146151093283ac62ae11933a73" dependencies = [ "atty", "bitflags", - "clap_derive", + "clap_derive 4.0.10", "clap_lex 0.3.0", "once_cell", "strsim", "termcolor", ] +[[package]] +name = "clap_derive" +version = "3.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "clap_derive" version = "4.0.10" @@ -324,12 +338,6 @@ dependencies = [ "cc", ] -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[package]] name = "log" version = "0.4.17" @@ -754,12 +762,3 @@ 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 = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] diff --git a/vsock/Cargo.toml b/vsock/Cargo.toml index 282c116..572a35e 100644 --- a/vsock/Cargo.toml +++ b/vsock/Cargo.toml @@ -11,7 +11,7 @@ edition = "2018" [dependencies] byteorder = "1" -clap = { version = ">=3.0", features = ["yaml"] } +clap = { version = ">=3.0", features = ["derive"] } epoll = "4.3.1" futures = { version = "0.3", features = ["thread-pool"] } log = ">=0.4.6" diff --git a/vsock/src/cli.yaml b/vsock/src/cli.yaml deleted file mode 100644 index ef172a3..0000000 --- a/vsock/src/cli.yaml +++ /dev/null @@ -1,36 +0,0 @@ -name: vhost-device-vsock -version: "0.1.0" -author: "Harshavardhan Unnibhavi " -about: Virtio VSOCK backend daemon. - -settings: - - ArgRequiredElseHelp - -args: - # CID of the guest - - guest_cid: - long: guest-cid - value_name: INT - takes_value: true - help: Context identifier of the guest which uniquely identifies the device for its lifetime. Defaults to 3 if not specified. - # Socket paths - - uds_path: - long: uds-path - value_name: FILE - takes_value: true - help: Unix socket to which a host-side application connects to. - - socket: - long: socket - value_name: FILE - takes_value: true - help: Unix socket to which a hypervisor conencts to and sets up the control path with the device. - -groups: - - required_args: - args: - - guest_cid - args: - - uds_path - args: - - socket - required: true diff --git a/vsock/src/main.rs b/vsock/src/main.rs index cc3b8b1..8439e98 100644 --- a/vsock/src/main.rs +++ b/vsock/src/main.rs @@ -7,7 +7,7 @@ mod vhu_vsock; mod vhu_vsock_thread; mod vsock_conn; -use clap::{load_yaml, App}; +use clap::Parser; use std::{ convert::TryFrom, process, @@ -15,7 +15,7 @@ use std::{ }; use vhost::{vhost_user, vhost_user::Listener}; use vhost_user_backend::VhostUserDaemon; -use vhu_vsock::{VhostUserVsockBackend, VsockConfig}; +use vhu_vsock::{VhostUserVsockBackend, VsockArgs, VsockConfig}; use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; /// This is the public API through which an external program starts the @@ -78,10 +78,7 @@ pub(crate) fn start_backend_server(vsock_config: VsockConfig) { } fn main() { - let yaml = load_yaml!("cli.yaml"); - let vsock_args = App::from_yaml(yaml).get_matches(); - - let vsock_config = VsockConfig::try_from(vsock_args).unwrap(); + let vsock_config = VsockConfig::try_from(VsockArgs::parse()).unwrap(); start_backend_server(vsock_config); } diff --git a/vsock/src/vhu_vsock.rs b/vsock/src/vhu_vsock.rs index eb50c39..b86494b 100644 --- a/vsock/src/vhu_vsock.rs +++ b/vsock/src/vhu_vsock.rs @@ -1,5 +1,5 @@ use super::vhu_vsock_thread::*; -use clap::ArgMatches; +use clap::Parser; use core::slice; use std::convert::TryFrom; use std::{io, mem, result, sync::Mutex, u16, u32, u64, u8}; @@ -116,10 +116,6 @@ pub(crate) enum Error { NoFreeLocalPort, #[error("Backend rx queue is empty")] EmptyBackendRxQ, - #[error("Invalid socket path as cmd line argument")] - SocketPathMissing, - #[error("Invalid UDS path as cmd line argument")] - UDSPathMissing, } impl std::convert::From for std::io::Error { @@ -128,6 +124,22 @@ impl std::convert::From for std::io::Error { } } +#[derive(Parser, Debug)] +#[clap(version, about, long_about = None)] +pub(crate) struct VsockArgs { + /// Context identifier of the guest which uniquely identifies the device for its lifetime. + #[clap(long, default_value_t = 3)] + guest_cid: u64, + + /// Unix socket to which a hypervisor connects to and sets up the control path with the device. + #[clap(long)] + socket: String, + + /// Unix socket to which a host-side application connects to. + #[clap(long)] + uds_path: String, +} + #[derive(Debug, Clone)] /// This structure is the public API through which an external program /// is allowed to configure the backend. @@ -166,25 +178,14 @@ impl VsockConfig { } } -impl TryFrom for VsockConfig { +impl TryFrom for VsockConfig { type Error = Error; - fn try_from(cmd_args: ArgMatches) -> Result { - let guest_cid = cmd_args - .value_of("guest_cid") - .unwrap_or("3") - .parse::() - .unwrap_or(3); - let socket = cmd_args - .value_of("socket") - .ok_or(Error::SocketPathMissing)? - .to_string(); - let uds_path = cmd_args - .value_of("uds_path") - .ok_or(Error::UDSPathMissing)? - .to_string(); + fn try_from(cmd_args: VsockArgs) -> Result { + let socket = cmd_args.socket.trim().to_string(); + let uds_path = cmd_args.uds_path.trim().to_string(); - Ok(VsockConfig::new(guest_cid, socket, uds_path)) + Ok(VsockConfig::new(cmd_args.guest_cid, socket, uds_path)) } } From df1073bf2e8ef616559543a55db531f81b7f2d73 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Mon, 26 Sep 2022 20:20:11 +0200 Subject: [PATCH 05/16] vsock: use VsockPacket from the virtio-vsock crate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace our VsockPacket with the new one implemented in the virtio-vsock crate. Based on Alex Bennée and Laura Loghin commit: https://github.com/stsquad/vhost-device/commit/6752266608dd To achieve this the following changes where made: - deleted the internal packet.rs - convert the send_pkt/recv_pkt helpers to be BitmapSlice aware - update push from LocalTxBuf - tweak a few API calls due to minor diffs Fixed tests and moved some helpers from the removed vsock/src/packet.rs file. Co-developed-by: Laura Loghin Co-developed-by: Alex Bennée Signed-off-by: Stefano Garzarella --- Cargo.lock | 11 + vsock/Cargo.toml | 1 + vsock/src/main.rs | 1 - vsock/src/packet.rs | 592 ---------------------------------- vsock/src/thread_backend.rs | 19 +- vsock/src/txbuf.rs | 26 +- vsock/src/vhu_vsock_thread.rs | 54 ++-- vsock/src/vsock_conn.rs | 193 ++++++++--- 8 files changed, 224 insertions(+), 673 deletions(-) delete mode 100644 vsock/src/packet.rs diff --git a/Cargo.lock b/Cargo.lock index af2a32d..228d38f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -683,6 +683,7 @@ dependencies = [ "vhost-user-backend", "virtio-bindings", "virtio-queue", + "virtio-vsock", "vm-memory", "vmm-sys-util", ] @@ -705,6 +706,16 @@ dependencies = [ "vmm-sys-util", ] +[[package]] +name = "virtio-vsock" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876299fc0f59e07aadc77541ce49dd75f7548f4d095eac6f7104b805394029e8" +dependencies = [ + "virtio-queue", + "vm-memory", +] + [[package]] name = "vm-memory" version = "0.9.0" diff --git a/vsock/Cargo.toml b/vsock/Cargo.toml index 572a35e..5e05376 100644 --- a/vsock/Cargo.toml +++ b/vsock/Cargo.toml @@ -20,6 +20,7 @@ vhost = { version = "0.5", features = ["vhost-user-slave"] } vhost-user-backend = "0.7.0" virtio-bindings = ">=0.1" virtio-queue = "0.6" +virtio-vsock = "0.1.0" vm-memory = ">=0.8" vmm-sys-util = "=0.10.0" diff --git a/vsock/src/main.rs b/vsock/src/main.rs index 8439e98..6f35f7f 100644 --- a/vsock/src/main.rs +++ b/vsock/src/main.rs @@ -1,4 +1,3 @@ -mod packet; mod rxops; mod rxqueue; mod thread_backend; diff --git a/vsock/src/packet.rs b/vsock/src/packet.rs deleted file mode 100644 index b30686a..0000000 --- a/vsock/src/packet.rs +++ /dev/null @@ -1,592 +0,0 @@ -#![deny(missing_docs)] -use byteorder::{ByteOrder, LittleEndian}; -use thiserror::Error as ThisError; -use virtio_queue::DescriptorChain; -use vm_memory::{ - GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, GuestMemoryLoadGuard, - GuestMemoryMmap, -}; - -pub(crate) type Result = std::result::Result; - -/// Below enum defines custom error types for vsock packet operations. -#[derive(Debug, PartialEq, ThisError)] -pub(crate) enum Error { - #[error("Descriptor not writable")] - UnwritableDescriptor, - #[error("Missing descriptor in queue")] - QueueMissingDescriptor, - #[error("Small header descriptor: {0}")] - HdrDescTooSmall(u32), - #[error("Chained guest memory error")] - GuestMemory, - #[error("Descriptor not readable")] - UnreadableDescriptor, - #[error("Extra descriptors in the descriptor chain")] - ExtraDescrInChain, - #[error("Data buffer size less than size in packet header")] - DataDescTooSmall, -} - -// TODO: Replace below with bindgen generated struct -// vsock packet header size when packed -pub const VSOCK_PKT_HDR_SIZE: usize = 44; - -// Offset into header for source cid -const HDROFF_SRC_CID: usize = 0; - -// Offset into header for destination cid -const HDROFF_DST_CID: usize = 8; - -// Offset into header for source port -const HDROFF_SRC_PORT: usize = 16; - -// Offset into header for destination port -const HDROFF_DST_PORT: usize = 20; - -// Offset into the header for data length -const HDROFF_LEN: usize = 24; - -// Offset into header for packet type -const HDROFF_TYPE: usize = 28; - -// Offset into header for operation kind -const HDROFF_OP: usize = 30; - -// Offset into header for additional flags -// only for VSOCK_OP_SHUTDOWN -const HDROFF_FLAGS: usize = 32; - -// Offset into header for tx buf alloc -const HDROFF_BUF_ALLOC: usize = 36; - -// Offset into header for forward count -const HDROFF_FWD_CNT: usize = 40; - -/// Vsock packet structure implemented as a wrapper around a virtq descriptor chain: -/// - chain head holds the packet header -/// - optional data descriptor, only present for data packets (VSOCK_OP_RW) -#[derive(Debug)] -pub struct VsockPacket { - hdr: *mut u8, - buf: Option<*mut u8>, - buf_size: usize, -} - -impl VsockPacket { - /// Create a vsock packet wrapper around a chain in the rx virtqueue. - /// Perform bounds checking before creating the wrapper. - pub(crate) fn from_rx_virtq_head( - chain: &mut DescriptorChain>>, - mem: GuestMemoryAtomic, - ) -> Result { - // head is at 0, next is at 1, max of two descriptors - // head contains the packet header - // next contains the optional packet data - let mut descr_vec = Vec::with_capacity(2); - - for descr in chain { - if !descr.is_write_only() { - return Err(Error::UnwritableDescriptor); - } - - descr_vec.push(descr); - } - - if descr_vec.len() < 2 { - // We expect a head and a data descriptor - return Err(Error::QueueMissingDescriptor); - } - - let head_descr = descr_vec[0]; - let data_descr = descr_vec[1]; - - if head_descr.len() < VSOCK_PKT_HDR_SIZE as u32 { - return Err(Error::HdrDescTooSmall(head_descr.len())); - } - - Ok(Self { - hdr: VsockPacket::guest_to_host_address( - &mem.memory(), - head_descr.addr(), - VSOCK_PKT_HDR_SIZE, - ) - .ok_or(Error::GuestMemory)? as *mut u8, - buf: Some( - VsockPacket::guest_to_host_address( - &mem.memory(), - data_descr.addr(), - data_descr.len() as usize, - ) - .ok_or(Error::GuestMemory)? as *mut u8, - ), - buf_size: data_descr.len() as usize, - }) - } - - /// Create a vsock packet wrapper around a chain in the tx virtqueue - /// Bounds checking before creating the wrapper. - pub(crate) fn from_tx_virtq_head( - chain: &mut DescriptorChain>>, - mem: GuestMemoryAtomic, - ) -> Result { - // head is at 0, next is at 1, max of two descriptors - // head contains the packet header - // next contains the optional packet data - let mut descr_vec = Vec::with_capacity(2); - // let mut num_descr = 0; - - for descr in chain { - if descr.is_write_only() { - return Err(Error::UnreadableDescriptor); - } - - descr_vec.push(descr); - } - - if descr_vec.len() > 2 { - return Err(Error::ExtraDescrInChain); - } - - let head_descr = descr_vec[0]; - - if head_descr.len() < VSOCK_PKT_HDR_SIZE as u32 { - return Err(Error::HdrDescTooSmall(head_descr.len())); - } - - let mut pkt = Self { - hdr: VsockPacket::guest_to_host_address( - &mem.memory(), - head_descr.addr(), - VSOCK_PKT_HDR_SIZE, - ) - .ok_or(Error::GuestMemory)? as *mut u8, - buf: None, - buf_size: 0, - }; - - // Zero length packet - if pkt.is_empty() { - return Ok(pkt); - } - - // There exists packet data as well - let data_descr = descr_vec[1]; - - // Data buffer should be as large as described in the header - if data_descr.len() < pkt.len() { - return Err(Error::DataDescTooSmall); - } - - pkt.buf_size = data_descr.len() as usize; - pkt.buf = Some( - VsockPacket::guest_to_host_address( - &mem.memory(), - data_descr.addr(), - data_descr.len() as usize, - ) - .ok_or(Error::GuestMemory)? as *mut u8, - ); - - Ok(pkt) - } - - /// Convert an absolute address in guest address space to a host - /// pointer and verify that the provided size defines a valid - /// range within a single memory region. - fn guest_to_host_address( - mem: &GuestMemoryLoadGuard, - addr: GuestAddress, - size: usize, - ) -> Option<*mut u8> { - if mem.check_range(addr, size) { - Some(mem.get_host_address(addr).unwrap()) - } else { - None - } - } - - /// In place byte slice access to vsock packet header. - pub fn hdr(&self) -> &[u8] { - // Safe as bound checks performed in from_*_virtq_head - unsafe { std::slice::from_raw_parts(self.hdr as *const u8, VSOCK_PKT_HDR_SIZE) } - } - - /// In place mutable slice access to vsock packet header. - pub fn hdr_mut(&mut self) -> &mut [u8] { - // Safe as bound checks performed in from_*_virtq_head - unsafe { std::slice::from_raw_parts_mut(self.hdr, VSOCK_PKT_HDR_SIZE) } - } - - /// Size of vsock packet data, found by accessing len field - /// of virtio_vsock_hdr struct. - pub fn len(&self) -> u32 { - LittleEndian::read_u32(&self.hdr()[HDROFF_LEN..]) - } - - /// Set the source cid. - pub fn set_src_cid(&mut self, cid: u64) -> &mut Self { - LittleEndian::write_u64(&mut self.hdr_mut()[HDROFF_SRC_CID..], cid); - self - } - - /// Set the destination cid. - pub fn set_dst_cid(&mut self, cid: u64) -> &mut Self { - LittleEndian::write_u64(&mut self.hdr_mut()[HDROFF_DST_CID..], cid); - self - } - - /// Set source port. - pub fn set_src_port(&mut self, port: u32) -> &mut Self { - LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_SRC_PORT..], port); - self - } - - /// Set destination port. - pub fn set_dst_port(&mut self, port: u32) -> &mut Self { - LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_DST_PORT..], port); - self - } - - /// Set type of connection. - pub fn set_type(&mut self, type_: u16) -> &mut Self { - LittleEndian::write_u16(&mut self.hdr_mut()[HDROFF_TYPE..], type_); - self - } - - /// Set size of tx buf. - pub fn set_buf_alloc(&mut self, buf_alloc: u32) -> &mut Self { - LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_BUF_ALLOC..], buf_alloc); - self - } - - /// Set amount of tx buf data written to stream. - pub fn set_fwd_cnt(&mut self, fwd_cnt: u32) -> &mut Self { - LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_FWD_CNT..], fwd_cnt); - self - } - - /// Set packet operation ID. - pub fn set_op(&mut self, op: u16) -> &mut Self { - LittleEndian::write_u16(&mut self.hdr_mut()[HDROFF_OP..], op); - self - } - - /// Check if the packet has no data. - fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Get destination port from packet. - pub fn dst_port(&self) -> u32 { - LittleEndian::read_u32(&self.hdr()[HDROFF_DST_PORT..]) - } - - /// Get source port from packet. - pub fn src_port(&self) -> u32 { - LittleEndian::read_u32(&self.hdr()[HDROFF_SRC_PORT..]) - } - - /// Get source cid from packet. - pub fn src_cid(&self) -> u64 { - LittleEndian::read_u64(&self.hdr()[HDROFF_SRC_CID..]) - } - - /// Get destination cid from packet. - pub fn dst_cid(&self) -> u64 { - LittleEndian::read_u64(&self.hdr()[HDROFF_DST_CID..]) - } - - /// Get packet type. - pub fn pkt_type(&self) -> u16 { - LittleEndian::read_u16(&self.hdr()[HDROFF_TYPE..]) - } - - /// Get operation requested in the packet. - pub fn op(&self) -> u16 { - LittleEndian::read_u16(&self.hdr()[HDROFF_OP..]) - } - - /// Byte slice mutable access to vsock packet data buffer. - pub fn buf_mut(&mut self) -> Option<&mut [u8]> { - // Safe as bound checks performed while creating packet - self.buf - .map(|ptr| unsafe { std::slice::from_raw_parts_mut(ptr, self.buf_size) }) - } - - /// Byte slice access to vsock packet data buffer. - pub fn buf(&self) -> Option<&[u8]> { - // Safe as bound checks performed while creating packet - self.buf - .map(|ptr| unsafe { std::slice::from_raw_parts(ptr as *const u8, self.buf_size) }) - } - - /// Set data buffer length. - pub fn set_len(&mut self, len: u32) -> &mut Self { - LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_LEN..], len); - self - } - - /// Read buf alloc. - pub fn buf_alloc(&self) -> u32 { - LittleEndian::read_u32(&self.hdr()[HDROFF_BUF_ALLOC..]) - } - - /// Get fwd cnt from packet header. - pub fn fwd_cnt(&self) -> u32 { - LittleEndian::read_u32(&self.hdr()[HDROFF_FWD_CNT..]) - } - - /// Read flags from the packet header. - pub fn flags(&self) -> u32 { - LittleEndian::read_u32(&self.hdr()[HDROFF_FLAGS..]) - } - - /// Set packet header flag to flags. - pub fn set_flags(&mut self, flags: u32) -> &mut Self { - LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_FLAGS..], flags); - self - } - - /// Set OP specific flags. - pub fn set_flag(&mut self, flag: u32) -> &mut Self { - self.set_flags(self.flags() | flag); - self - } -} - -#[cfg(test)] -pub mod tests { - use crate::vhu_vsock::{VSOCK_OP_RW, VSOCK_TYPE_STREAM}; - - use super::*; - use virtio_bindings::bindings::virtio_ring::{VRING_DESC_F_NEXT, VRING_DESC_F_WRITE}; - use virtio_queue::{mock::MockSplitQueue, Descriptor, Queue, QueueOwnedT}; - use vm_memory::{Address, Bytes, GuestAddress, GuestMemoryAtomic, GuestMemoryMmap}; - - pub struct HeadParams { - head_len: usize, - data_len: u32, - } - - impl HeadParams { - pub fn new(head_len: usize, data_len: u32) -> Self { - Self { head_len, data_len } - } - fn construct_head(&self) -> Vec { - let mut header = vec![0_u8; self.head_len]; - if self.head_len == VSOCK_PKT_HDR_SIZE { - LittleEndian::write_u32(&mut header[HDROFF_LEN..], self.data_len); - } - header - } - } - - pub fn prepare_desc_chain_vsock( - write_only: bool, - head_params: &HeadParams, - data_chain_len: u16, - head_data_len: u32, - ) -> ( - GuestMemoryAtomic, - DescriptorChain>, - ) { - let mem = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(); - let virt_queue = MockSplitQueue::new(&mem, 16); - let mut next_addr = virt_queue.desc_table().total_size() + 0x100; - let mut flags = 0; - - if write_only { - flags |= VRING_DESC_F_WRITE; - } - - let mut head_flags = if data_chain_len > 0 { - flags | VRING_DESC_F_NEXT - } else { - flags - }; - - // vsock packet header - // let header = vec![0 as u8; head_params.head_len]; - let header = head_params.construct_head(); - let head_desc = - Descriptor::new(next_addr, head_params.head_len as u32, head_flags as u16, 1); - mem.write(&header, head_desc.addr()).unwrap(); - assert!(virt_queue.desc_table().store(0, head_desc).is_ok()); - next_addr += head_params.head_len as u64; - - // Put the descriptor index 0 in the first available ring position. - mem.write_obj(0u16, virt_queue.avail_addr().unchecked_add(4)) - .unwrap(); - - // Set `avail_idx` to 1. - mem.write_obj(1u16, virt_queue.avail_addr().unchecked_add(2)) - .unwrap(); - - // chain len excludes the head - for i in 0..(data_chain_len) { - // last descr in chain - if i == data_chain_len - 1 { - head_flags &= !VRING_DESC_F_NEXT; - } - // vsock data - let data = vec![0_u8; head_data_len as usize]; - let data_desc = Descriptor::new(next_addr, data.len() as u32, head_flags as u16, i + 2); - mem.write(&data, data_desc.addr()).unwrap(); - assert!(virt_queue.desc_table().store(i + 1, data_desc).is_ok()); - next_addr += head_data_len as u64; - } - - // Create descriptor chain from pre-filled memory - ( - GuestMemoryAtomic::new(mem.clone()), - virt_queue - .create_queue::() - .unwrap() - .iter(GuestMemoryAtomic::new(mem.clone()).memory()) - .unwrap() - .next() - .unwrap(), - ) - } - - #[test] - fn test_guest_to_host_address() { - let mem = GuestMemoryAtomic::new( - GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 1000)]).unwrap(), - ); - assert!(VsockPacket::guest_to_host_address(&mem.memory(), GuestAddress(0), 1000).is_some()); - assert!(VsockPacket::guest_to_host_address(&mem.memory(), GuestAddress(0), 500).is_some()); - assert!( - VsockPacket::guest_to_host_address(&mem.memory(), GuestAddress(500), 500).is_some() - ); - assert!( - VsockPacket::guest_to_host_address(&mem.memory(), GuestAddress(501), 500).is_none() - ); - } - - #[test] - fn test_from_rx_virtq_head() { - // parameters for packet head construction - let head_params = HeadParams::new(VSOCK_PKT_HDR_SIZE, 10); - - // write only descriptor chain - let (mem, mut descr_chain) = prepare_desc_chain_vsock(true, &head_params, 2, 10); - assert!(VsockPacket::from_rx_virtq_head(&mut descr_chain, mem).is_ok()); - - // read only descriptor chain - let (mem, mut descr_chain) = prepare_desc_chain_vsock(false, &head_params, 1, 10); - assert_eq!( - VsockPacket::from_rx_virtq_head(&mut descr_chain, mem).unwrap_err(), - Error::UnwritableDescriptor - ); - - // less than two descriptors - let (mem, mut descr_chain) = prepare_desc_chain_vsock(true, &head_params, 0, 10); - assert_eq!( - VsockPacket::from_rx_virtq_head(&mut descr_chain, mem).unwrap_err(), - Error::QueueMissingDescriptor - ); - - // incorrect header length - let head_params = HeadParams::new(22, 10); - let (mem, mut descr_chain) = prepare_desc_chain_vsock(true, &head_params, 1, 10); - assert_eq!( - VsockPacket::from_rx_virtq_head(&mut descr_chain, mem).unwrap_err(), - Error::HdrDescTooSmall(22) - ); - } - - #[test] - fn test_vsock_packet_header_ops() { - // parameters for head construction - let head_params = HeadParams::new(VSOCK_PKT_HDR_SIZE, 10); - - let (mem, mut descr_chain) = prepare_desc_chain_vsock(true, &head_params, 2, 10); - let mut vsock_packet = VsockPacket::from_rx_virtq_head(&mut descr_chain, mem).unwrap(); - - // Check packet data's length - assert!(!vsock_packet.is_empty()); - assert_eq!(vsock_packet.len(), 10); - - // Set and get the source CID in the packet header - vsock_packet.set_src_cid(1); - assert_eq!(vsock_packet.src_cid(), 1); - - // Set and get the destination CID in the packet header - vsock_packet.set_dst_cid(1); - assert_eq!(vsock_packet.dst_cid(), 1); - - // Set and get the source port in the packet header - vsock_packet.set_src_port(5000); - assert_eq!(vsock_packet.src_port(), 5000); - - // Set and get the destination port in the packet header - vsock_packet.set_dst_port(5000); - assert_eq!(vsock_packet.dst_port(), 5000); - - // Set and get packet type - vsock_packet.set_type(VSOCK_TYPE_STREAM); - assert_eq!(vsock_packet.pkt_type(), VSOCK_TYPE_STREAM); - - // Set and get tx buffer size - vsock_packet.set_buf_alloc(10); - assert_eq!(vsock_packet.buf_alloc(), 10); - - // Set and get fwd_cnt of packet's data - vsock_packet.set_fwd_cnt(100); - assert_eq!(vsock_packet.fwd_cnt(), 100); - - // Set and get packet operation type - vsock_packet.set_op(VSOCK_OP_RW); - assert_eq!(vsock_packet.op(), VSOCK_OP_RW); - - // Set and get length of packet's data buffer - // this is a dummy test - vsock_packet.set_len(20); - assert_eq!(vsock_packet.len(), 20); - assert!(!vsock_packet.is_empty()); - - // Set and get packet's flags - vsock_packet.set_flags(1); - assert_eq!(vsock_packet.flags(), 1); - } - - #[test] - fn test_from_tx_virtq_head() { - // parameters for head construction - let head_params = HeadParams::new(VSOCK_PKT_HDR_SIZE, 0); - - // read only descriptor chain no data - let (mem, mut descr_chain) = prepare_desc_chain_vsock(false, &head_params, 0, 0); - assert!(VsockPacket::from_tx_virtq_head(&mut descr_chain, mem).is_ok()); - - // parameters for head construction - let head_params = HeadParams::new(VSOCK_PKT_HDR_SIZE, 10); - - // read only descriptor chain - let (mem, mut descr_chain) = prepare_desc_chain_vsock(false, &head_params, 1, 10); - assert!(VsockPacket::from_tx_virtq_head(&mut descr_chain, mem).is_ok()); - - // write only descriptor chain - let (mem, mut descr_chain) = prepare_desc_chain_vsock(true, &head_params, 1, 10); - assert_eq!( - VsockPacket::from_tx_virtq_head(&mut descr_chain, mem).unwrap_err(), - Error::UnreadableDescriptor - ); - - // more than 2 descriptors in chain - let (mem, mut descr_chain) = prepare_desc_chain_vsock(false, &head_params, 2, 10); - assert_eq!( - VsockPacket::from_tx_virtq_head(&mut descr_chain, mem).unwrap_err(), - Error::ExtraDescrInChain - ); - - // length of data descriptor does not match the value in head - let (mem, mut descr_chain) = prepare_desc_chain_vsock(false, &head_params, 1, 5); - assert_eq!( - VsockPacket::from_tx_virtq_head(&mut descr_chain, mem).unwrap_err(), - Error::DataDescTooSmall - ); - } -} diff --git a/vsock/src/thread_backend.rs b/vsock/src/thread_backend.rs index 8351003..50c853f 100644 --- a/vsock/src/thread_backend.rs +++ b/vsock/src/thread_backend.rs @@ -1,7 +1,6 @@ #![deny(missing_docs)] use super::{ - packet::*, rxops::*, vhu_vsock::{ ConnMapKey, Error, Result, VSOCK_HOST_CID, VSOCK_OP_REQUEST, VSOCK_OP_RST, @@ -18,6 +17,8 @@ use std::{ prelude::{AsRawFd, FromRawFd, RawFd}, }, }; +use virtio_vsock::packet::VsockPacket; +use vm_memory::bitmap::BitmapSlice; // TODO: convert UnixStream to Arc> pub struct VsockThreadBackend { @@ -63,7 +64,7 @@ impl VsockThreadBackend { /// Returns: /// - `Ok(())` if the packet was successfully filled in /// - `Err(Error::EmptyBackendRxQ) if there was no available data - pub(crate) fn recv_pkt(&mut self, pkt: &mut VsockPacket) -> Result<()> { + pub(crate) fn recv_pkt(&mut self, pkt: &mut VsockPacket) -> Result<()> { // Pop an event from the backend_rxq let key = self.backend_rxq.pop_front().ok_or(Error::EmptyBackendRxQ)?; let conn = match self.conn_map.get_mut(&key) { @@ -117,11 +118,11 @@ impl VsockThreadBackend { /// /// Returns: /// - always `Ok(())` if packet has been consumed correctly - pub(crate) fn send_pkt(&mut self, pkt: &VsockPacket) -> Result<()> { + pub(crate) fn send_pkt(&mut self, pkt: &VsockPacket) -> Result<()> { let key = ConnMapKey::new(pkt.dst_port(), pkt.src_port()); // TODO: Rst if packet has unsupported type - if pkt.pkt_type() != VSOCK_TYPE_STREAM { + if pkt.type_() != VSOCK_TYPE_STREAM { info!("vsock: dropping packet of unknown type"); return Ok(()); } @@ -130,7 +131,7 @@ impl VsockThreadBackend { if pkt.dst_cid() != VSOCK_HOST_CID { info!( "vsock: dropping packet for cid other than host: {:?}", - pkt.hdr() + pkt.dst_cid() ); return Ok(()); @@ -186,7 +187,7 @@ impl VsockThreadBackend { /// Attempts to connect to a host side unix socket listening on a path /// corresponding to the destination port as follows: /// - "{self.host_sock_path}_{local_port}"" - fn handle_new_guest_conn(&mut self, pkt: &VsockPacket) { + fn handle_new_guest_conn(&mut self, pkt: &VsockPacket) { let port_path = format!("{}_{}", self.host_socket_path, pkt.dst_port()); UnixStream::connect(port_path) @@ -197,7 +198,11 @@ impl VsockThreadBackend { } /// Wrapper to add new connection to relevant HashMaps. - fn add_new_guest_conn(&mut self, stream: UnixStream, pkt: &VsockPacket) -> Result<()> { + fn add_new_guest_conn( + &mut self, + stream: UnixStream, + pkt: &VsockPacket, + ) -> Result<()> { let stream_fd = stream.as_raw_fd(); self.listener_map .insert(stream_fd, ConnMapKey::new(pkt.dst_port(), pkt.src_port())); diff --git a/vsock/src/txbuf.rs b/vsock/src/txbuf.rs index 012110b..40c935c 100644 --- a/vsock/src/txbuf.rs +++ b/vsock/src/txbuf.rs @@ -1,5 +1,6 @@ use super::vhu_vsock::{Error, Result, CONN_TX_BUF_SIZE}; use std::{io::Write, num::Wrapping}; +use vm_memory::{bitmap::BitmapSlice, VolatileSlice}; #[derive(Debug)] pub struct LocalTxBuf { @@ -28,7 +29,7 @@ impl LocalTxBuf { /// Add new data to the tx buffer, push all or none. /// Returns LocalTxBufFull error if space not sufficient. - pub(crate) fn push(&mut self, data_buf: &[u8]) -> Result<()> { + pub(crate) fn push(&mut self, data_buf: &VolatileSlice) -> Result<()> { if CONN_TX_BUF_SIZE as usize - self.len() < data_buf.len() { // Tx buffer is full return Err(Error::LocalTxBufFull); @@ -39,11 +40,13 @@ impl LocalTxBuf { // Check if we can fit the data buffer between head and end of buffer let len = std::cmp::min(CONN_TX_BUF_SIZE as usize - tail_idx, data_buf.len()); - self.buf[tail_idx..tail_idx + len].copy_from_slice(&data_buf[..len]); + let txbuf = &mut self.buf[tail_idx..tail_idx + len]; + data_buf.copy_to(txbuf); // Check if there is more data to be wrapped around if len < data_buf.len() { - self.buf[..(data_buf.len() - len)].copy_from_slice(&data_buf[len..]); + let remain_txbuf = &mut self.buf[..(data_buf.len() - len)]; + data_buf.copy_to(remain_txbuf); } // Increment tail by the amount of data that has been added to the buffer @@ -124,7 +127,8 @@ mod tests { #[test] fn test_txbuf_push() { let mut loc_tx_buf = LocalTxBuf::new(); - let data = [0; CONN_TX_BUF_SIZE as usize]; + let mut buf = [0; CONN_TX_BUF_SIZE as usize]; + let data = unsafe { VolatileSlice::new(buf.as_mut_ptr(), buf.len()) }; // push data into empty tx buffer let res_push = loc_tx_buf.push(&data); @@ -143,7 +147,8 @@ mod tests { assert_eq!(loc_tx_buf.tail, Wrapping(CONN_TX_BUF_SIZE * 2)); // only tail wraps at full - let data = vec![1; 4]; + let mut buf = vec![1; 4]; + let data = unsafe { VolatileSlice::new(buf.as_mut_ptr(), buf.len()) }; let mut cmp_data = vec![1; 4]; cmp_data.append(&mut vec![0; (CONN_TX_BUF_SIZE - 4) as usize]); loc_tx_buf.head = Wrapping(4); @@ -160,7 +165,8 @@ mod tests { let mut loc_tx_buf = LocalTxBuf::new(); // data to be flushed - let data = vec![1; CONN_TX_BUF_SIZE as usize]; + let mut buf = vec![1; CONN_TX_BUF_SIZE as usize]; + let data = unsafe { VolatileSlice::new(buf.as_mut_ptr(), buf.len()) }; // target to which data is flushed let mut cmp_vec = Vec::with_capacity(data.len()); @@ -178,12 +184,14 @@ mod tests { assert_eq!(loc_tx_buf.head, Wrapping(n as u32)); assert_eq!(loc_tx_buf.tail, Wrapping(CONN_TX_BUF_SIZE)); assert_eq!(n, cmp_vec.len()); - assert_eq!(cmp_vec, data[..n]); + assert_eq!(cmp_vec, buf[..n]); } // wrapping head flush - let mut data = vec![0; (CONN_TX_BUF_SIZE / 2) as usize]; - data.append(&mut vec![1; (CONN_TX_BUF_SIZE / 2) as usize]); + let mut buf = vec![0; (CONN_TX_BUF_SIZE / 2) as usize]; + buf.append(&mut vec![1; (CONN_TX_BUF_SIZE / 2) as usize]); + let data = unsafe { VolatileSlice::new(buf.as_mut_ptr(), buf.len()) }; + loc_tx_buf.head = Wrapping(0); loc_tx_buf.tail = Wrapping(0); let res_push = loc_tx_buf.push(&data); diff --git a/vsock/src/vhu_vsock_thread.rs b/vsock/src/vhu_vsock_thread.rs index 796f6ce..0e3c59c 100644 --- a/vsock/src/vhu_vsock_thread.rs +++ b/vsock/src/vhu_vsock_thread.rs @@ -1,8 +1,10 @@ use super::{ - packet::*, rxops::*, thread_backend::*, - vhu_vsock::{ConnMapKey, Error, Result, VhostUserVsockBackend, BACKEND_EVENT, VSOCK_HOST_CID}, + vhu_vsock::{ + ConnMapKey, Error, Result, VhostUserVsockBackend, BACKEND_EVENT, CONN_TX_BUF_SIZE, + VSOCK_HOST_CID, + }, vsock_conn::*, }; use futures::executor::{ThreadPool, ThreadPoolBuilder}; @@ -12,6 +14,7 @@ use std::{ io, io::Read, num::Wrapping, + ops::Deref, os::unix::{ net::{UnixListener, UnixStream}, prelude::{AsRawFd, FromRawFd, RawFd}, @@ -20,6 +23,7 @@ use std::{ }; use vhost_user_backend::{VringEpollHandler, VringRwLock, VringT}; use virtio_queue::QueueOwnedT; +use virtio_vsock::packet::{VsockPacket, PKT_HEADER_SIZE}; use vm_memory::{GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap}; use vmm_sys_util::{ epoll::EventSet, @@ -381,27 +385,27 @@ impl VhostUserVsockThread { .next() { used_any = true; - let atomic_mem = atomic_mem.clone(); + let mem = atomic_mem.clone().memory(); let head_idx = avail_desc.head_index(); - let used_len = - match VsockPacket::from_rx_virtq_head(&mut avail_desc, atomic_mem.clone()) { - Ok(mut pkt) => { - if self.thread_backend.recv_pkt(&mut pkt).is_ok() { - pkt.hdr().len() + pkt.len() as usize - } else { - queue - .iter(atomic_mem.memory()) - .unwrap() - .go_to_previous_position(); - break; - } + let used_len = match VsockPacket::from_rx_virtq_chain( + mem.deref(), + &mut avail_desc, + CONN_TX_BUF_SIZE, + ) { + Ok(mut pkt) => { + if self.thread_backend.recv_pkt(&mut pkt).is_ok() { + PKT_HEADER_SIZE + pkt.len() as usize + } else { + queue.iter(mem).unwrap().go_to_previous_position(); + break; } - Err(e) => { - warn!("vsock: RX queue error: {:?}", e); - 0 - } - }; + } + Err(e) => { + warn!("vsock: RX queue error: {:?}", e); + 0 + } + }; let vring = vring.clone(); let event_idx = self.event_idx; @@ -484,10 +488,14 @@ impl VhostUserVsockThread { .next() { used_any = true; - let atomic_mem = atomic_mem.clone(); + let mem = atomic_mem.clone().memory(); let head_idx = avail_desc.head_index(); - let pkt = match VsockPacket::from_tx_virtq_head(&mut avail_desc, atomic_mem.clone()) { + let pkt = match VsockPacket::from_tx_virtq_chain( + mem.deref(), + &mut avail_desc, + CONN_TX_BUF_SIZE, + ) { Ok(pkt) => pkt, Err(e) => { dbg!("vsock: error reading TX packet: {:?}", e); @@ -499,7 +507,7 @@ impl VhostUserVsockThread { vring .get_mut() .get_queue_mut() - .iter(atomic_mem.memory()) + .iter(mem) .unwrap() .go_to_previous_position(); break; diff --git a/vsock/src/vsock_conn.rs b/vsock/src/vsock_conn.rs index f3b590b..96418ba 100644 --- a/vsock/src/vsock_conn.rs +++ b/vsock/src/vsock_conn.rs @@ -1,5 +1,4 @@ use super::{ - packet::*, rxops::*, rxqueue::*, txbuf::*, @@ -16,6 +15,8 @@ use std::{ num::Wrapping, os::unix::prelude::{AsRawFd, RawFd}, }; +use virtio_vsock::packet::{VsockPacket, PKT_HEADER_SIZE}; +use vm_memory::{bitmap::BitmapSlice, Bytes, VolatileSlice}; #[derive(Debug)] pub struct VsockConnection { @@ -117,7 +118,7 @@ impl VsockConnection { /// Process a vsock packet that is meant for this connection. /// Forward data to the host-side application if the vsock packet /// contains a RW operation. - pub(crate) fn recv_pkt(&mut self, pkt: &mut VsockPacket) -> Result<()> { + pub(crate) fn recv_pkt(&mut self, pkt: &mut VsockPacket) -> Result<()> { // Initialize all fields in the packet header self.init_pkt(pkt); @@ -141,7 +142,7 @@ impl VsockConnection { pkt.set_op(VSOCK_OP_CREDIT_REQUEST); return Ok(()); } - let buf = pkt.buf_mut().ok_or(Error::PktBufMissing)?; + let buf = pkt.data_slice().ok_or(Error::PktBufMissing)?; // Perform a credit check to find the maximum read size. The read // data must fit inside a packet buffer and be within peer's @@ -149,7 +150,7 @@ impl VsockConnection { let max_read_len = std::cmp::min(buf.len(), self.peer_avail_credit()); // Read data from the stream directly into the buffer - if let Ok(read_cnt) = self.stream.read(&mut buf[..max_read_len]) { + if let Ok(read_cnt) = buf.read_from(0, &mut self.stream, max_read_len) { if read_cnt == 0 { // If no data was read then the stream was closed down unexpectedly. // Send a shutdown packet to the guest-side application. @@ -198,7 +199,7 @@ impl VsockConnection { /// /// Returns: /// - always `Ok(())` to indicate that the packet has been consumed - pub(crate) fn send_pkt(&mut self, pkt: &VsockPacket) -> Result<()> { + pub(crate) fn send_pkt(&mut self, pkt: &VsockPacket) -> Result<()> { // Update peer credit information self.peer_buf_alloc = pkt.buf_alloc(); self.peer_fwd_cnt = Wrapping(pkt.fwd_cnt()); @@ -213,19 +214,21 @@ impl VsockConnection { } VSOCK_OP_RW => { // Data has to be written to the host-side stream - if pkt.buf().is_none() { - info!( - "Dropping empty packet from guest (lp={}, pp={})", - self.local_port, self.peer_port - ); - return Ok(()); - } - - let buf_slice = &pkt.buf().unwrap()[..(pkt.len() as usize)]; - if let Err(err) = self.send_bytes(buf_slice) { - // TODO: Terminate this connection - dbg!("err:{:?}", err); - return Ok(()); + match pkt.data_slice() { + None => { + info!( + "Dropping empty packet from guest (lp={}, pp={})", + self.local_port, self.peer_port + ); + return Ok(()); + } + Some(buf) => { + if let Err(err) = self.send_bytes(buf) { + // TODO: Terminate this connection + dbg!("err:{:?}", err); + return Ok(()); + } + } } } VSOCK_OP_CREDIT_UPDATE => { @@ -271,7 +274,7 @@ impl VsockConnection { /// Returns: /// - Ok(cnt) where cnt is the number of bytes written to the stream /// - Err(Error::UnixWrite) if there was an error writing to the stream - fn send_bytes(&mut self, buf: &[u8]) -> Result<()> { + fn send_bytes(&mut self, buf: &VolatileSlice) -> Result<()> { if !self.tx_buf.is_empty() { // Data is already present in the buffer and the backend // is waiting for a EPOLLOUT event to flush it @@ -279,9 +282,9 @@ impl VsockConnection { } // Write data to the stream - let written_count = match self.stream.write(buf) { + let written_count = match buf.write_to(0, &mut self.stream, buf.len()) { Ok(cnt) => cnt, - Err(e) => { + Err(vm_memory::VolatileMemoryError::IOError(e)) => { if e.kind() == ErrorKind::WouldBlock { 0 } else { @@ -289,6 +292,10 @@ impl VsockConnection { return Err(Error::UnixWrite); } } + Err(e) => { + println!("send_bytes error: {:?}", e); + return Err(Error::UnixWrite); + } }; // Increment forwarded count by number of bytes written to the stream @@ -297,18 +304,19 @@ impl VsockConnection { self.rx_queue.enqueue(RxOps::CreditUpdate); if written_count != buf.len() { - return self.tx_buf.push(&buf[written_count..]); + return self.tx_buf.push(&buf.offset(written_count).unwrap()); } Ok(()) } /// Initialize all header fields in the vsock packet. - fn init_pkt<'a>(&self, pkt: &'a mut VsockPacket) -> &'a mut VsockPacket { + fn init_pkt<'a, 'b, B: BitmapSlice>( + &self, + pkt: &'a mut VsockPacket<'b, B>, + ) -> &'a mut VsockPacket<'b, B> { // Zero out the packet header - for b in pkt.hdr_mut() { - *b = 0; - } + pkt.set_header_from_raw(&[0u8; PKT_HEADER_SIZE]).unwrap(); pkt.set_src_cid(self.local_cid) .set_dst_cid(self.guest_cid) @@ -337,9 +345,103 @@ mod tests { use byteorder::{ByteOrder, LittleEndian}; use super::*; - use crate::packet::tests::{prepare_desc_chain_vsock, HeadParams}; - use crate::vhu_vsock::VSOCK_HOST_CID; + use crate::vhu_vsock::{VSOCK_HOST_CID, VSOCK_OP_RW, VSOCK_TYPE_STREAM}; use std::io::Result as IoResult; + use std::ops::Deref; + use virtio_bindings::bindings::virtio_ring::{VRING_DESC_F_NEXT, VRING_DESC_F_WRITE}; + use virtio_queue::{mock::MockSplitQueue, Descriptor, DescriptorChain, Queue, QueueOwnedT}; + use vm_memory::{ + Address, Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryLoadGuard, + GuestMemoryMmap, + }; + + struct HeadParams { + head_len: usize, + data_len: u32, + } + + impl HeadParams { + pub fn new(head_len: usize, data_len: u32) -> Self { + Self { head_len, data_len } + } + fn construct_head(&self) -> Vec { + let mut header = vec![0_u8; self.head_len]; + if self.head_len == PKT_HEADER_SIZE { + // Offset into the header for data length + const HDROFF_LEN: usize = 24; + LittleEndian::write_u32(&mut header[HDROFF_LEN..], self.data_len); + } + header + } + } + + fn prepare_desc_chain_vsock( + write_only: bool, + head_params: &HeadParams, + data_chain_len: u16, + head_data_len: u32, + ) -> ( + GuestMemoryAtomic, + DescriptorChain>, + ) { + let mem = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(); + let virt_queue = MockSplitQueue::new(&mem, 16); + let mut next_addr = virt_queue.desc_table().total_size() + 0x100; + let mut flags = 0; + + if write_only { + flags |= VRING_DESC_F_WRITE; + } + + let mut head_flags = if data_chain_len > 0 { + flags | VRING_DESC_F_NEXT + } else { + flags + }; + + // vsock packet header + // let header = vec![0 as u8; head_params.head_len]; + let header = head_params.construct_head(); + let head_desc = + Descriptor::new(next_addr, head_params.head_len as u32, head_flags as u16, 1); + mem.write(&header, head_desc.addr()).unwrap(); + assert!(virt_queue.desc_table().store(0, head_desc).is_ok()); + next_addr += head_params.head_len as u64; + + // Put the descriptor index 0 in the first available ring position. + mem.write_obj(0u16, virt_queue.avail_addr().unchecked_add(4)) + .unwrap(); + + // Set `avail_idx` to 1. + mem.write_obj(1u16, virt_queue.avail_addr().unchecked_add(2)) + .unwrap(); + + // chain len excludes the head + for i in 0..(data_chain_len) { + // last descr in chain + if i == data_chain_len - 1 { + head_flags &= !VRING_DESC_F_NEXT; + } + // vsock data + let data = vec![0_u8; head_data_len as usize]; + let data_desc = Descriptor::new(next_addr, data.len() as u32, head_flags as u16, i + 2); + mem.write(&data, data_desc.addr()).unwrap(); + assert!(virt_queue.desc_table().store(i + 1, data_desc).is_ok()); + next_addr += head_data_len as u64; + } + + // Create descriptor chain from pre-filled memory + ( + GuestMemoryAtomic::new(mem.clone()), + virt_queue + .create_queue::() + .unwrap() + .iter(GuestMemoryAtomic::new(mem.clone()).memory()) + .unwrap() + .next() + .unwrap(), + ) + } struct VsockDummySocket { data: Vec, @@ -435,7 +537,7 @@ mod tests { #[test] fn test_vsock_conn_init_pkt() { // parameters for packet head construction - let head_params = HeadParams::new(VSOCK_PKT_HDR_SIZE, 10); + let head_params = HeadParams::new(PKT_HEADER_SIZE, 10); // new locally inititated connection let dummy_file = VsockDummySocket::new(); @@ -444,7 +546,10 @@ mod tests { // write only descriptor chain let (mem, mut descr_chain) = prepare_desc_chain_vsock(true, &head_params, 2, 10); - let mut vsock_pkt = VsockPacket::from_rx_virtq_head(&mut descr_chain, mem).unwrap(); + let mem = mem.memory(); + let mut vsock_pkt = + VsockPacket::from_rx_virtq_chain(mem.deref(), &mut descr_chain, CONN_TX_BUF_SIZE) + .unwrap(); // initialize a vsock packet for the guest vsock_conn_local.init_pkt(&mut vsock_pkt); @@ -453,7 +558,7 @@ mod tests { assert_eq!(vsock_pkt.dst_cid(), 3); assert_eq!(vsock_pkt.src_port(), 5000); assert_eq!(vsock_pkt.dst_port(), 5001); - assert_eq!(vsock_pkt.pkt_type(), VSOCK_TYPE_STREAM); + assert_eq!(vsock_pkt.type_(), VSOCK_TYPE_STREAM); assert_eq!(vsock_pkt.buf_alloc(), CONN_TX_BUF_SIZE); assert_eq!(vsock_pkt.fwd_cnt(), 0); } @@ -461,7 +566,7 @@ mod tests { #[test] fn test_vsock_conn_recv_pkt() { // parameters for packet head construction - let head_params = HeadParams::new(VSOCK_PKT_HDR_SIZE, 5); + let head_params = HeadParams::new(PKT_HEADER_SIZE, 5); // new locally inititated connection let dummy_file = VsockDummySocket::new(); @@ -470,7 +575,10 @@ mod tests { // write only descriptor chain let (mem, mut descr_chain) = prepare_desc_chain_vsock(true, &head_params, 1, 5); - let mut vsock_pkt = VsockPacket::from_rx_virtq_head(&mut descr_chain, mem).unwrap(); + let mem = mem.memory(); + let mut vsock_pkt = + VsockPacket::from_rx_virtq_chain(mem.deref(), &mut descr_chain, CONN_TX_BUF_SIZE) + .unwrap(); // VSOCK_OP_REQUEST: new local conn request vsock_conn_local.rx_queue.enqueue(RxOps::Request); @@ -519,7 +627,9 @@ mod tests { assert_eq!(vsock_pkt.op(), VSOCK_OP_RW); assert!(!vsock_conn_local.rx_queue.pending_rx()); assert_eq!(vsock_pkt.len(), 5); - assert_eq!(vsock_pkt.buf().unwrap(), b"hello"); + let buf = &mut [0u8; 5]; + assert!(vsock_pkt.data_slice().unwrap().read_slice(buf, 0).is_ok()); + assert_eq!(buf, b"hello"); // VSOCK_OP_RESPONSE: response from a locally initiated connection vsock_conn_local.rx_queue.enqueue(RxOps::Response); @@ -545,7 +655,7 @@ mod tests { #[test] fn test_vsock_conn_send_pkt() { // parameters for packet head construction - let head_params = HeadParams::new(VSOCK_PKT_HDR_SIZE, 5); + let head_params = HeadParams::new(PKT_HEADER_SIZE, 5); // new locally inititated connection let dummy_file = VsockDummySocket::new(); @@ -554,13 +664,13 @@ mod tests { // write only descriptor chain let (mem, mut descr_chain) = prepare_desc_chain_vsock(false, &head_params, 1, 5); - let mut vsock_pkt = VsockPacket::from_tx_virtq_head(&mut descr_chain, mem).unwrap(); + let mem = mem.memory(); + let mut vsock_pkt = + VsockPacket::from_tx_virtq_chain(mem.deref(), &mut descr_chain, CONN_TX_BUF_SIZE) + .unwrap(); // peer credit information - const HDROFF_BUF_ALLOC: usize = 36; - const HDROFF_FWD_CNT: usize = 40; - LittleEndian::write_u32(&mut vsock_pkt.hdr_mut()[HDROFF_BUF_ALLOC..], 65536); - LittleEndian::write_u32(&mut vsock_pkt.hdr_mut()[HDROFF_FWD_CNT..], 1024); + vsock_pkt.set_buf_alloc(65536).set_fwd_cnt(1024); // check if peer credit information is updated currently let credit_check = vsock_conn_local.send_pkt(&vsock_pkt); @@ -579,7 +689,8 @@ mod tests { // VSOCK_OP_RW vsock_pkt.set_op(VSOCK_OP_RW); - vsock_pkt.buf_mut().unwrap().copy_from_slice(b"hello"); + let buf = b"hello"; + assert!(vsock_pkt.data_slice().unwrap().write_slice(buf, 0).is_ok()); let rw_response = vsock_conn_local.send_pkt(&vsock_pkt); assert!(rw_response.is_ok()); let mut resp_buf = vec![0; 5]; From 8fa597e94129c9bf53cfb2ab06cddeffc3e36c33 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Wed, 28 Sep 2022 13:34:37 +0200 Subject: [PATCH 06/16] vsock: add SPDX-License-Identifier in each file Harsha specified `license = "Apache-2.0 OR BSD-3-Clause"` in vsock/Cargo.toml, so let's add a properly SPDX tag in the vsock files. Signed-off-by: Stefano Garzarella --- vsock/src/main.rs | 2 ++ vsock/src/rxops.rs | 2 ++ vsock/src/rxqueue.rs | 2 ++ vsock/src/thread_backend.rs | 2 ++ vsock/src/txbuf.rs | 2 ++ vsock/src/vhu_vsock.rs | 2 ++ vsock/src/vhu_vsock_thread.rs | 2 ++ vsock/src/vsock_conn.rs | 2 ++ 8 files changed, 16 insertions(+) diff --git a/vsock/src/main.rs b/vsock/src/main.rs index 6f35f7f..9e433a1 100644 --- a/vsock/src/main.rs +++ b/vsock/src/main.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + mod rxops; mod rxqueue; mod thread_backend; diff --git a/vsock/src/rxops.rs b/vsock/src/rxops.rs index 3460a8d..7212c7f 100644 --- a/vsock/src/rxops.rs +++ b/vsock/src/rxops.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + #[derive(Clone, Copy, Eq, PartialEq, Debug)] pub enum RxOps { /// VSOCK_OP_REQUEST diff --git a/vsock/src/rxqueue.rs b/vsock/src/rxqueue.rs index 0a1dbda..10f8572 100644 --- a/vsock/src/rxqueue.rs +++ b/vsock/src/rxqueue.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + use super::rxops::RxOps; #[derive(Debug, Eq, PartialEq)] diff --git a/vsock/src/thread_backend.rs b/vsock/src/thread_backend.rs index 50c853f..436cd65 100644 --- a/vsock/src/thread_backend.rs +++ b/vsock/src/thread_backend.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + #![deny(missing_docs)] use super::{ diff --git a/vsock/src/txbuf.rs b/vsock/src/txbuf.rs index 40c935c..4c435b0 100644 --- a/vsock/src/txbuf.rs +++ b/vsock/src/txbuf.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + use super::vhu_vsock::{Error, Result, CONN_TX_BUF_SIZE}; use std::{io::Write, num::Wrapping}; use vm_memory::{bitmap::BitmapSlice, VolatileSlice}; diff --git a/vsock/src/vhu_vsock.rs b/vsock/src/vhu_vsock.rs index b86494b..49e48c6 100644 --- a/vsock/src/vhu_vsock.rs +++ b/vsock/src/vhu_vsock.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + use super::vhu_vsock_thread::*; use clap::Parser; use core::slice; diff --git a/vsock/src/vhu_vsock_thread.rs b/vsock/src/vhu_vsock_thread.rs index 0e3c59c..f28e44e 100644 --- a/vsock/src/vhu_vsock_thread.rs +++ b/vsock/src/vhu_vsock_thread.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + use super::{ rxops::*, thread_backend::*, diff --git a/vsock/src/vsock_conn.rs b/vsock/src/vsock_conn.rs index 96418ba..3d590d5 100644 --- a/vsock/src/vsock_conn.rs +++ b/vsock/src/vsock_conn.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + use super::{ rxops::*, rxqueue::*, From d6775cd90d73c68285e6e9eb24081473187881df Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Fri, 30 Sep 2022 16:49:09 +0200 Subject: [PATCH 07/16] vsock: clean up comments and fix log messages Remove old comments, useless code, and code commented. Avoid println!() to print messages and replaced with dbg!(), info!(), or error!(). Also init `env_logger` like other daemons in the main(). Signed-off-by: Stefano Garzarella --- Cargo.lock | 1 + vsock/Cargo.toml | 1 + vsock/src/main.rs | 33 ++++++++++----------------------- vsock/src/thread_backend.rs | 3 --- vsock/src/vhu_vsock.rs | 4 ---- vsock/src/vhu_vsock_thread.rs | 6 ------ vsock/src/vsock_conn.rs | 4 ++-- 7 files changed, 14 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 228d38f..6da01dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -675,6 +675,7 @@ version = "0.1.0" dependencies = [ "byteorder", "clap 3.2.22", + "env_logger", "epoll", "futures", "log", diff --git a/vsock/Cargo.toml b/vsock/Cargo.toml index 5e05376..7814892 100644 --- a/vsock/Cargo.toml +++ b/vsock/Cargo.toml @@ -12,6 +12,7 @@ edition = "2018" [dependencies] byteorder = "1" clap = { version = ">=3.0", features = ["derive"] } +env_logger = ">=0.9" epoll = "4.3.1" futures = { version = "0.3", features = ["thread-pool"] } log = ">=0.4.6" diff --git a/vsock/src/main.rs b/vsock/src/main.rs index 9e433a1..a0fd018 100644 --- a/vsock/src/main.rs +++ b/vsock/src/main.rs @@ -9,9 +9,9 @@ mod vhu_vsock_thread; mod vsock_conn; use clap::Parser; +use log::{info, warn}; use std::{ convert::TryFrom, - process, sync::{Arc, RwLock}, }; use vhost::{vhost_user, vhost_user::Listener}; @@ -38,48 +38,35 @@ pub(crate) fn start_backend_server(vsock_config: VsockConfig) { let mut vring_workers = vsock_daemon.get_epoll_handlers(); - if vring_workers.len() != vsock_backend.read().unwrap().threads.len() { - println!("Number of vring workers must be identical to number of backend threads"); - } - for thread in vsock_backend.read().unwrap().threads.iter() { thread .lock() .unwrap() .set_vring_worker(Some(vring_workers.remove(0))); } - if let Err(e) = vsock_daemon.start(listener) { - dbg!("Failed to start vsock daemon: {:?}", e); - process::exit(1); - } + + vsock_daemon.start(listener).unwrap(); match vsock_daemon.wait() { Ok(()) => { - println!("Stopping cleanly"); - process::exit(0); + info!("Stopping cleanly"); } Err(vhost_user_backend::Error::HandleRequest(vhost_user::Error::PartialMessage)) => { - println!("vhost-user connection closed with partial message. If the VM is shutting down, this is expected behavior; otherwise, it might be a bug."); - continue; + info!("vhost-user connection closed with partial message. If the VM is shutting down, this is expected behavior; otherwise, it might be a bug."); } Err(e) => { - println!("Error running daemon: {:?}", e); + warn!("Error running daemon: {:?}", e); } } - vsock_backend - .read() - .unwrap() - .exit_event - .write(1) - .expect("Shutting down worker thread"); - - println!("Vsock daemon is finished"); + // No matter the result, we need to shut down the worker thread. + vsock_backend.read().unwrap().exit_event.write(1).unwrap(); } } fn main() { - let vsock_config = VsockConfig::try_from(VsockArgs::parse()).unwrap(); + env_logger::init(); + let vsock_config = VsockConfig::try_from(VsockArgs::parse()).unwrap(); start_backend_server(vsock_config); } diff --git a/vsock/src/thread_backend.rs b/vsock/src/thread_backend.rs index 436cd65..9d91d24 100644 --- a/vsock/src/thread_backend.rs +++ b/vsock/src/thread_backend.rs @@ -1,7 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause -#![deny(missing_docs)] - use super::{ rxops::*, vhu_vsock::{ @@ -22,7 +20,6 @@ use std::{ use virtio_vsock::packet::VsockPacket; use vm_memory::bitmap::BitmapSlice; -// TODO: convert UnixStream to Arc> pub struct VsockThreadBackend { /// Map of ConnMapKey objects indexed by raw file descriptors. pub listener_map: HashMap, diff --git a/vsock/src/vhu_vsock.rs b/vsock/src/vhu_vsock.rs index 49e48c6..44bc8ef 100644 --- a/vsock/src/vhu_vsock.rs +++ b/vsock/src/vhu_vsock.rs @@ -278,10 +278,6 @@ impl VhostUserBackendMut for VhostUserVsockBackend { let vring_rx = &vrings[0]; let vring_tx = &vrings[1]; - if evset == EventSet::OUT { - dbg!("received epollout"); - } - if evset != EventSet::IN { return Err(Error::HandleEventNotEpollIn.into()); } diff --git a/vsock/src/vhu_vsock_thread.rs b/vsock/src/vhu_vsock_thread.rs index f28e44e..098075e 100644 --- a/vsock/src/vhu_vsock_thread.rs +++ b/vsock/src/vhu_vsock_thread.rs @@ -365,7 +365,6 @@ impl VhostUserVsockThread { epoll::Events::EPOLLIN, )?; - // self.register_listener(stream_fd, BACKEND_EVENT); Ok(()) } @@ -461,11 +460,6 @@ impl VhostUserVsockThread { if !vring.enable_notification().unwrap() { break; } - // TODO: This may not be required because of - // previous pending_rx check - // if !work { - // break; - // } } } else { self.process_rx_queue(vring)?; diff --git a/vsock/src/vsock_conn.rs b/vsock/src/vsock_conn.rs index 3d590d5..09a7761 100644 --- a/vsock/src/vsock_conn.rs +++ b/vsock/src/vsock_conn.rs @@ -290,12 +290,12 @@ impl VsockConnection { if e.kind() == ErrorKind::WouldBlock { 0 } else { - println!("send_bytes error: {:?}", e); + dbg!("send_bytes error: {:?}", e); return Err(Error::UnixWrite); } } Err(e) => { - println!("send_bytes error: {:?}", e); + dbg!("send_bytes error: {:?}", e); return Err(Error::UnixWrite); } }; From 5986155c8a1ccaf7daa1539024af7d65d25de8b3 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Mon, 26 Sep 2022 19:14:16 +0200 Subject: [PATCH 08/16] vsock: don't panic if "connect PORT\n" is malformed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of panic if the local peer doesn't send "connect PORT\n" correctly, let's print an error, ignoring the command. Print an error message also when allocating local port fails. Reported-by: Alex Bennée Signed-off-by: Stefano Garzarella --- vsock/src/vhu_vsock_thread.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/vsock/src/vhu_vsock_thread.rs b/vsock/src/vhu_vsock_thread.rs index 098075e..7de1d10 100644 --- a/vsock/src/vhu_vsock_thread.rs +++ b/vsock/src/vhu_vsock_thread.rs @@ -208,12 +208,19 @@ impl VhostUserVsockThread { let mut unix_stream = self.thread_backend.stream_map.remove(&fd).unwrap(); // Local peer is sending a "connect PORT\n" command - let peer_port = Self::read_local_stream_port(&mut unix_stream).unwrap(); + let peer_port = match Self::read_local_stream_port(&mut unix_stream) { + Ok(port) => port, + Err(err) => { + warn!("Error while parsing \"connect PORT\n\" command: {:?}", err); + return; + } + }; // Allocate a local port number let local_port = match self.allocate_local_port() { Ok(lp) => lp, - Err(_) => { + Err(err) => { + warn!("Error while allocating local port: {:?}", err); return; } }; From 1605f54fab3b8fd7415c771c501f409d5b45b172 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Wed, 28 Sep 2022 11:22:15 +0200 Subject: [PATCH 09/16] vsock: send credit update only when needed If we don't remove any bytes from the buffer, it's useless to send a credit update since the credit hasn't changed. Signed-off-by: Stefano Garzarella --- vsock/src/vhu_vsock_thread.rs | 6 ++++-- vsock/src/vsock_conn.rs | 10 ++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/vsock/src/vhu_vsock_thread.rs b/vsock/src/vhu_vsock_thread.rs index 7de1d10..102c62a 100644 --- a/vsock/src/vhu_vsock_thread.rs +++ b/vsock/src/vhu_vsock_thread.rs @@ -269,8 +269,10 @@ impl VhostUserVsockThread { // Flush any remaining data from the tx buffer match vsock_conn.tx_buf.flush_to(&mut vsock_conn.stream) { Ok(cnt) => { - vsock_conn.fwd_cnt += Wrapping(cnt as u32); - vsock_conn.rx_queue.enqueue(RxOps::CreditUpdate); + if cnt > 0 { + vsock_conn.fwd_cnt += Wrapping(cnt as u32); + vsock_conn.rx_queue.enqueue(RxOps::CreditUpdate); + } self.thread_backend.backend_rxq.push_back(ConnMapKey::new( vsock_conn.local_port, vsock_conn.peer_port, diff --git a/vsock/src/vsock_conn.rs b/vsock/src/vsock_conn.rs index 09a7761..1f0132a 100644 --- a/vsock/src/vsock_conn.rs +++ b/vsock/src/vsock_conn.rs @@ -300,10 +300,12 @@ impl VsockConnection { } }; - // Increment forwarded count by number of bytes written to the stream - self.fwd_cnt += Wrapping(written_count as u32); - // TODO: https://github.com/torvalds/linux/commit/c69e6eafff5f725bc29dcb8b52b6782dca8ea8a2 - self.rx_queue.enqueue(RxOps::CreditUpdate); + if written_count > 0 { + // Increment forwarded count by number of bytes written to the stream + self.fwd_cnt += Wrapping(written_count as u32); + // TODO: https://github.com/torvalds/linux/commit/c69e6eafff5f725bc29dcb8b52b6782dca8ea8a2 + self.rx_queue.enqueue(RxOps::CreditUpdate); + } if written_count != buf.len() { return self.tx_buf.push(&buf.offset(written_count).unwrap()); From 167206dea396ca5aa6147ca60c7dd6d6c1677e21 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Thu, 29 Sep 2022 14:50:00 +0200 Subject: [PATCH 10/16] vsock: try to avoid panic as much as we can Instead of panic, let's propagate errors or print warnings where we can't. Signed-off-by: Stefano Garzarella --- vsock/src/vhu_vsock.rs | 16 ++++++++++------ vsock/src/vhu_vsock_thread.rs | 23 +++++++++++++++++++---- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/vsock/src/vhu_vsock.rs b/vsock/src/vhu_vsock.rs index 44bc8ef..84d1026 100644 --- a/vsock/src/vhu_vsock.rs +++ b/vsock/src/vhu_vsock.rs @@ -78,6 +78,8 @@ pub(crate) enum Error { HandleUnknownEvent, #[error("Failed to accept new local socket connection")] UnixAccept(std::io::Error), + #[error("Failed to bind a unix stream")] + UnixBind(std::io::Error), #[error("Failed to create an epoll fd")] EpollFdCreate(std::io::Error), #[error("Failed to add to epoll")] @@ -118,6 +120,8 @@ pub(crate) enum Error { NoFreeLocalPort, #[error("Backend rx queue is empty")] EmptyBackendRxQ, + #[error("Failed to create an EventFd")] + EventFdCreate(std::io::Error), } impl std::convert::From for std::io::Error { @@ -217,17 +221,17 @@ pub struct VhostUserVsockBackend { impl VhostUserVsockBackend { pub(crate) fn new(vsock_config: VsockConfig) -> Result { - let thread = Mutex::new( - VhostUserVsockThread::new(vsock_config.get_uds_path(), vsock_config.get_guest_cid()) - .unwrap(), - ); + let thread = Mutex::new(VhostUserVsockThread::new( + vsock_config.get_uds_path(), + vsock_config.get_guest_cid(), + )?); let queues_per_thread = vec![QUEUE_MASK]; Ok(Self { guest_cid: vsock_config.get_guest_cid(), threads: vec![thread], queues_per_thread, - exit_event: EventFd::new(EFD_NONBLOCK).expect("Creating exit eventfd"), + exit_event: EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?, }) } } @@ -322,7 +326,7 @@ impl VhostUserBackendMut for VhostUserVsockBackend { } fn exit_event(&self, _thread_index: usize) -> Option { - Some(self.exit_event.try_clone().expect("Cloning exit eventfd")) + self.exit_event.try_clone().ok() } } diff --git a/vsock/src/vhu_vsock_thread.rs b/vsock/src/vhu_vsock_thread.rs index 102c62a..c8bd267 100644 --- a/vsock/src/vhu_vsock_thread.rs +++ b/vsock/src/vhu_vsock_thread.rs @@ -41,6 +41,8 @@ pub struct VhostUserVsockThread { pub event_idx: bool, /// Host socket raw file descriptor. host_sock: RawFd, + /// Host socket path + host_sock_path: String, /// Listener listening for new connections on the host. host_listener: UnixListener, /// Used to kill the thread. @@ -62,11 +64,11 @@ pub struct VhostUserVsockThread { impl VhostUserVsockThread { /// Create a new instance of VhostUserVsockThread. pub(crate) fn new(uds_path: String, guest_cid: u64) -> Result { - // TODO: better error handling - if let Ok(()) = std::fs::remove_file(uds_path.clone()) {} + // TODO: better error handling, maybe add a param to force the unlink + let _ = std::fs::remove_file(uds_path.clone()); let host_sock = UnixListener::bind(&uds_path) .and_then(|sock| sock.set_nonblocking(true).map(|_| sock)) - .unwrap(); + .map_err(Error::UnixBind)?; let epoll_fd = epoll::create(true).map_err(Error::EpollFdCreate)?; let epoll_file = unsafe { File::from_raw_fd(epoll_fd) }; @@ -77,6 +79,7 @@ impl VhostUserVsockThread { mem: None, event_idx: false, host_sock: host_sock.as_raw_fd(), + host_sock_path: uds_path.clone(), host_listener: host_sock, kill_evt: EventFd::new(EFD_NONBLOCK).unwrap(), vring_worker: None, @@ -205,7 +208,13 @@ impl VhostUserVsockThread { // Has to be EPOLLIN as it was not connected previously return; } - let mut unix_stream = self.thread_backend.stream_map.remove(&fd).unwrap(); + let mut unix_stream = match self.thread_backend.stream_map.remove(&fd) { + Some(uds) => uds, + None => { + warn!("Error while searching fd in the stream map"); + return; + } + }; // Local peer is sending a "connect PORT\n" command let peer_port = match Self::read_local_stream_port(&mut unix_stream) { @@ -572,3 +581,9 @@ impl VhostUserVsockThread { Ok(false) } } + +impl Drop for VhostUserVsockThread { + fn drop(&mut self) { + let _ = std::fs::remove_file(&self.host_sock_path); + } +} From 498c7490e13020b30ee75f442f7dede176d8a18f Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Thu, 29 Sep 2022 16:05:04 +0200 Subject: [PATCH 11/16] vsock: fix VhostUserVsockBackend::get_config VIRTIO spec defines `guest_cid` in LE byte order. Let's introduce VirtioVsockConfig based on the virtio-spec 1.2. For now it contains only `guest_cid`, but in the future it could contain other fields. Let's also handle correctly `offset` and `size`, returning an empty Vec if they are not valid. Signed-off-by: Stefano Garzarella --- vsock/src/vhu_vsock.rs | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/vsock/src/vhu_vsock.rs b/vsock/src/vhu_vsock.rs index 84d1026..add1928 100644 --- a/vsock/src/vhu_vsock.rs +++ b/vsock/src/vhu_vsock.rs @@ -2,17 +2,16 @@ use super::vhu_vsock_thread::*; use clap::Parser; -use core::slice; use std::convert::TryFrom; -use std::{io, mem, result, sync::Mutex, u16, u32, u64, u8}; +use std::{io, result, sync::Mutex, u16, u32, u64, u8}; use thiserror::Error as ThisError; use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; use vhost_user_backend::{VhostUserBackendMut, VringRwLock}; use virtio_bindings::bindings::{ - virtio_blk::__u64, virtio_net::VIRTIO_F_NOTIFY_ON_EMPTY, virtio_net::VIRTIO_F_VERSION_1, + virtio_net::VIRTIO_F_NOTIFY_ON_EMPTY, virtio_net::VIRTIO_F_VERSION_1, virtio_ring::VIRTIO_RING_F_EVENT_IDX, }; -use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; +use vm_memory::{ByteValued, GuestMemoryAtomic, GuestMemoryMmap, Le64}; use vmm_sys_util::{ epoll::EventSet, eventfd::{EventFd, EFD_NONBLOCK}, @@ -212,8 +211,17 @@ impl ConnMapKey { } } +/// Virtio Vsock Configuration +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[repr(C)] +struct VirtioVsockConfig { + pub guest_cid: Le64, +} + +unsafe impl ByteValued for VirtioVsockConfig {} + pub struct VhostUserVsockBackend { - guest_cid: __u64, + config: VirtioVsockConfig, pub threads: Vec>, queues_per_thread: Vec, pub exit_event: EventFd, @@ -228,7 +236,9 @@ impl VhostUserVsockBackend { let queues_per_thread = vec![QUEUE_MASK]; Ok(Self { - guest_cid: vsock_config.get_guest_cid(), + config: VirtioVsockConfig { + guest_cid: From::from(vsock_config.get_guest_cid()), + }, threads: vec![thread], queues_per_thread, exit_event: EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?, @@ -311,14 +321,17 @@ impl VhostUserBackendMut for VhostUserVsockBackend { Ok(false) } - fn get_config(&self, _offset: u32, _size: u32) -> Vec { - let buf = unsafe { - slice::from_raw_parts( - &self.guest_cid as *const __u64 as *const _, - mem::size_of::<__u64>(), - ) - }; - buf.to_vec() + fn get_config(&self, offset: u32, size: u32) -> Vec { + let offset = offset as usize; + let size = size as usize; + + let buf = self.config.as_slice(); + + if offset + size > buf.len() { + return Vec::new(); + } + + buf[offset..offset + size].to_vec() } fn queues_per_thread(&self) -> Vec { From a6d157f5e54ae5a8466270ee6efeb8761fb382c3 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Thu, 29 Sep 2022 14:53:18 +0200 Subject: [PATCH 12/16] vsock: add more tests and update coverage_score Add several tests to get good coverage. Unfortunately, it's a lot of new code and not easy to test everything. We may add more mocks and tests in the future. Signed-off-by: Stefano Garzarella --- coverage_config_x86_64.json | 2 +- vsock/src/main.rs | 39 ++++++++++ vsock/src/thread_backend.rs | 58 ++++++++++++++ vsock/src/vhu_vsock.rs | 138 ++++++++++++++++++++++++++++++++-- vsock/src/vhu_vsock_thread.rs | 92 +++++++++++++++++++++++ 5 files changed, 323 insertions(+), 6 deletions(-) diff --git a/coverage_config_x86_64.json b/coverage_config_x86_64.json index 47e6d43..9cf6dcc 100644 --- a/coverage_config_x86_64.json +++ b/coverage_config_x86_64.json @@ -1,5 +1,5 @@ { - "coverage_score": 77.0, + "coverage_score": 69.6, "exclude_path": "", "crate_features": "" } diff --git a/vsock/src/main.rs b/vsock/src/main.rs index a0fd018..5a43591 100644 --- a/vsock/src/main.rs +++ b/vsock/src/main.rs @@ -70,3 +70,42 @@ fn main() { let vsock_config = VsockConfig::try_from(VsockArgs::parse()).unwrap(); start_backend_server(vsock_config); } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_vsock_server() { + const CID: u64 = 3; + const VHOST_SOCKET_PATH: &str = "test_vsock_server.socket"; + const VSOCK_SOCKET_PATH: &str = "test_vsock_server.vsock"; + + let vsock_config = VsockConfig::new( + CID, + VHOST_SOCKET_PATH.to_string(), + VSOCK_SOCKET_PATH.to_string(), + ); + + let vsock_backend = Arc::new(RwLock::new( + VhostUserVsockBackend::new(vsock_config).unwrap(), + )); + + let vsock_daemon = VhostUserDaemon::new( + String::from("vhost-user-vsock"), + vsock_backend.clone(), + GuestMemoryAtomic::new(GuestMemoryMmap::new()), + ) + .unwrap(); + + let vring_workers = vsock_daemon.get_epoll_handlers(); + + // VhostUserVsockBackend support a single thread that handles the TX and RX queues + assert_eq!(vsock_backend.read().unwrap().threads.len(), 1); + + assert_eq!( + vring_workers.len(), + vsock_backend.read().unwrap().threads.len() + ); + } +} diff --git a/vsock/src/thread_backend.rs b/vsock/src/thread_backend.rs index 9d91d24..ef875d6 100644 --- a/vsock/src/thread_backend.rs +++ b/vsock/src/thread_backend.rs @@ -238,3 +238,61 @@ impl VsockThreadBackend { dbg!("New guest conn error: Enqueue RST"); } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::vhu_vsock::VSOCK_OP_RW; + use std::os::unix::net::UnixListener; + use virtio_vsock::packet::{VsockPacket, PKT_HEADER_SIZE}; + + const DATA_LEN: usize = 16; + + #[test] + fn test_vsock_thread_backend() { + const VSOCK_SOCKET_PATH: &str = "test_vsock_thread_backend.vsock"; + const VSOCK_PEER_PORT: u32 = 1234; + const VSOCK_PEER_PATH: &str = "test_vsock_thread_backend.vsock_1234"; + + let _ = std::fs::remove_file(VSOCK_PEER_PATH); + let _listener = UnixListener::bind(VSOCK_PEER_PATH).unwrap(); + + let epoll_fd = epoll::create(false).unwrap(); + let mut vtp = VsockThreadBackend::new(VSOCK_SOCKET_PATH.to_string(), epoll_fd); + + assert!(!vtp.pending_rx()); + + let mut pkt_raw = [0u8; PKT_HEADER_SIZE + DATA_LEN]; + let (hdr_raw, data_raw) = pkt_raw.split_at_mut(PKT_HEADER_SIZE); + + let mut packet = unsafe { VsockPacket::new(hdr_raw, Some(data_raw)).unwrap() }; + + assert_eq!( + vtp.recv_pkt(&mut packet).unwrap_err().to_string(), + Error::EmptyBackendRxQ.to_string() + ); + + assert!(vtp.send_pkt(&packet).is_ok()); + + packet.set_type(VSOCK_TYPE_STREAM); + assert!(vtp.send_pkt(&packet).is_ok()); + + packet.set_dst_cid(VSOCK_HOST_CID); + packet.set_dst_port(VSOCK_PEER_PORT); + assert!(vtp.send_pkt(&packet).is_ok()); + + packet.set_op(VSOCK_OP_REQUEST); + assert!(vtp.send_pkt(&packet).is_ok()); + + packet.set_op(VSOCK_OP_RW); + assert!(vtp.send_pkt(&packet).is_ok()); + + packet.set_op(VSOCK_OP_RST); + assert!(vtp.send_pkt(&packet).is_ok()); + + assert!(vtp.recv_pkt(&mut packet).is_ok()); + + // cleanup + let _ = std::fs::remove_file(VSOCK_PEER_PATH); + } +} diff --git a/vsock/src/vhu_vsock.rs b/vsock/src/vhu_vsock.rs index add1928..298cd7c 100644 --- a/vsock/src/vhu_vsock.rs +++ b/vsock/src/vhu_vsock.rs @@ -346,17 +346,145 @@ impl VhostUserBackendMut for VhostUserVsockBackend { #[cfg(test)] mod tests { use super::*; + use std::convert::TryInto; + use vhost_user_backend::VringT; + use vm_memory::GuestAddress; + + impl VsockArgs { + fn from_args(guest_cid: u64, socket: &str, uds_path: &str) -> Self { + VsockArgs { + guest_cid, + socket: socket.to_string(), + uds_path: uds_path.to_string(), + } + } + } #[test] fn test_vsock_config_setup() { - let vsock_config = VsockConfig::new( - 3, - "/tmp/vhost4.socket".to_string(), - "/tmp/vm4.vsock".to_string(), - ); + let vsock_args = VsockArgs::from_args(3, "/tmp/vhost4.socket", "/tmp/vm4.vsock"); + let vsock_config = VsockConfig::try_from(vsock_args); + assert!(vsock_config.is_ok()); + + let vsock_config = vsock_config.unwrap(); assert_eq!(vsock_config.get_guest_cid(), 3); assert_eq!(vsock_config.get_socket_path(), "/tmp/vhost4.socket"); assert_eq!(vsock_config.get_uds_path(), "/tmp/vm4.vsock"); } + + #[test] + fn test_vsock_backend() { + const CID: u64 = 3; + const VHOST_SOCKET_PATH: &str = "test_vsock_backend.socket"; + const VSOCK_SOCKET_PATH: &str = "test_vsock_backend.vsock"; + + let vsock_args = VsockArgs::from_args(CID, VHOST_SOCKET_PATH, VSOCK_SOCKET_PATH); + let vsock_config = VsockConfig::try_from(vsock_args); + + let vsock_backend = VhostUserVsockBackend::new(vsock_config.unwrap()); + + assert!(vsock_backend.is_ok()); + let mut vsock_backend = vsock_backend.unwrap(); + + assert_eq!(vsock_backend.num_queues(), NUM_QUEUES); + assert_eq!(vsock_backend.max_queue_size(), QUEUE_SIZE); + assert_ne!(vsock_backend.features(), 0); + assert!(!vsock_backend.protocol_features().is_empty()); + vsock_backend.set_event_idx(false); + + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap(), + ); + let vrings = [ + VringRwLock::new(mem.clone(), 0x1000).unwrap(), + VringRwLock::new(mem.clone(), 0x2000).unwrap(), + ]; + + assert!(vsock_backend.update_memory(mem).is_ok()); + + let queues_per_thread = vsock_backend.queues_per_thread(); + assert_eq!(queues_per_thread.len(), 1); + assert_eq!(queues_per_thread[0], 0b11); + + let config = vsock_backend.get_config(0, 8); + assert_eq!(config.len(), 8); + let cid = u64::from_le_bytes(config.try_into().unwrap()); + assert_eq!(cid, CID); + + let exit = vsock_backend.exit_event(0); + assert!(exit.is_some()); + exit.unwrap().write(1).unwrap(); + + let ret = vsock_backend.handle_event(RX_QUEUE_EVENT, EventSet::IN, &vrings, 0); + assert!(ret.is_ok()); + assert!(!ret.unwrap()); + + let ret = vsock_backend.handle_event(TX_QUEUE_EVENT, EventSet::IN, &vrings, 0); + assert!(ret.is_ok()); + assert!(!ret.unwrap()); + + let ret = vsock_backend.handle_event(EVT_QUEUE_EVENT, EventSet::IN, &vrings, 0); + assert!(ret.is_ok()); + assert!(!ret.unwrap()); + + let ret = vsock_backend.handle_event(BACKEND_EVENT, EventSet::IN, &vrings, 0); + assert!(ret.is_ok()); + assert!(!ret.unwrap()); + + // cleanup + let _ = std::fs::remove_file(VHOST_SOCKET_PATH); + let _ = std::fs::remove_file(VSOCK_SOCKET_PATH); + } + + #[test] + fn test_vsock_backend_failures() { + const CID: u64 = 3; + const VHOST_SOCKET_PATH: &str = "test_vsock_backend_failures.socket"; + const VSOCK_SOCKET_PATH: &str = "test_vsock_backend_failures.vsock"; + + let vsock_args = + VsockArgs::from_args(CID, "/sys/not_allowed.socket", "/sys/not_allowed.vsock"); + let vsock_config = VsockConfig::try_from(vsock_args); + + let vsock_backend = VhostUserVsockBackend::new(vsock_config.unwrap()); + assert!(vsock_backend.is_err()); + + let vsock_args = VsockArgs::from_args(CID, VHOST_SOCKET_PATH, VSOCK_SOCKET_PATH); + let vsock_config = VsockConfig::try_from(vsock_args); + + let mut vsock_backend = VhostUserVsockBackend::new(vsock_config.unwrap()).unwrap(); + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap(), + ); + let vrings = [ + VringRwLock::new(mem.clone(), 0x1000).unwrap(), + VringRwLock::new(mem.clone(), 0x2000).unwrap(), + ]; + + vsock_backend.update_memory(mem).unwrap(); + + // reading out of the config space, expecting empty config + let config = vsock_backend.get_config(2, 8); + assert_eq!(config.len(), 0); + + assert_eq!( + vsock_backend + .handle_event(RX_QUEUE_EVENT, EventSet::OUT, &vrings, 0) + .unwrap_err() + .to_string(), + Error::HandleEventNotEpollIn.to_string() + ); + assert_eq!( + vsock_backend + .handle_event(BACKEND_EVENT + 1, EventSet::IN, &vrings, 0) + .unwrap_err() + .to_string(), + Error::HandleUnknownEvent.to_string() + ); + + // cleanup + let _ = std::fs::remove_file(VHOST_SOCKET_PATH); + let _ = std::fs::remove_file(VSOCK_SOCKET_PATH); + } } diff --git a/vsock/src/vhu_vsock_thread.rs b/vsock/src/vhu_vsock_thread.rs index c8bd267..bf612f8 100644 --- a/vsock/src/vhu_vsock_thread.rs +++ b/vsock/src/vhu_vsock_thread.rs @@ -587,3 +587,95 @@ impl Drop for VhostUserVsockThread { let _ = std::fs::remove_file(&self.host_sock_path); } } +#[cfg(test)] +mod tests { + use super::*; + use vm_memory::GuestAddress; + + impl VhostUserVsockThread { + pub fn get_epoll_file(&self) -> &File { + &self.epoll_file + } + } + + #[test] + fn test_vsock_thread() { + let t = VhostUserVsockThread::new("test_vsock_thread.vsock".to_string(), 3); + assert!(t.is_ok()); + + let mut t = t.unwrap(); + let epoll_fd = t.get_epoll_file().as_raw_fd(); + + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap(), + ); + + t.mem = Some(mem.clone()); + + let dummy_fd = EventFd::new(0).unwrap(); + + assert!(VhostUserVsockThread::epoll_register( + epoll_fd, + dummy_fd.as_raw_fd(), + epoll::Events::EPOLLOUT + ) + .is_ok()); + assert!(VhostUserVsockThread::epoll_modify( + epoll_fd, + dummy_fd.as_raw_fd(), + epoll::Events::EPOLLIN + ) + .is_ok()); + assert!(VhostUserVsockThread::epoll_unregister(epoll_fd, dummy_fd.as_raw_fd()).is_ok()); + assert!(VhostUserVsockThread::epoll_register( + epoll_fd, + dummy_fd.as_raw_fd(), + epoll::Events::EPOLLIN + ) + .is_ok()); + + let vring = VringRwLock::new(mem, 0x1000).unwrap(); + + assert!(t.process_tx(&vring, false).is_ok()); + assert!(t.process_tx(&vring, true).is_ok()); + // add backend_rxq to avoid that RX processing is skipped + t.thread_backend + .backend_rxq + .push_back(ConnMapKey::new(0, 0)); + assert!(t.process_rx(&vring, false).is_ok()); + assert!(t.process_rx(&vring, true).is_ok()); + + dummy_fd.write(1).unwrap(); + + t.process_backend_evt(EventSet::empty()); + } + + #[test] + fn test_vsock_thread_failures() { + let t = VhostUserVsockThread::new("/sys/not_allowed.vsock".to_string(), 3); + assert!(t.is_err()); + + let mut t = + VhostUserVsockThread::new("test_vsock_thread_failures.vsock".to_string(), 3).unwrap(); + assert!(VhostUserVsockThread::epoll_register(-1, -1, epoll::Events::EPOLLIN).is_err()); + assert!(VhostUserVsockThread::epoll_modify(-1, -1, epoll::Events::EPOLLIN).is_err()); + assert!(VhostUserVsockThread::epoll_unregister(-1, -1).is_err()); + + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap(), + ); + + let vring = VringRwLock::new(mem, 0x1000).unwrap(); + + // memory is not configured, so processing TX should fail + assert!(t.process_tx(&vring, false).is_err()); + assert!(t.process_tx(&vring, true).is_err()); + + // add backend_rxq to avoid that RX processing is skipped + t.thread_backend + .backend_rxq + .push_back(ConnMapKey::new(0, 0)); + assert!(t.process_rx(&vring, false).is_err()); + assert!(t.process_rx(&vring, true).is_err()); + } +} From f7f239e39042c9cb6e4bf26d94c512dc67df34d1 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Wed, 12 Oct 2022 11:24:12 +0200 Subject: [PATCH 13/16] vsock: use pub(crate) for types used only by our binary This is a binary crate and most of the types shouldn't be visible out of this crate. Change also documentation with ///. If we would like to export some types as part of a library in the future, we will adjust the visibility accordingly. Suggested-by: Viresh Kumar Signed-off-by: Stefano Garzarella --- vsock/src/rxops.rs | 2 +- vsock/src/rxqueue.rs | 2 +- vsock/src/thread_backend.rs | 6 ++-- vsock/src/txbuf.rs | 6 ++-- vsock/src/vhu_vsock.rs | 65 ++++++++++++++++++----------------- vsock/src/vhu_vsock_thread.rs | 25 ++++++-------- vsock/src/vsock_conn.rs | 8 ++--- 7 files changed, 55 insertions(+), 59 deletions(-) diff --git a/vsock/src/rxops.rs b/vsock/src/rxops.rs index 7212c7f..6f20466 100644 --- a/vsock/src/rxops.rs +++ b/vsock/src/rxops.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause #[derive(Clone, Copy, Eq, PartialEq, Debug)] -pub enum RxOps { +pub(crate) enum RxOps { /// VSOCK_OP_REQUEST Request = 0, /// VSOCK_OP_RW diff --git a/vsock/src/rxqueue.rs b/vsock/src/rxqueue.rs index 10f8572..85a121d 100644 --- a/vsock/src/rxqueue.rs +++ b/vsock/src/rxqueue.rs @@ -3,7 +3,7 @@ use super::rxops::RxOps; #[derive(Debug, Eq, PartialEq)] -pub struct RxQueue { +pub(crate) struct RxQueue { /// Bitmap of rx operations. queue: u8, } diff --git a/vsock/src/thread_backend.rs b/vsock/src/thread_backend.rs index ef875d6..00f5b3c 100644 --- a/vsock/src/thread_backend.rs +++ b/vsock/src/thread_backend.rs @@ -20,7 +20,7 @@ use std::{ use virtio_vsock::packet::VsockPacket; use vm_memory::bitmap::BitmapSlice; -pub struct VsockThreadBackend { +pub(crate) struct VsockThreadBackend { /// Map of ConnMapKey objects indexed by raw file descriptors. pub listener_map: HashMap, /// Map of vsock connection objects indexed by ConnMapKey objects. @@ -63,7 +63,7 @@ impl VsockThreadBackend { /// Returns: /// - `Ok(())` if the packet was successfully filled in /// - `Err(Error::EmptyBackendRxQ) if there was no available data - pub(crate) fn recv_pkt(&mut self, pkt: &mut VsockPacket) -> Result<()> { + pub fn recv_pkt(&mut self, pkt: &mut VsockPacket) -> Result<()> { // Pop an event from the backend_rxq let key = self.backend_rxq.pop_front().ok_or(Error::EmptyBackendRxQ)?; let conn = match self.conn_map.get_mut(&key) { @@ -117,7 +117,7 @@ impl VsockThreadBackend { /// /// Returns: /// - always `Ok(())` if packet has been consumed correctly - pub(crate) fn send_pkt(&mut self, pkt: &VsockPacket) -> Result<()> { + pub fn send_pkt(&mut self, pkt: &VsockPacket) -> Result<()> { let key = ConnMapKey::new(pkt.dst_port(), pkt.src_port()); // TODO: Rst if packet has unsupported type diff --git a/vsock/src/txbuf.rs b/vsock/src/txbuf.rs index 4c435b0..61ded6d 100644 --- a/vsock/src/txbuf.rs +++ b/vsock/src/txbuf.rs @@ -5,7 +5,7 @@ use std::{io::Write, num::Wrapping}; use vm_memory::{bitmap::BitmapSlice, VolatileSlice}; #[derive(Debug)] -pub struct LocalTxBuf { +pub(crate) struct LocalTxBuf { /// Buffer holding data to be forwarded to a host-side application buf: Vec, /// Index into buffer from which data can be consumed from the buffer @@ -31,7 +31,7 @@ impl LocalTxBuf { /// Add new data to the tx buffer, push all or none. /// Returns LocalTxBufFull error if space not sufficient. - pub(crate) fn push(&mut self, data_buf: &VolatileSlice) -> Result<()> { + pub fn push(&mut self, data_buf: &VolatileSlice) -> Result<()> { if CONN_TX_BUF_SIZE as usize - self.len() < data_buf.len() { // Tx buffer is full return Err(Error::LocalTxBufFull); @@ -58,7 +58,7 @@ impl LocalTxBuf { } /// Flush buf data to stream. - pub(crate) fn flush_to(&mut self, stream: &mut S) -> Result { + pub fn flush_to(&mut self, stream: &mut S) -> Result { if self.is_empty() { // No data to be flushed return Ok(0); diff --git a/vsock/src/vhu_vsock.rs b/vsock/src/vhu_vsock.rs index 298cd7c..c004907 100644 --- a/vsock/src/vhu_vsock.rs +++ b/vsock/src/vhu_vsock.rs @@ -26,42 +26,43 @@ const RX_QUEUE_EVENT: u16 = 0; const TX_QUEUE_EVENT: u16 = 1; // New descriptors are pending on the event queue. const EVT_QUEUE_EVENT: u16 = 2; -// Notification coming from the backend. -pub const BACKEND_EVENT: u16 = 3; -// Vsock connection TX buffer capacity -// TODO: Make this value configurable -pub const CONN_TX_BUF_SIZE: u32 = 64 * 1024; +/// Notification coming from the backend. +pub(crate) const BACKEND_EVENT: u16 = 3; -// CID of the host -pub const VSOCK_HOST_CID: u64 = 2; +/// Vsock connection TX buffer capacity +/// TODO: Make this value configurable +pub(crate) const CONN_TX_BUF_SIZE: u32 = 64 * 1024; -// Connection oriented packet -pub const VSOCK_TYPE_STREAM: u16 = 1; +/// CID of the host +pub(crate) const VSOCK_HOST_CID: u64 = 2; + +/// Connection oriented packet +pub(crate) const VSOCK_TYPE_STREAM: u16 = 1; // Vsock packet operation ID -// -// Connection request -pub const VSOCK_OP_REQUEST: u16 = 1; -// Connection response -pub const VSOCK_OP_RESPONSE: u16 = 2; -// Connection reset -pub const VSOCK_OP_RST: u16 = 3; -// Shutdown connection -pub const VSOCK_OP_SHUTDOWN: u16 = 4; -// Data read/write -pub const VSOCK_OP_RW: u16 = 5; -// Flow control credit update -pub const VSOCK_OP_CREDIT_UPDATE: u16 = 6; -// Flow control credit request -pub const VSOCK_OP_CREDIT_REQUEST: u16 = 7; + +/// Connection request +pub(crate) const VSOCK_OP_REQUEST: u16 = 1; +/// Connection response +pub(crate) const VSOCK_OP_RESPONSE: u16 = 2; +/// Connection reset +pub(crate) const VSOCK_OP_RST: u16 = 3; +/// Shutdown connection +pub(crate) const VSOCK_OP_SHUTDOWN: u16 = 4; +/// Data read/write +pub(crate) const VSOCK_OP_RW: u16 = 5; +/// Flow control credit update +pub(crate) const VSOCK_OP_CREDIT_UPDATE: u16 = 6; +/// Flow control credit request +pub(crate) const VSOCK_OP_CREDIT_REQUEST: u16 = 7; // Vsock packet flags -// -// VSOCK_OP_SHUTDOWN: Packet sender will receive no more data -pub const VSOCK_FLAGS_SHUTDOWN_RCV: u32 = 1; -// VSOCK_OP_SHUTDOWN: Packet sender will send no more data -pub const VSOCK_FLAGS_SHUTDOWN_SEND: u32 = 2; + +/// VSOCK_OP_SHUTDOWN: Packet sender will receive no more data +pub(crate) const VSOCK_FLAGS_SHUTDOWN_RCV: u32 = 1; +/// VSOCK_OP_SHUTDOWN: Packet sender will send no more data +pub(crate) const VSOCK_FLAGS_SHUTDOWN_SEND: u32 = 2; // Queue mask to select vrings. const QUEUE_MASK: u64 = 0b11; @@ -197,7 +198,7 @@ impl TryFrom for VsockConfig { /// A local port and peer port pair used to retrieve /// the corresponding connection. #[derive(Hash, PartialEq, Eq, Debug, Clone)] -pub struct ConnMapKey { +pub(crate) struct ConnMapKey { local_port: u32, peer_port: u32, } @@ -220,7 +221,7 @@ struct VirtioVsockConfig { unsafe impl ByteValued for VirtioVsockConfig {} -pub struct VhostUserVsockBackend { +pub(crate) struct VhostUserVsockBackend { config: VirtioVsockConfig, pub threads: Vec>, queues_per_thread: Vec, @@ -228,7 +229,7 @@ pub struct VhostUserVsockBackend { } impl VhostUserVsockBackend { - pub(crate) fn new(vsock_config: VsockConfig) -> Result { + pub fn new(vsock_config: VsockConfig) -> Result { let thread = Mutex::new(VhostUserVsockThread::new( vsock_config.get_uds_path(), vsock_config.get_guest_cid(), diff --git a/vsock/src/vhu_vsock_thread.rs b/vsock/src/vhu_vsock_thread.rs index bf612f8..f69d711 100644 --- a/vsock/src/vhu_vsock_thread.rs +++ b/vsock/src/vhu_vsock_thread.rs @@ -27,14 +27,11 @@ use vhost_user_backend::{VringEpollHandler, VringRwLock, VringT}; use virtio_queue::QueueOwnedT; use virtio_vsock::packet::{VsockPacket, PKT_HEADER_SIZE}; use vm_memory::{GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap}; -use vmm_sys_util::{ - epoll::EventSet, - eventfd::{EventFd, EFD_NONBLOCK}, -}; +use vmm_sys_util::epoll::EventSet; type ArcVhostBknd = Arc>; -pub struct VhostUserVsockThread { +pub(crate) struct VhostUserVsockThread { /// Guest memory map. pub mem: Option>, /// VIRTIO_RING_F_EVENT_IDX. @@ -45,8 +42,6 @@ pub struct VhostUserVsockThread { host_sock_path: String, /// Listener listening for new connections on the host. host_listener: UnixListener, - /// Used to kill the thread. - pub kill_evt: EventFd, /// Instance of VringWorker. vring_worker: Option>>, /// epoll fd to which new host connections are added. @@ -63,7 +58,7 @@ pub struct VhostUserVsockThread { impl VhostUserVsockThread { /// Create a new instance of VhostUserVsockThread. - pub(crate) fn new(uds_path: String, guest_cid: u64) -> Result { + pub fn new(uds_path: String, guest_cid: u64) -> Result { // TODO: better error handling, maybe add a param to force the unlink let _ = std::fs::remove_file(uds_path.clone()); let host_sock = UnixListener::bind(&uds_path) @@ -81,7 +76,6 @@ impl VhostUserVsockThread { host_sock: host_sock.as_raw_fd(), host_sock_path: uds_path.clone(), host_listener: host_sock, - kill_evt: EventFd::new(EFD_NONBLOCK).unwrap(), vring_worker: None, epoll_file, thread_backend: VsockThreadBackend::new(uds_path, epoll_fd), @@ -99,7 +93,7 @@ impl VhostUserVsockThread { } /// Register a file with an epoll to listen for events in evset. - pub(crate) fn epoll_register(epoll_fd: RawFd, fd: RawFd, evset: epoll::Events) -> Result<()> { + pub fn epoll_register(epoll_fd: RawFd, fd: RawFd, evset: epoll::Events) -> Result<()> { epoll::ctl( epoll_fd, epoll::ControlOptions::EPOLL_CTL_ADD, @@ -112,7 +106,7 @@ impl VhostUserVsockThread { } /// Remove a file from the epoll. - pub(crate) fn epoll_unregister(epoll_fd: RawFd, fd: RawFd) -> Result<()> { + pub fn epoll_unregister(epoll_fd: RawFd, fd: RawFd) -> Result<()> { epoll::ctl( epoll_fd, epoll::ControlOptions::EPOLL_CTL_DEL, @@ -125,7 +119,7 @@ impl VhostUserVsockThread { } /// Modify the events we listen to for the fd in the epoll. - pub(crate) fn epoll_modify(epoll_fd: RawFd, fd: RawFd, evset: epoll::Events) -> Result<()> { + pub fn epoll_modify(epoll_fd: RawFd, fd: RawFd, evset: epoll::Events) -> Result<()> { epoll::ctl( epoll_fd, epoll::ControlOptions::EPOLL_CTL_MOD, @@ -462,7 +456,7 @@ impl VhostUserVsockThread { } /// Wrapper to process rx queue based on whether event idx is enabled or not. - pub(crate) fn process_rx(&mut self, vring: &VringRwLock, event_idx: bool) -> Result { + pub fn process_rx(&mut self, vring: &VringRwLock, event_idx: bool) -> Result { if event_idx { // To properly handle EVENT_IDX we need to keep calling // process_rx_queue until it stops finding new requests @@ -562,7 +556,7 @@ impl VhostUserVsockThread { } /// Wrapper to process tx queue based on whether event idx is enabled or not. - pub(crate) fn process_tx(&mut self, vring_lock: &VringRwLock, event_idx: bool) -> Result { + pub fn process_tx(&mut self, vring_lock: &VringRwLock, event_idx: bool) -> Result { if event_idx { // To properly handle EVENT_IDX we need to keep calling // process_rx_queue until it stops finding new requests @@ -591,9 +585,10 @@ impl Drop for VhostUserVsockThread { mod tests { use super::*; use vm_memory::GuestAddress; + use vmm_sys_util::eventfd::EventFd; impl VhostUserVsockThread { - pub fn get_epoll_file(&self) -> &File { + fn get_epoll_file(&self) -> &File { &self.epoll_file } } diff --git a/vsock/src/vsock_conn.rs b/vsock/src/vsock_conn.rs index 1f0132a..634e394 100644 --- a/vsock/src/vsock_conn.rs +++ b/vsock/src/vsock_conn.rs @@ -21,7 +21,7 @@ use virtio_vsock::packet::{VsockPacket, PKT_HEADER_SIZE}; use vm_memory::{bitmap::BitmapSlice, Bytes, VolatileSlice}; #[derive(Debug)] -pub struct VsockConnection { +pub(crate) struct VsockConnection { /// Host-side stream corresponding to this vsock connection. pub stream: S, /// Specifies if the stream is connected to a listener on the host. @@ -120,7 +120,7 @@ impl VsockConnection { /// Process a vsock packet that is meant for this connection. /// Forward data to the host-side application if the vsock packet /// contains a RW operation. - pub(crate) fn recv_pkt(&mut self, pkt: &mut VsockPacket) -> Result<()> { + pub fn recv_pkt(&mut self, pkt: &mut VsockPacket) -> Result<()> { // Initialize all fields in the packet header self.init_pkt(pkt); @@ -201,7 +201,7 @@ impl VsockConnection { /// /// Returns: /// - always `Ok(())` to indicate that the packet has been consumed - pub(crate) fn send_pkt(&mut self, pkt: &VsockPacket) -> Result<()> { + pub fn send_pkt(&mut self, pkt: &VsockPacket) -> Result<()> { // Update peer credit information self.peer_buf_alloc = pkt.buf_alloc(); self.peer_fwd_cnt = Wrapping(pkt.fwd_cnt()); @@ -365,7 +365,7 @@ mod tests { } impl HeadParams { - pub fn new(head_len: usize, data_len: u32) -> Self { + fn new(head_len: usize, data_len: u32) -> Self { Self { head_len, data_len } } fn construct_head(&self) -> Vec { From c452d1c2f9364cba79bbacecf958dbd7d6c86835 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Wed, 12 Oct 2022 12:26:48 +0200 Subject: [PATCH 14/16] vsock: move VsockArgs in main.rs VsockArgs is used only by main.rs, so we can move its definition there. Let's also move the test. Suggested-by: Viresh Kumar Signed-off-by: Stefano Garzarella --- vsock/src/main.rs | 52 ++++++++++++++++++++++++++- vsock/src/vhu_vsock.rs | 80 ++++++++++-------------------------------- 2 files changed, 69 insertions(+), 63 deletions(-) diff --git a/vsock/src/main.rs b/vsock/src/main.rs index 5a43591..4ec8655 100644 --- a/vsock/src/main.rs +++ b/vsock/src/main.rs @@ -16,9 +16,36 @@ use std::{ }; use vhost::{vhost_user, vhost_user::Listener}; use vhost_user_backend::VhostUserDaemon; -use vhu_vsock::{VhostUserVsockBackend, VsockArgs, VsockConfig}; +use vhu_vsock::{Error, Result, VhostUserVsockBackend, VsockConfig}; use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; +#[derive(Parser, Debug)] +#[clap(version, about, long_about = None)] +struct VsockArgs { + /// Context identifier of the guest which uniquely identifies the device for its lifetime. + #[clap(long, default_value_t = 3)] + guest_cid: u64, + + /// Unix socket to which a hypervisor conencts to and sets up the control path with the device. + #[clap(long)] + socket: String, + + /// Unix socket to which a host-side application connects to. + #[clap(long)] + uds_path: String, +} + +impl TryFrom for VsockConfig { + type Error = Error; + + fn try_from(cmd_args: VsockArgs) -> Result { + let socket = cmd_args.socket.trim().to_string(); + let uds_path = cmd_args.uds_path.trim().to_string(); + + Ok(VsockConfig::new(cmd_args.guest_cid, socket, uds_path)) + } +} + /// This is the public API through which an external program starts the /// vhost-user-vsock backend server. pub(crate) fn start_backend_server(vsock_config: VsockConfig) { @@ -75,6 +102,29 @@ fn main() { mod tests { use super::*; + impl VsockArgs { + fn from_args(guest_cid: u64, socket: &str, uds_path: &str) -> Self { + VsockArgs { + guest_cid, + socket: socket.to_string(), + uds_path: uds_path.to_string(), + } + } + } + + #[test] + fn test_vsock_config_setup() { + let vsock_args = VsockArgs::from_args(3, "/tmp/vhost4.socket", "/tmp/vm4.vsock"); + + let vsock_config = VsockConfig::try_from(vsock_args); + assert!(vsock_config.is_ok()); + + let vsock_config = vsock_config.unwrap(); + assert_eq!(vsock_config.get_guest_cid(), 3); + assert_eq!(vsock_config.get_socket_path(), "/tmp/vhost4.socket"); + assert_eq!(vsock_config.get_uds_path(), "/tmp/vm4.vsock"); + } + #[test] fn test_vsock_server() { const CID: u64 = 3; diff --git a/vsock/src/vhu_vsock.rs b/vsock/src/vhu_vsock.rs index c004907..c1a628b 100644 --- a/vsock/src/vhu_vsock.rs +++ b/vsock/src/vhu_vsock.rs @@ -1,8 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause use super::vhu_vsock_thread::*; -use clap::Parser; -use std::convert::TryFrom; use std::{io, result, sync::Mutex, u16, u32, u64, u8}; use thiserror::Error as ThisError; use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; @@ -130,22 +128,6 @@ impl std::convert::From for std::io::Error { } } -#[derive(Parser, Debug)] -#[clap(version, about, long_about = None)] -pub(crate) struct VsockArgs { - /// Context identifier of the guest which uniquely identifies the device for its lifetime. - #[clap(long, default_value_t = 3)] - guest_cid: u64, - - /// Unix socket to which a hypervisor connects to and sets up the control path with the device. - #[clap(long)] - socket: String, - - /// Unix socket to which a host-side application connects to. - #[clap(long)] - uds_path: String, -} - #[derive(Debug, Clone)] /// This structure is the public API through which an external program /// is allowed to configure the backend. @@ -184,17 +166,6 @@ impl VsockConfig { } } -impl TryFrom for VsockConfig { - type Error = Error; - - fn try_from(cmd_args: VsockArgs) -> Result { - let socket = cmd_args.socket.trim().to_string(); - let uds_path = cmd_args.uds_path.trim().to_string(); - - Ok(VsockConfig::new(cmd_args.guest_cid, socket, uds_path)) - } -} - /// A local port and peer port pair used to retrieve /// the corresponding connection. #[derive(Hash, PartialEq, Eq, Debug, Clone)] @@ -351,39 +322,19 @@ mod tests { use vhost_user_backend::VringT; use vm_memory::GuestAddress; - impl VsockArgs { - fn from_args(guest_cid: u64, socket: &str, uds_path: &str) -> Self { - VsockArgs { - guest_cid, - socket: socket.to_string(), - uds_path: uds_path.to_string(), - } - } - } - - #[test] - fn test_vsock_config_setup() { - let vsock_args = VsockArgs::from_args(3, "/tmp/vhost4.socket", "/tmp/vm4.vsock"); - - let vsock_config = VsockConfig::try_from(vsock_args); - assert!(vsock_config.is_ok()); - - let vsock_config = vsock_config.unwrap(); - assert_eq!(vsock_config.get_guest_cid(), 3); - assert_eq!(vsock_config.get_socket_path(), "/tmp/vhost4.socket"); - assert_eq!(vsock_config.get_uds_path(), "/tmp/vm4.vsock"); - } - #[test] fn test_vsock_backend() { const CID: u64 = 3; const VHOST_SOCKET_PATH: &str = "test_vsock_backend.socket"; const VSOCK_SOCKET_PATH: &str = "test_vsock_backend.vsock"; - let vsock_args = VsockArgs::from_args(CID, VHOST_SOCKET_PATH, VSOCK_SOCKET_PATH); - let vsock_config = VsockConfig::try_from(vsock_args); + let vsock_config = VsockConfig::new( + CID, + VHOST_SOCKET_PATH.to_string(), + VSOCK_SOCKET_PATH.to_string(), + ); - let vsock_backend = VhostUserVsockBackend::new(vsock_config.unwrap()); + let vsock_backend = VhostUserVsockBackend::new(vsock_config); assert!(vsock_backend.is_ok()); let mut vsock_backend = vsock_backend.unwrap(); @@ -444,17 +395,22 @@ mod tests { const VHOST_SOCKET_PATH: &str = "test_vsock_backend_failures.socket"; const VSOCK_SOCKET_PATH: &str = "test_vsock_backend_failures.vsock"; - let vsock_args = - VsockArgs::from_args(CID, "/sys/not_allowed.socket", "/sys/not_allowed.vsock"); - let vsock_config = VsockConfig::try_from(vsock_args); + let vsock_config = VsockConfig::new( + CID, + "/sys/not_allowed.socket".to_string(), + "/sys/not_allowed.vsock".to_string(), + ); - let vsock_backend = VhostUserVsockBackend::new(vsock_config.unwrap()); + let vsock_backend = VhostUserVsockBackend::new(vsock_config); assert!(vsock_backend.is_err()); - let vsock_args = VsockArgs::from_args(CID, VHOST_SOCKET_PATH, VSOCK_SOCKET_PATH); - let vsock_config = VsockConfig::try_from(vsock_args); + let vsock_config = VsockConfig::new( + CID, + VHOST_SOCKET_PATH.to_string(), + VSOCK_SOCKET_PATH.to_string(), + ); - let mut vsock_backend = VhostUserVsockBackend::new(vsock_config.unwrap()).unwrap(); + let mut vsock_backend = VhostUserVsockBackend::new(vsock_config).unwrap(); let mem = GuestMemoryAtomic::new( GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap(), ); From a76f9ebe168bfc0ec96214c34510f3f53384a547 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Wed, 12 Oct 2022 13:09:42 +0200 Subject: [PATCH 15/16] vsock: remove "vsock_" prefix where is redundant In several places the "vsock_" prefix doesn't add much and makes names longer, let's remove it where it's not needed. Suggested-by: Viresh Kumar Signed-off-by: Stefano Garzarella --- vsock/src/main.rs | 59 ++++----- vsock/src/thread_backend.rs | 4 +- vsock/src/vhu_vsock.rs | 60 ++++----- vsock/src/vhu_vsock_thread.rs | 29 ++--- vsock/src/vsock_conn.rs | 227 +++++++++++++++++----------------- 5 files changed, 184 insertions(+), 195 deletions(-) diff --git a/vsock/src/main.rs b/vsock/src/main.rs index 4ec8655..08318d6 100644 --- a/vsock/src/main.rs +++ b/vsock/src/main.rs @@ -48,33 +48,33 @@ impl TryFrom for VsockConfig { /// This is the public API through which an external program starts the /// vhost-user-vsock backend server. -pub(crate) fn start_backend_server(vsock_config: VsockConfig) { +pub(crate) fn start_backend_server(config: VsockConfig) { loop { - let vsock_backend = Arc::new(RwLock::new( - VhostUserVsockBackend::new(vsock_config.clone()).unwrap(), + let backend = Arc::new(RwLock::new( + VhostUserVsockBackend::new(config.clone()).unwrap(), )); - let listener = Listener::new(vsock_config.get_socket_path(), true).unwrap(); + let listener = Listener::new(config.get_socket_path(), true).unwrap(); - let mut vsock_daemon = VhostUserDaemon::new( + let mut daemon = VhostUserDaemon::new( String::from("vhost-user-vsock"), - vsock_backend.clone(), + backend.clone(), GuestMemoryAtomic::new(GuestMemoryMmap::new()), ) .unwrap(); - let mut vring_workers = vsock_daemon.get_epoll_handlers(); + let mut vring_workers = daemon.get_epoll_handlers(); - for thread in vsock_backend.read().unwrap().threads.iter() { + for thread in backend.read().unwrap().threads.iter() { thread .lock() .unwrap() .set_vring_worker(Some(vring_workers.remove(0))); } - vsock_daemon.start(listener).unwrap(); + daemon.start(listener).unwrap(); - match vsock_daemon.wait() { + match daemon.wait() { Ok(()) => { info!("Stopping cleanly"); } @@ -87,15 +87,15 @@ pub(crate) fn start_backend_server(vsock_config: VsockConfig) { } // No matter the result, we need to shut down the worker thread. - vsock_backend.read().unwrap().exit_event.write(1).unwrap(); + backend.read().unwrap().exit_event.write(1).unwrap(); } } fn main() { env_logger::init(); - let vsock_config = VsockConfig::try_from(VsockArgs::parse()).unwrap(); - start_backend_server(vsock_config); + let config = VsockConfig::try_from(VsockArgs::parse()).unwrap(); + start_backend_server(config); } #[cfg(test)] @@ -114,15 +114,15 @@ mod tests { #[test] fn test_vsock_config_setup() { - let vsock_args = VsockArgs::from_args(3, "/tmp/vhost4.socket", "/tmp/vm4.vsock"); + let args = VsockArgs::from_args(3, "/tmp/vhost4.socket", "/tmp/vm4.vsock"); - let vsock_config = VsockConfig::try_from(vsock_args); - assert!(vsock_config.is_ok()); + let config = VsockConfig::try_from(args); + assert!(config.is_ok()); - let vsock_config = vsock_config.unwrap(); - assert_eq!(vsock_config.get_guest_cid(), 3); - assert_eq!(vsock_config.get_socket_path(), "/tmp/vhost4.socket"); - assert_eq!(vsock_config.get_uds_path(), "/tmp/vm4.vsock"); + let config = config.unwrap(); + assert_eq!(config.get_guest_cid(), 3); + assert_eq!(config.get_socket_path(), "/tmp/vhost4.socket"); + assert_eq!(config.get_uds_path(), "/tmp/vm4.vsock"); } #[test] @@ -131,31 +131,26 @@ mod tests { const VHOST_SOCKET_PATH: &str = "test_vsock_server.socket"; const VSOCK_SOCKET_PATH: &str = "test_vsock_server.vsock"; - let vsock_config = VsockConfig::new( + let config = VsockConfig::new( CID, VHOST_SOCKET_PATH.to_string(), VSOCK_SOCKET_PATH.to_string(), ); - let vsock_backend = Arc::new(RwLock::new( - VhostUserVsockBackend::new(vsock_config).unwrap(), - )); + let backend = Arc::new(RwLock::new(VhostUserVsockBackend::new(config).unwrap())); - let vsock_daemon = VhostUserDaemon::new( + let daemon = VhostUserDaemon::new( String::from("vhost-user-vsock"), - vsock_backend.clone(), + backend.clone(), GuestMemoryAtomic::new(GuestMemoryMmap::new()), ) .unwrap(); - let vring_workers = vsock_daemon.get_epoll_handlers(); + let vring_workers = daemon.get_epoll_handlers(); // VhostUserVsockBackend support a single thread that handles the TX and RX queues - assert_eq!(vsock_backend.read().unwrap().threads.len(), 1); + assert_eq!(backend.read().unwrap().threads.len(), 1); - assert_eq!( - vring_workers.len(), - vsock_backend.read().unwrap().threads.len() - ); + assert_eq!(vring_workers.len(), backend.read().unwrap().threads.len()); } } diff --git a/vsock/src/thread_backend.rs b/vsock/src/thread_backend.rs index 00f5b3c..d8138c9 100644 --- a/vsock/src/thread_backend.rs +++ b/vsock/src/thread_backend.rs @@ -206,7 +206,7 @@ impl VsockThreadBackend { self.listener_map .insert(stream_fd, ConnMapKey::new(pkt.dst_port(), pkt.src_port())); - let vsock_conn = VsockConnection::new_peer_init( + let conn = VsockConnection::new_peer_init( stream, pkt.dst_cid(), pkt.dst_port(), @@ -217,7 +217,7 @@ impl VsockThreadBackend { ); self.conn_map - .insert(ConnMapKey::new(pkt.dst_port(), pkt.src_port()), vsock_conn); + .insert(ConnMapKey::new(pkt.dst_port(), pkt.src_port()), conn); self.backend_rxq .push_back(ConnMapKey::new(pkt.dst_port(), pkt.src_port())); self.stream_map diff --git a/vsock/src/vhu_vsock.rs b/vsock/src/vhu_vsock.rs index c1a628b..e872a10 100644 --- a/vsock/src/vhu_vsock.rs +++ b/vsock/src/vhu_vsock.rs @@ -200,16 +200,16 @@ pub(crate) struct VhostUserVsockBackend { } impl VhostUserVsockBackend { - pub fn new(vsock_config: VsockConfig) -> Result { + pub fn new(config: VsockConfig) -> Result { let thread = Mutex::new(VhostUserVsockThread::new( - vsock_config.get_uds_path(), - vsock_config.get_guest_cid(), + config.get_uds_path(), + config.get_guest_cid(), )?); let queues_per_thread = vec![QUEUE_MASK]; Ok(Self { config: VirtioVsockConfig { - guest_cid: From::from(vsock_config.get_guest_cid()), + guest_cid: From::from(config.get_guest_cid()), }, threads: vec![thread], queues_per_thread, @@ -328,22 +328,22 @@ mod tests { const VHOST_SOCKET_PATH: &str = "test_vsock_backend.socket"; const VSOCK_SOCKET_PATH: &str = "test_vsock_backend.vsock"; - let vsock_config = VsockConfig::new( + let config = VsockConfig::new( CID, VHOST_SOCKET_PATH.to_string(), VSOCK_SOCKET_PATH.to_string(), ); - let vsock_backend = VhostUserVsockBackend::new(vsock_config); + let backend = VhostUserVsockBackend::new(config); - assert!(vsock_backend.is_ok()); - let mut vsock_backend = vsock_backend.unwrap(); + assert!(backend.is_ok()); + let mut backend = backend.unwrap(); - assert_eq!(vsock_backend.num_queues(), NUM_QUEUES); - assert_eq!(vsock_backend.max_queue_size(), QUEUE_SIZE); - assert_ne!(vsock_backend.features(), 0); - assert!(!vsock_backend.protocol_features().is_empty()); - vsock_backend.set_event_idx(false); + assert_eq!(backend.num_queues(), NUM_QUEUES); + assert_eq!(backend.max_queue_size(), QUEUE_SIZE); + assert_ne!(backend.features(), 0); + assert!(!backend.protocol_features().is_empty()); + backend.set_event_idx(false); let mem = GuestMemoryAtomic::new( GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap(), @@ -353,34 +353,34 @@ mod tests { VringRwLock::new(mem.clone(), 0x2000).unwrap(), ]; - assert!(vsock_backend.update_memory(mem).is_ok()); + assert!(backend.update_memory(mem).is_ok()); - let queues_per_thread = vsock_backend.queues_per_thread(); + let queues_per_thread = backend.queues_per_thread(); assert_eq!(queues_per_thread.len(), 1); assert_eq!(queues_per_thread[0], 0b11); - let config = vsock_backend.get_config(0, 8); + let config = backend.get_config(0, 8); assert_eq!(config.len(), 8); let cid = u64::from_le_bytes(config.try_into().unwrap()); assert_eq!(cid, CID); - let exit = vsock_backend.exit_event(0); + let exit = backend.exit_event(0); assert!(exit.is_some()); exit.unwrap().write(1).unwrap(); - let ret = vsock_backend.handle_event(RX_QUEUE_EVENT, EventSet::IN, &vrings, 0); + let ret = backend.handle_event(RX_QUEUE_EVENT, EventSet::IN, &vrings, 0); assert!(ret.is_ok()); assert!(!ret.unwrap()); - let ret = vsock_backend.handle_event(TX_QUEUE_EVENT, EventSet::IN, &vrings, 0); + let ret = backend.handle_event(TX_QUEUE_EVENT, EventSet::IN, &vrings, 0); assert!(ret.is_ok()); assert!(!ret.unwrap()); - let ret = vsock_backend.handle_event(EVT_QUEUE_EVENT, EventSet::IN, &vrings, 0); + let ret = backend.handle_event(EVT_QUEUE_EVENT, EventSet::IN, &vrings, 0); assert!(ret.is_ok()); assert!(!ret.unwrap()); - let ret = vsock_backend.handle_event(BACKEND_EVENT, EventSet::IN, &vrings, 0); + let ret = backend.handle_event(BACKEND_EVENT, EventSet::IN, &vrings, 0); assert!(ret.is_ok()); assert!(!ret.unwrap()); @@ -395,22 +395,22 @@ mod tests { const VHOST_SOCKET_PATH: &str = "test_vsock_backend_failures.socket"; const VSOCK_SOCKET_PATH: &str = "test_vsock_backend_failures.vsock"; - let vsock_config = VsockConfig::new( + let config = VsockConfig::new( CID, "/sys/not_allowed.socket".to_string(), "/sys/not_allowed.vsock".to_string(), ); - let vsock_backend = VhostUserVsockBackend::new(vsock_config); - assert!(vsock_backend.is_err()); + let backend = VhostUserVsockBackend::new(config); + assert!(backend.is_err()); - let vsock_config = VsockConfig::new( + let config = VsockConfig::new( CID, VHOST_SOCKET_PATH.to_string(), VSOCK_SOCKET_PATH.to_string(), ); - let mut vsock_backend = VhostUserVsockBackend::new(vsock_config).unwrap(); + let mut backend = VhostUserVsockBackend::new(config).unwrap(); let mem = GuestMemoryAtomic::new( GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap(), ); @@ -419,21 +419,21 @@ mod tests { VringRwLock::new(mem.clone(), 0x2000).unwrap(), ]; - vsock_backend.update_memory(mem).unwrap(); + backend.update_memory(mem).unwrap(); // reading out of the config space, expecting empty config - let config = vsock_backend.get_config(2, 8); + let config = backend.get_config(2, 8); assert_eq!(config.len(), 0); assert_eq!( - vsock_backend + backend .handle_event(RX_QUEUE_EVENT, EventSet::OUT, &vrings, 0) .unwrap_err() .to_string(), Error::HandleEventNotEpollIn.to_string() ); assert_eq!( - vsock_backend + backend .handle_event(BACKEND_EVENT + 1, EventSet::IN, &vrings, 0) .unwrap_err() .to_string(), diff --git a/vsock/src/vhu_vsock_thread.rs b/vsock/src/vhu_vsock_thread.rs index f69d711..08d793b 100644 --- a/vsock/src/vhu_vsock_thread.rs +++ b/vsock/src/vhu_vsock_thread.rs @@ -236,7 +236,7 @@ impl VhostUserVsockThread { // Create a new connection object an enqueue a connection request // packet to be sent to the guest let conn_map_key = ConnMapKey::new(local_port, peer_port); - let mut new_vsock_conn = VsockConnection::new_local_init( + let mut new_conn = VsockConnection::new_local_init( unix_stream, VSOCK_HOST_CID, local_port, @@ -244,13 +244,11 @@ impl VhostUserVsockThread { peer_port, self.get_epoll_fd(), ); - new_vsock_conn.rx_queue.enqueue(RxOps::Request); - new_vsock_conn.set_peer_port(peer_port); + new_conn.rx_queue.enqueue(RxOps::Request); + new_conn.set_peer_port(peer_port); // Add connection object into the backend's maps - self.thread_backend - .conn_map - .insert(conn_map_key, new_vsock_conn); + self.thread_backend.conn_map.insert(conn_map_key, new_conn); self.thread_backend .backend_rxq @@ -266,20 +264,19 @@ impl VhostUserVsockThread { } else { // Previously connected connection let key = self.thread_backend.listener_map.get(&fd).unwrap(); - let vsock_conn = self.thread_backend.conn_map.get_mut(key).unwrap(); + let conn = self.thread_backend.conn_map.get_mut(key).unwrap(); if evset == epoll::Events::EPOLLOUT { // Flush any remaining data from the tx buffer - match vsock_conn.tx_buf.flush_to(&mut vsock_conn.stream) { + match conn.tx_buf.flush_to(&mut conn.stream) { Ok(cnt) => { if cnt > 0 { - vsock_conn.fwd_cnt += Wrapping(cnt as u32); - vsock_conn.rx_queue.enqueue(RxOps::CreditUpdate); + conn.fwd_cnt += Wrapping(cnt as u32); + conn.rx_queue.enqueue(RxOps::CreditUpdate); } - self.thread_backend.backend_rxq.push_back(ConnMapKey::new( - vsock_conn.local_port, - vsock_conn.peer_port, - )); + self.thread_backend + .backend_rxq + .push_back(ConnMapKey::new(conn.local_port, conn.peer_port)); } Err(e) => { dbg!("Error: {:?}", e); @@ -293,10 +290,10 @@ impl VhostUserVsockThread { Self::epoll_unregister(self.epoll_file.as_raw_fd(), fd).unwrap(); // Enqueue a read request - vsock_conn.rx_queue.enqueue(RxOps::Rw); + conn.rx_queue.enqueue(RxOps::Rw); self.thread_backend .backend_rxq - .push_back(ConnMapKey::new(vsock_conn.local_port, vsock_conn.peer_port)); + .push_back(ConnMapKey::new(conn.local_port, conn.peer_port)); } } } diff --git a/vsock/src/vsock_conn.rs b/vsock/src/vsock_conn.rs index 634e394..8e93f7c 100644 --- a/vsock/src/vsock_conn.rs +++ b/vsock/src/vsock_conn.rs @@ -486,56 +486,56 @@ mod tests { fn test_vsock_conn_init() { // new locally inititated connection let dummy_file = VsockDummySocket::new(); - let mut vsock_conn_local = + let mut conn_local = VsockConnection::new_local_init(dummy_file, VSOCK_HOST_CID, 5000, 3, 5001, -1); - assert!(!vsock_conn_local.connect); - assert_eq!(vsock_conn_local.peer_port, 5001); - assert_eq!(vsock_conn_local.rx_queue, RxQueue::new()); - assert_eq!(vsock_conn_local.local_cid, VSOCK_HOST_CID); - assert_eq!(vsock_conn_local.local_port, 5000); - assert_eq!(vsock_conn_local.guest_cid, 3); + assert!(!conn_local.connect); + assert_eq!(conn_local.peer_port, 5001); + assert_eq!(conn_local.rx_queue, RxQueue::new()); + assert_eq!(conn_local.local_cid, VSOCK_HOST_CID); + assert_eq!(conn_local.local_port, 5000); + assert_eq!(conn_local.guest_cid, 3); // set peer port - vsock_conn_local.set_peer_port(5002); - assert_eq!(vsock_conn_local.peer_port, 5002); + conn_local.set_peer_port(5002); + assert_eq!(conn_local.peer_port, 5002); // New connection initiated by the peer/guest let dummy_file = VsockDummySocket::new(); - let mut vsock_conn_peer = + let mut conn_peer = VsockConnection::new_peer_init(dummy_file, VSOCK_HOST_CID, 5000, 3, 5001, -1, 65536); - assert!(!vsock_conn_peer.connect); - assert_eq!(vsock_conn_peer.peer_port, 5001); - assert_eq!(vsock_conn_peer.rx_queue.dequeue().unwrap(), RxOps::Response); - assert!(!vsock_conn_peer.rx_queue.pending_rx()); - assert_eq!(vsock_conn_peer.local_cid, VSOCK_HOST_CID); - assert_eq!(vsock_conn_peer.local_port, 5000); - assert_eq!(vsock_conn_peer.guest_cid, 3); - assert_eq!(vsock_conn_peer.peer_buf_alloc, 65536); + assert!(!conn_peer.connect); + assert_eq!(conn_peer.peer_port, 5001); + assert_eq!(conn_peer.rx_queue.dequeue().unwrap(), RxOps::Response); + assert!(!conn_peer.rx_queue.pending_rx()); + assert_eq!(conn_peer.local_cid, VSOCK_HOST_CID); + assert_eq!(conn_peer.local_port, 5000); + assert_eq!(conn_peer.guest_cid, 3); + assert_eq!(conn_peer.peer_buf_alloc, 65536); } #[test] fn test_vsock_conn_credit() { // new locally inititated connection let dummy_file = VsockDummySocket::new(); - let mut vsock_conn_local = + let mut conn_local = VsockConnection::new_local_init(dummy_file, VSOCK_HOST_CID, 5000, 3, 5001, -1); - assert_eq!(vsock_conn_local.peer_avail_credit(), 0); - assert!(vsock_conn_local.need_credit_update_from_peer()); + assert_eq!(conn_local.peer_avail_credit(), 0); + assert!(conn_local.need_credit_update_from_peer()); - vsock_conn_local.peer_buf_alloc = 65536; - assert_eq!(vsock_conn_local.peer_avail_credit(), 65536); - assert!(!vsock_conn_local.need_credit_update_from_peer()); + conn_local.peer_buf_alloc = 65536; + assert_eq!(conn_local.peer_avail_credit(), 65536); + assert!(!conn_local.need_credit_update_from_peer()); - vsock_conn_local.rx_cnt = Wrapping(32768); - assert_eq!(vsock_conn_local.peer_avail_credit(), 32768); - assert!(!vsock_conn_local.need_credit_update_from_peer()); + conn_local.rx_cnt = Wrapping(32768); + assert_eq!(conn_local.peer_avail_credit(), 32768); + assert!(!conn_local.need_credit_update_from_peer()); - vsock_conn_local.rx_cnt = Wrapping(65536); - assert_eq!(vsock_conn_local.peer_avail_credit(), 0); - assert!(vsock_conn_local.need_credit_update_from_peer()); + conn_local.rx_cnt = Wrapping(65536); + assert_eq!(conn_local.peer_avail_credit(), 0); + assert!(conn_local.need_credit_update_from_peer()); } #[test] @@ -545,26 +545,26 @@ mod tests { // new locally inititated connection let dummy_file = VsockDummySocket::new(); - let vsock_conn_local = + let conn_local = VsockConnection::new_local_init(dummy_file, VSOCK_HOST_CID, 5000, 3, 5001, -1); // write only descriptor chain let (mem, mut descr_chain) = prepare_desc_chain_vsock(true, &head_params, 2, 10); let mem = mem.memory(); - let mut vsock_pkt = + let mut pkt = VsockPacket::from_rx_virtq_chain(mem.deref(), &mut descr_chain, CONN_TX_BUF_SIZE) .unwrap(); // initialize a vsock packet for the guest - vsock_conn_local.init_pkt(&mut vsock_pkt); + conn_local.init_pkt(&mut pkt); - assert_eq!(vsock_pkt.src_cid(), VSOCK_HOST_CID); - assert_eq!(vsock_pkt.dst_cid(), 3); - assert_eq!(vsock_pkt.src_port(), 5000); - assert_eq!(vsock_pkt.dst_port(), 5001); - assert_eq!(vsock_pkt.type_(), VSOCK_TYPE_STREAM); - assert_eq!(vsock_pkt.buf_alloc(), CONN_TX_BUF_SIZE); - assert_eq!(vsock_pkt.fwd_cnt(), 0); + assert_eq!(pkt.src_cid(), VSOCK_HOST_CID); + assert_eq!(pkt.dst_cid(), 3); + assert_eq!(pkt.src_port(), 5000); + assert_eq!(pkt.dst_port(), 5001); + assert_eq!(pkt.type_(), VSOCK_TYPE_STREAM); + assert_eq!(pkt.buf_alloc(), CONN_TX_BUF_SIZE); + assert_eq!(pkt.fwd_cnt(), 0); } #[test] @@ -574,86 +574,86 @@ mod tests { // new locally inititated connection let dummy_file = VsockDummySocket::new(); - let mut vsock_conn_local = + let mut conn_local = VsockConnection::new_local_init(dummy_file, VSOCK_HOST_CID, 5000, 3, 5001, -1); // write only descriptor chain let (mem, mut descr_chain) = prepare_desc_chain_vsock(true, &head_params, 1, 5); let mem = mem.memory(); - let mut vsock_pkt = + let mut pkt = VsockPacket::from_rx_virtq_chain(mem.deref(), &mut descr_chain, CONN_TX_BUF_SIZE) .unwrap(); // VSOCK_OP_REQUEST: new local conn request - vsock_conn_local.rx_queue.enqueue(RxOps::Request); - let vsock_op_req = vsock_conn_local.recv_pkt(&mut vsock_pkt); - assert!(vsock_op_req.is_ok()); - assert!(!vsock_conn_local.rx_queue.pending_rx()); - assert_eq!(vsock_pkt.op(), VSOCK_OP_REQUEST); + conn_local.rx_queue.enqueue(RxOps::Request); + let op_req = conn_local.recv_pkt(&mut pkt); + assert!(op_req.is_ok()); + assert!(!conn_local.rx_queue.pending_rx()); + assert_eq!(pkt.op(), VSOCK_OP_REQUEST); // VSOCK_OP_RST: reset if connection not established - vsock_conn_local.rx_queue.enqueue(RxOps::Rw); - let vsock_op_rst = vsock_conn_local.recv_pkt(&mut vsock_pkt); - assert!(vsock_op_rst.is_ok()); - assert!(!vsock_conn_local.rx_queue.pending_rx()); - assert_eq!(vsock_pkt.op(), VSOCK_OP_RST); + conn_local.rx_queue.enqueue(RxOps::Rw); + let op_rst = conn_local.recv_pkt(&mut pkt); + assert!(op_rst.is_ok()); + assert!(!conn_local.rx_queue.pending_rx()); + assert_eq!(pkt.op(), VSOCK_OP_RST); // VSOCK_OP_CREDIT_UPDATE: need credit update from peer/guest - vsock_conn_local.connect = true; - vsock_conn_local.rx_queue.enqueue(RxOps::Rw); - vsock_conn_local.fwd_cnt = Wrapping(1024); - let vsock_op_credit_update = vsock_conn_local.recv_pkt(&mut vsock_pkt); - assert!(vsock_op_credit_update.is_ok()); - assert!(!vsock_conn_local.rx_queue.pending_rx()); - assert_eq!(vsock_pkt.op(), VSOCK_OP_CREDIT_REQUEST); - assert_eq!(vsock_conn_local.last_fwd_cnt, Wrapping(1024)); + conn_local.connect = true; + conn_local.rx_queue.enqueue(RxOps::Rw); + conn_local.fwd_cnt = Wrapping(1024); + let op_credit_update = conn_local.recv_pkt(&mut pkt); + assert!(op_credit_update.is_ok()); + assert!(!conn_local.rx_queue.pending_rx()); + assert_eq!(pkt.op(), VSOCK_OP_CREDIT_REQUEST); + assert_eq!(conn_local.last_fwd_cnt, Wrapping(1024)); // VSOCK_OP_SHUTDOWN: zero data read from stream/file - vsock_conn_local.peer_buf_alloc = 65536; - vsock_conn_local.rx_queue.enqueue(RxOps::Rw); - let vsock_op_zero_read_shutdown = vsock_conn_local.recv_pkt(&mut vsock_pkt); - assert!(vsock_op_zero_read_shutdown.is_ok()); - assert!(!vsock_conn_local.rx_queue.pending_rx()); - assert_eq!(vsock_conn_local.rx_cnt, Wrapping(0)); - assert_eq!(vsock_conn_local.last_fwd_cnt, Wrapping(1024)); - assert_eq!(vsock_pkt.op(), VSOCK_OP_SHUTDOWN); + conn_local.peer_buf_alloc = 65536; + conn_local.rx_queue.enqueue(RxOps::Rw); + let op_zero_read_shutdown = conn_local.recv_pkt(&mut pkt); + assert!(op_zero_read_shutdown.is_ok()); + assert!(!conn_local.rx_queue.pending_rx()); + assert_eq!(conn_local.rx_cnt, Wrapping(0)); + assert_eq!(conn_local.last_fwd_cnt, Wrapping(1024)); + assert_eq!(pkt.op(), VSOCK_OP_SHUTDOWN); assert_eq!( - vsock_pkt.flags(), + pkt.flags(), VSOCK_FLAGS_SHUTDOWN_RCV | VSOCK_FLAGS_SHUTDOWN_SEND ); // VSOCK_OP_RW: finite data read from stream/file - vsock_conn_local.stream.write_all(b"hello").unwrap(); - vsock_conn_local.rx_queue.enqueue(RxOps::Rw); - let vsock_op_zero_read = vsock_conn_local.recv_pkt(&mut vsock_pkt); + conn_local.stream.write_all(b"hello").unwrap(); + conn_local.rx_queue.enqueue(RxOps::Rw); + let op_zero_read = conn_local.recv_pkt(&mut pkt); // below error due to epoll add - assert!(vsock_op_zero_read.is_err()); - assert_eq!(vsock_pkt.op(), VSOCK_OP_RW); - assert!(!vsock_conn_local.rx_queue.pending_rx()); - assert_eq!(vsock_pkt.len(), 5); + assert!(op_zero_read.is_err()); + assert_eq!(pkt.op(), VSOCK_OP_RW); + assert!(!conn_local.rx_queue.pending_rx()); + assert_eq!(pkt.len(), 5); let buf = &mut [0u8; 5]; - assert!(vsock_pkt.data_slice().unwrap().read_slice(buf, 0).is_ok()); + assert!(pkt.data_slice().unwrap().read_slice(buf, 0).is_ok()); assert_eq!(buf, b"hello"); // VSOCK_OP_RESPONSE: response from a locally initiated connection - vsock_conn_local.rx_queue.enqueue(RxOps::Response); - let vsock_op_response = vsock_conn_local.recv_pkt(&mut vsock_pkt); - assert!(vsock_op_response.is_ok()); - assert!(!vsock_conn_local.rx_queue.pending_rx()); - assert_eq!(vsock_pkt.op(), VSOCK_OP_RESPONSE); - assert!(vsock_conn_local.connect); + conn_local.rx_queue.enqueue(RxOps::Response); + let op_response = conn_local.recv_pkt(&mut pkt); + assert!(op_response.is_ok()); + assert!(!conn_local.rx_queue.pending_rx()); + assert_eq!(pkt.op(), VSOCK_OP_RESPONSE); + assert!(conn_local.connect); // VSOCK_OP_CREDIT_UPDATE: guest needs credit update - vsock_conn_local.rx_queue.enqueue(RxOps::CreditUpdate); - let vsock_op_credit_update = vsock_conn_local.recv_pkt(&mut vsock_pkt); - assert!(!vsock_conn_local.rx_queue.pending_rx()); - assert!(vsock_op_credit_update.is_ok()); - assert_eq!(vsock_pkt.op(), VSOCK_OP_CREDIT_UPDATE); - assert_eq!(vsock_conn_local.last_fwd_cnt, Wrapping(1024)); + conn_local.rx_queue.enqueue(RxOps::CreditUpdate); + let op_credit_update = conn_local.recv_pkt(&mut pkt); + assert!(!conn_local.rx_queue.pending_rx()); + assert!(op_credit_update.is_ok()); + assert_eq!(pkt.op(), VSOCK_OP_CREDIT_UPDATE); + assert_eq!(conn_local.last_fwd_cnt, Wrapping(1024)); // non-existent request - let vsock_op_error = vsock_conn_local.recv_pkt(&mut vsock_pkt); - assert!(vsock_op_error.is_err()); + let op_error = conn_local.recv_pkt(&mut pkt); + assert!(op_error.is_err()); } #[test] @@ -663,58 +663,55 @@ mod tests { // new locally inititated connection let dummy_file = VsockDummySocket::new(); - let mut vsock_conn_local = + let mut conn_local = VsockConnection::new_local_init(dummy_file, VSOCK_HOST_CID, 5000, 3, 5001, -1); // write only descriptor chain let (mem, mut descr_chain) = prepare_desc_chain_vsock(false, &head_params, 1, 5); let mem = mem.memory(); - let mut vsock_pkt = + let mut pkt = VsockPacket::from_tx_virtq_chain(mem.deref(), &mut descr_chain, CONN_TX_BUF_SIZE) .unwrap(); // peer credit information - vsock_pkt.set_buf_alloc(65536).set_fwd_cnt(1024); + pkt.set_buf_alloc(65536).set_fwd_cnt(1024); // check if peer credit information is updated currently - let credit_check = vsock_conn_local.send_pkt(&vsock_pkt); + let credit_check = conn_local.send_pkt(&pkt); assert!(credit_check.is_ok()); - assert_eq!(vsock_conn_local.peer_buf_alloc, 65536); - assert_eq!(vsock_conn_local.peer_fwd_cnt, Wrapping(1024)); + assert_eq!(conn_local.peer_buf_alloc, 65536); + assert_eq!(conn_local.peer_fwd_cnt, Wrapping(1024)); // VSOCK_OP_RESPONSE - vsock_pkt.set_op(VSOCK_OP_RESPONSE); - let peer_response = vsock_conn_local.send_pkt(&vsock_pkt); + pkt.set_op(VSOCK_OP_RESPONSE); + let peer_response = conn_local.send_pkt(&pkt); assert!(peer_response.is_ok()); - assert!(vsock_conn_local.connect); + assert!(conn_local.connect); let mut resp_buf = vec![0; 8]; - vsock_conn_local.stream.read_exact(&mut resp_buf).unwrap(); + conn_local.stream.read_exact(&mut resp_buf).unwrap(); assert_eq!(resp_buf, b"OK 5001\n"); // VSOCK_OP_RW - vsock_pkt.set_op(VSOCK_OP_RW); + pkt.set_op(VSOCK_OP_RW); let buf = b"hello"; - assert!(vsock_pkt.data_slice().unwrap().write_slice(buf, 0).is_ok()); - let rw_response = vsock_conn_local.send_pkt(&vsock_pkt); + assert!(pkt.data_slice().unwrap().write_slice(buf, 0).is_ok()); + let rw_response = conn_local.send_pkt(&pkt); assert!(rw_response.is_ok()); let mut resp_buf = vec![0; 5]; - vsock_conn_local.stream.read_exact(&mut resp_buf).unwrap(); + conn_local.stream.read_exact(&mut resp_buf).unwrap(); assert_eq!(resp_buf, b"hello"); // VSOCK_OP_CREDIT_REQUEST - vsock_pkt.set_op(VSOCK_OP_CREDIT_REQUEST); - let credit_response = vsock_conn_local.send_pkt(&vsock_pkt); + pkt.set_op(VSOCK_OP_CREDIT_REQUEST); + let credit_response = conn_local.send_pkt(&pkt); assert!(credit_response.is_ok()); - assert_eq!( - vsock_conn_local.rx_queue.peek().unwrap(), - RxOps::CreditUpdate - ); + assert_eq!(conn_local.rx_queue.peek().unwrap(), RxOps::CreditUpdate); // VSOCK_OP_SHUTDOWN - vsock_pkt.set_op(VSOCK_OP_SHUTDOWN); - vsock_pkt.set_flags(VSOCK_FLAGS_SHUTDOWN_RCV | VSOCK_FLAGS_SHUTDOWN_SEND); - let shutdown_response = vsock_conn_local.send_pkt(&vsock_pkt); + pkt.set_op(VSOCK_OP_SHUTDOWN); + pkt.set_flags(VSOCK_FLAGS_SHUTDOWN_RCV | VSOCK_FLAGS_SHUTDOWN_SEND); + let shutdown_response = conn_local.send_pkt(&pkt); assert!(shutdown_response.is_ok()); - assert!(vsock_conn_local.rx_queue.contains(RxOps::Reset.bitmask())); + assert!(conn_local.rx_queue.contains(RxOps::Reset.bitmask())); } } From e6a85ffd5e558e69cff272cace157aa74a4f8adf Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Wed, 12 Oct 2022 13:51:09 +0200 Subject: [PATCH 16/16] vsock: reorder use declarations Following the other crates, reorder the use declaration in 3 groups: 1. standard library 2. external crates 3. local crates Signed-off-by: Stefano Garzarella --- vsock/src/main.rs | 8 +++++--- vsock/src/rxqueue.rs | 2 +- vsock/src/thread_backend.rs | 24 +++++++++++++----------- vsock/src/txbuf.rs | 4 +++- vsock/src/vhu_vsock.rs | 4 +++- vsock/src/vhu_vsock_thread.rs | 24 +++++++++++++----------- vsock/src/vsock_conn.rs | 20 +++++++++++--------- 7 files changed, 49 insertions(+), 37 deletions(-) diff --git a/vsock/src/main.rs b/vsock/src/main.rs index 08318d6..6892324 100644 --- a/vsock/src/main.rs +++ b/vsock/src/main.rs @@ -8,17 +8,19 @@ mod vhu_vsock; mod vhu_vsock_thread; mod vsock_conn; -use clap::Parser; -use log::{info, warn}; use std::{ convert::TryFrom, sync::{Arc, RwLock}, }; + +use clap::Parser; +use log::{info, warn}; use vhost::{vhost_user, vhost_user::Listener}; use vhost_user_backend::VhostUserDaemon; -use vhu_vsock::{Error, Result, VhostUserVsockBackend, VsockConfig}; use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; +use crate::vhu_vsock::{Error, Result, VhostUserVsockBackend, VsockConfig}; + #[derive(Parser, Debug)] #[clap(version, about, long_about = None)] struct VsockArgs { diff --git a/vsock/src/rxqueue.rs b/vsock/src/rxqueue.rs index 85a121d..4b76727 100644 --- a/vsock/src/rxqueue.rs +++ b/vsock/src/rxqueue.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause -use super::rxops::RxOps; +use crate::rxops::RxOps; #[derive(Debug, Eq, PartialEq)] pub(crate) struct RxQueue { diff --git a/vsock/src/thread_backend.rs b/vsock/src/thread_backend.rs index d8138c9..04d91ec 100644 --- a/vsock/src/thread_backend.rs +++ b/vsock/src/thread_backend.rs @@ -1,6 +1,18 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause -use super::{ +use std::{ + collections::{HashMap, HashSet, VecDeque}, + os::unix::{ + net::UnixStream, + prelude::{AsRawFd, FromRawFd, RawFd}, + }, +}; + +use log::{info, warn}; +use virtio_vsock::packet::VsockPacket; +use vm_memory::bitmap::BitmapSlice; + +use crate::{ rxops::*, vhu_vsock::{ ConnMapKey, Error, Result, VSOCK_HOST_CID, VSOCK_OP_REQUEST, VSOCK_OP_RST, @@ -9,16 +21,6 @@ use super::{ vhu_vsock_thread::VhostUserVsockThread, vsock_conn::*, }; -use log::{info, warn}; -use std::{ - collections::{HashMap, HashSet, VecDeque}, - os::unix::{ - net::UnixStream, - prelude::{AsRawFd, FromRawFd, RawFd}, - }, -}; -use virtio_vsock::packet::VsockPacket; -use vm_memory::bitmap::BitmapSlice; pub(crate) struct VsockThreadBackend { /// Map of ConnMapKey objects indexed by raw file descriptors. diff --git a/vsock/src/txbuf.rs b/vsock/src/txbuf.rs index 61ded6d..f7b4119 100644 --- a/vsock/src/txbuf.rs +++ b/vsock/src/txbuf.rs @@ -1,9 +1,11 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause -use super::vhu_vsock::{Error, Result, CONN_TX_BUF_SIZE}; use std::{io::Write, num::Wrapping}; + use vm_memory::{bitmap::BitmapSlice, VolatileSlice}; +use crate::vhu_vsock::{Error, Result, CONN_TX_BUF_SIZE}; + #[derive(Debug)] pub(crate) struct LocalTxBuf { /// Buffer holding data to be forwarded to a host-side application diff --git a/vsock/src/vhu_vsock.rs b/vsock/src/vhu_vsock.rs index e872a10..e7a3dbd 100644 --- a/vsock/src/vhu_vsock.rs +++ b/vsock/src/vhu_vsock.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause -use super::vhu_vsock_thread::*; use std::{io, result, sync::Mutex, u16, u32, u64, u8}; + use thiserror::Error as ThisError; use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; use vhost_user_backend::{VhostUserBackendMut, VringRwLock}; @@ -15,6 +15,8 @@ use vmm_sys_util::{ eventfd::{EventFd, EFD_NONBLOCK}, }; +use crate::vhu_vsock_thread::*; + const NUM_QUEUES: usize = 2; const QUEUE_SIZE: usize = 256; diff --git a/vsock/src/vhu_vsock_thread.rs b/vsock/src/vhu_vsock_thread.rs index 08d793b..b81ba2e 100644 --- a/vsock/src/vhu_vsock_thread.rs +++ b/vsock/src/vhu_vsock_thread.rs @@ -1,16 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause -use super::{ - rxops::*, - thread_backend::*, - vhu_vsock::{ - ConnMapKey, Error, Result, VhostUserVsockBackend, BACKEND_EVENT, CONN_TX_BUF_SIZE, - VSOCK_HOST_CID, - }, - vsock_conn::*, -}; -use futures::executor::{ThreadPool, ThreadPoolBuilder}; -use log::warn; use std::{ fs::File, io, @@ -23,12 +12,25 @@ use std::{ }, sync::{Arc, RwLock}, }; + +use futures::executor::{ThreadPool, ThreadPoolBuilder}; +use log::warn; use vhost_user_backend::{VringEpollHandler, VringRwLock, VringT}; use virtio_queue::QueueOwnedT; use virtio_vsock::packet::{VsockPacket, PKT_HEADER_SIZE}; use vm_memory::{GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap}; use vmm_sys_util::epoll::EventSet; +use crate::{ + rxops::*, + thread_backend::*, + vhu_vsock::{ + ConnMapKey, Error, Result, VhostUserVsockBackend, BACKEND_EVENT, CONN_TX_BUF_SIZE, + VSOCK_HOST_CID, + }, + vsock_conn::*, +}; + type ArcVhostBknd = Arc>; pub(crate) struct VhostUserVsockThread { diff --git a/vsock/src/vsock_conn.rs b/vsock/src/vsock_conn.rs index 8e93f7c..0e67182 100644 --- a/vsock/src/vsock_conn.rs +++ b/vsock/src/vsock_conn.rs @@ -1,6 +1,16 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause -use super::{ +use std::{ + io::{ErrorKind, Read, Write}, + num::Wrapping, + os::unix::prelude::{AsRawFd, RawFd}, +}; + +use log::info; +use virtio_vsock::packet::{VsockPacket, PKT_HEADER_SIZE}; +use vm_memory::{bitmap::BitmapSlice, Bytes, VolatileSlice}; + +use crate::{ rxops::*, rxqueue::*, txbuf::*, @@ -11,14 +21,6 @@ use super::{ }, vhu_vsock_thread::VhostUserVsockThread, }; -use log::info; -use std::{ - io::{ErrorKind, Read, Write}, - num::Wrapping, - os::unix::prelude::{AsRawFd, RawFd}, -}; -use virtio_vsock::packet::{VsockPacket, PKT_HEADER_SIZE}; -use vm_memory::{bitmap::BitmapSlice, Bytes, VolatileSlice}; #[derive(Debug)] pub(crate) struct VsockConnection {