From bc468eb34e9b109d8ee59aacd3969539f22bf73c Mon Sep 17 00:00:00 2001 From: BLACKBOX Agent Date: Fri, 7 Nov 2025 21:39:08 +0000 Subject: [PATCH] feat(vsock): add SEQPACKET socket type support for virtio vsock --- SEQPACKET_USAGE_GUIDE.md | 463 ++++++++++++++++++ .../devices/virtio/vsock/csm/connection.rs | 22 +- src/vmm/src/devices/virtio/vsock/device.rs | 10 +- src/vmm/src/devices/virtio/vsock/mod.rs | 4 +- .../src/devices/virtio/vsock/unix/muxer.rs | 154 +++++- 5 files changed, 637 insertions(+), 16 deletions(-) create mode 100644 SEQPACKET_USAGE_GUIDE.md diff --git a/SEQPACKET_USAGE_GUIDE.md b/SEQPACKET_USAGE_GUIDE.md new file mode 100644 index 00000000000..d7985ae7311 --- /dev/null +++ b/SEQPACKET_USAGE_GUIDE.md @@ -0,0 +1,463 @@ +# SEQPACKET Usage Guide for Firecracker vsock + +## Overview + +This guide demonstrates how to use SEQPACKET socket type with Firecracker's vsock implementation. SEQPACKET provides connection-oriented communication with message boundaries preserved, unlike STREAM sockets which treat data as a continuous byte stream. + +## Use Cases for SEQPACKET + +SEQPACKET is ideal when you need: +- **Message boundaries**: Each send/recv operation corresponds to a complete message +- **Datagram-like semantics**: With the reliability of connection-oriented sockets +- **Protocol framing**: Automatic message delineation without manual framing +- **VM-to-host communication**: Where discrete messages need to be preserved + +Example scenarios: +- RPC systems where each request/response is a discrete message +- Event notification systems +- Command/control protocols +- Relaying datagrams over vsock while preserving boundaries + +## Guest-Side Usage (Inside VM) + +### Go Example + +```go +package main + +import ( + "fmt" + "golang.org/x/sys/unix" +) + +func main() { + // Create a SEQPACKET vsock socket + socketFd, err := unix.Socket(unix.AF_VSOCK, unix.SOCK_SEQPACKET, 0) + if err != nil { + panic(fmt.Sprintf("Failed to create socket: %v", err)) + } + defer unix.Close(socketFd) + + // Connect to host (CID 2) on port 500 + sockaddr := &unix.SockaddrVM{ + CID: 2, // Host CID + Port: 500, // Destination port + } + + if err := unix.Connect(socketFd, sockaddr); err != nil { + panic(fmt.Sprintf("Failed to connect: %v", err)) + } + + // Send a message (boundaries preserved) + message := []byte("Hello from guest!") + n, err := unix.Send(socketFd, message, 0) + if err != nil { + panic(fmt.Sprintf("Failed to send: %v", err)) + } + fmt.Printf("Sent %d bytes\n", n) + + // Receive a message (complete message received in one call) + buf := make([]byte, 4096) + n, err = unix.Recv(socketFd, buf, 0) + if err != nil { + panic(fmt.Sprintf("Failed to receive: %v", err)) + } + fmt.Printf("Received %d bytes: %s\n", n, string(buf[:n])) +} +``` + +### C Example + +```c +#include +#include +#include +#include +#include +#include + +int main() { + int sockfd; + struct sockaddr_vm addr; + char message[] = "Hello from guest!"; + char buffer[4096]; + ssize_t n; + + // Create SEQPACKET vsock socket + sockfd = socket(AF_VSOCK, SOCK_SEQPACKET, 0); + if (sockfd < 0) { + perror("socket"); + exit(1); + } + + // Connect to host (CID 2) on port 500 + memset(&addr, 0, sizeof(addr)); + addr.svm_family = AF_VSOCK; + addr.svm_cid = VMADDR_CID_HOST; // 2 + addr.svm_port = 500; + + if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + perror("connect"); + close(sockfd); + exit(1); + } + + // Send message (boundaries preserved) + n = send(sockfd, message, strlen(message), 0); + if (n < 0) { + perror("send"); + close(sockfd); + exit(1); + } + printf("Sent %zd bytes\n", n); + + // Receive message (complete message in one call) + n = recv(sockfd, buffer, sizeof(buffer), 0); + if (n < 0) { + perror("recv"); + close(sockfd); + exit(1); + } + buffer[n] = '\0'; + printf("Received %zd bytes: %s\n", n, buffer); + + close(sockfd); + return 0; +} +``` + +### Python Example + +```python +import socket + +# Create SEQPACKET vsock socket +sock = socket.socket(socket.AF_VSOCK, socket.SOCK_SEQPACKET) + +try: + # Connect to host (CID 2) on port 500 + sock.connect((2, 500)) + + # Send message (boundaries preserved) + message = b"Hello from guest!" + sock.send(message) + print(f"Sent {len(message)} bytes") + + # Receive message (complete message in one call) + data = sock.recv(4096) + print(f"Received {len(data)} bytes: {data.decode()}") + +finally: + sock.close() +``` + +## Host-Side Usage + +### Setting up the vsock device + +When starting Firecracker, configure the vsock device: + +```bash +# Create the vsock socket path +VSOCK_PATH="/tmp/firecracker.vsock" + +# Configure vsock via API +curl -X PUT 'http://localhost/vsock' \ + -H 'Content-Type: application/json' \ + -d '{ + "guest_cid": 3, + "uds_path": "'${VSOCK_PATH}'" + }' +``` + +### Connecting from Host with SEQPACKET + +#### Using socat + +```bash +# Connect to guest port 500 with SEQPACKET +echo "connect 500 seqpacket" | socat - UNIX-CONNECT:/tmp/firecracker.vsock + +# For STREAM (default, backward compatible): +echo "connect 500" | socat - UNIX-CONNECT:/tmp/firecracker.vsock +# or explicitly: +echo "connect 500 stream" | socat - UNIX-CONNECT:/tmp/firecracker.vsock +``` + +#### Using nc (netcat) with Unix sockets + +```bash +# Connect with SEQPACKET +echo "connect 500 seqpacket" | nc -U /tmp/firecracker.vsock +``` + +#### Python Script (Host-Side) + +```python +#!/usr/bin/env python3 +import socket +import os + +def connect_to_guest_seqpacket(vsock_path, guest_port): + """Connect to guest vsock with SEQPACKET socket type""" + + # Create Unix domain socket + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + + try: + # Connect to Firecracker's vsock Unix socket + sock.connect(vsock_path) + + # Send connection request with SEQPACKET type + connect_cmd = f"connect {guest_port} seqpacket\n" + sock.send(connect_cmd.encode()) + + # Read acknowledgment + response = sock.recv(1024).decode().strip() + if not response.startswith("OK"): + raise Exception(f"Connection failed: {response}") + + print(f"Connected to guest port {guest_port} with SEQPACKET") + + # Now you can send/receive messages with preserved boundaries + # Note: The underlying Unix socket is now SOCK_SEQPACKET + + # Send a message + message = b"Hello from host!" + sock.send(message) + print(f"Sent {len(message)} bytes") + + # Receive response + data = sock.recv(4096) + print(f"Received {len(data)} bytes: {data.decode()}") + + finally: + sock.close() + +if __name__ == "__main__": + vsock_path = "/tmp/firecracker.vsock" + guest_port = 500 + connect_to_guest_seqpacket(vsock_path, guest_port) +``` + +#### Go Script (Host-Side) + +```go +package main + +import ( + "fmt" + "net" + "strings" +) + +func connectToGuestSeqpacket(vsockPath string, guestPort int) error { + // Connect to Firecracker's vsock Unix socket + conn, err := net.Dial("unix", vsockPath) + if err != nil { + return fmt.Errorf("failed to connect: %w", err) + } + defer conn.Close() + + // Send connection request with SEQPACKET type + connectCmd := fmt.Sprintf("connect %d seqpacket\n", guestPort) + _, err = conn.Write([]byte(connectCmd)) + if err != nil { + return fmt.Errorf("failed to send connect command: %w", err) + } + + // Read acknowledgment + buf := make([]byte, 1024) + n, err := conn.Read(buf) + if err != nil { + return fmt.Errorf("failed to read response: %w", err) + } + + response := strings.TrimSpace(string(buf[:n])) + if !strings.HasPrefix(response, "OK") { + return fmt.Errorf("connection failed: %s", response) + } + + fmt.Printf("Connected to guest port %d with SEQPACKET\n", guestPort) + + // Send a message + message := []byte("Hello from host!") + _, err = conn.Write(message) + if err != nil { + return fmt.Errorf("failed to send message: %w", err) + } + fmt.Printf("Sent %d bytes\n", len(message)) + + // Receive response + n, err = conn.Read(buf) + if err != nil { + return fmt.Errorf("failed to receive: %w", err) + } + fmt.Printf("Received %d bytes: %s\n", n, string(buf[:n])) + + return nil +} + +func main() { + vsockPath := "/tmp/firecracker.vsock" + guestPort := 500 + + if err := connectToGuestSeqpacket(vsockPath, guestPort); err != nil { + panic(err) + } +} +``` + +## Key Differences: STREAM vs SEQPACKET + +### STREAM Socket Behavior + +```python +# Sender +sock.send(b"Message1") +sock.send(b"Message2") + +# Receiver might get: +data = sock.recv(4096) # Could be "Message1Message2" or "Message1Mes" or any combination +``` + +### SEQPACKET Socket Behavior + +```python +# Sender +sock.send(b"Message1") +sock.send(b"Message2") + +# Receiver gets: +data1 = sock.recv(4096) # Always gets exactly "Message1" +data2 = sock.recv(4096) # Always gets exactly "Message2" +``` + +## Message Boundary Preservation Example + +### Guest Application (Python) + +```python +import socket +import time + +sock = socket.socket(socket.AF_VSOCK, socket.SOCK_SEQPACKET) +sock.connect((2, 500)) + +# Send multiple discrete messages +messages = [ + b"Command: START", + b"Command: PROCESS", + b"Command: STOP" +] + +for msg in messages: + sock.send(msg) + time.sleep(0.1) # Small delay between messages + +# Each message is received as a complete unit on the host side +sock.close() +``` + +### Host Application (Python) + +```python +import socket + +# ... connection setup code ... + +# Receive messages - each recv() gets exactly one complete message +while True: + try: + data = sock.recv(4096) + if not data: + break + print(f"Received complete message: {data.decode()}") + # Output: + # Received complete message: Command: START + # Received complete message: Command: PROCESS + # Received complete message: Command: STOP + except Exception as e: + print(f"Error: {e}") + break +``` + +## Testing SEQPACKET Support + +### Verify Feature is Available + +From inside the guest VM: + +```bash +# Check if SEQPACKET feature is negotiated +cat /sys/devices/virtual/virtio-ports/vport*/features +# Should show bit 0 set (VIRTIO_VSOCK_F_SEQPACKET) +``` + +### Simple Test Script + +```bash +#!/bin/bash +# test_seqpacket.sh + +VSOCK_PATH="/tmp/firecracker.vsock" +GUEST_PORT=9999 + +# Start a listener in the guest (run this in guest VM first) +# python3 -c " +# import socket +# s = socket.socket(socket.AF_VSOCK, socket.SOCK_SEQPACKET) +# s.bind((socket.VMADDR_CID_ANY, 9999)) +# s.listen(1) +# conn, addr = s.accept() +# print(f'Connected: {addr}') +# while True: +# data = conn.recv(1024) +# if not data: break +# print(f'Received: {data}') +# conn.send(b'ACK: ' + data) +# " + +# Connect from host with SEQPACKET +( + echo "connect ${GUEST_PORT} seqpacket" + sleep 0.5 + echo "Test message 1" + sleep 0.5 + echo "Test message 2" + sleep 0.5 +) | socat - UNIX-CONNECT:${VSOCK_PATH} +``` + +## Troubleshooting + +### Connection Refused + +If you get connection refused: +1. Ensure the guest application is listening on the specified port +2. Verify the vsock device is properly configured +3. Check that the guest CID matches your configuration + +### Socket Type Mismatch + +If the host tries to connect with SEQPACKET but the guest is listening with STREAM (or vice versa), the connection will be refused. Ensure both sides use the same socket type. + +### Feature Not Available + +If SEQPACKET is not working: +1. Verify you're using a recent version of Firecracker with SEQPACKET support +2. Check that the guest kernel supports vsock SEQPACKET (Linux 5.6+) +3. Ensure the VIRTIO_VSOCK_F_SEQPACKET feature is negotiated + +## Performance Considerations + +- **SEQPACKET**: Slightly higher overhead due to message boundary tracking +- **STREAM**: Lower overhead, but requires manual framing for message boundaries +- **Use SEQPACKET when**: Message boundaries are important and worth the small overhead +- **Use STREAM when**: You're streaming continuous data without discrete messages + +## References + +- VirtIO vsock specification: https://docs.oasis-open.org/virtio/virtio/v1.2/ +- Linux vsock documentation: https://www.kernel.org/doc/html/latest/networking/af_vsock.html +- Firecracker vsock documentation: https://github.com/firecracker-microvm/firecracker/blob/main/docs/vsock.md diff --git a/src/vmm/src/devices/virtio/vsock/csm/connection.rs b/src/vmm/src/devices/virtio/vsock/csm/connection.rs index a5a2f4aec5b..13a0e6dcea3 100644 --- a/src/vmm/src/devices/virtio/vsock/csm/connection.rs +++ b/src/vmm/src/devices/virtio/vsock/csm/connection.rs @@ -117,6 +117,8 @@ pub struct VsockConnection { local_port: u32, /// The peer (guest) port. peer_port: u32, + /// The socket type for this connection (STREAM or SEQPACKET). + socket_type: u16, /// The (connected) host-side stream. stream: S, /// The TX buffer for this connection. @@ -509,12 +511,14 @@ where local_port: u32, peer_port: u32, peer_buf_alloc: u32, + socket_type: u16, ) -> Self { Self { local_cid, peer_cid, local_port, peer_port, + socket_type, stream, state: ConnState::PeerInit, tx_buf: TxBuf::new(), @@ -535,12 +539,14 @@ where peer_cid: u64, local_port: u32, peer_port: u32, + socket_type: u16, ) -> Self { Self { local_cid, peer_cid, local_port, peer_port, + socket_type, stream, state: ConnState::LocalInit, tx_buf: TxBuf::new(), @@ -671,10 +677,15 @@ where .set_dst_cid(self.peer_cid) .set_src_port(self.local_port) .set_dst_port(self.peer_port) - .set_type(uapi::VSOCK_TYPE_STREAM) + .set_type(self.socket_type) .set_buf_alloc(defs::CONN_TX_BUF_SIZE) .set_fwd_cnt(self.fwd_cnt.0); } + + /// Get the socket type for this connection. + pub fn socket_type(&self) -> u16 { + self.socket_type + } } #[cfg(test)] @@ -882,9 +893,15 @@ mod tests { LOCAL_PORT, PEER_PORT, PEER_BUF_ALLOC, + uapi::VSOCK_TYPE_STREAM, ), ConnState::LocalInit => VsockConnection::::new_local_init( - stream, LOCAL_CID, PEER_CID, LOCAL_PORT, PEER_PORT, + stream, + LOCAL_CID, + PEER_CID, + LOCAL_PORT, + PEER_PORT, + uapi::VSOCK_TYPE_STREAM, ), ConnState::Established => { let mut conn = VsockConnection::::new_peer_init( @@ -894,6 +911,7 @@ mod tests { LOCAL_PORT, PEER_PORT, PEER_BUF_ALLOC, + uapi::VSOCK_TYPE_STREAM, ); assert!(conn.has_pending_rx()); conn.recv_pkt(&mut rx_pkt).unwrap(); diff --git a/src/vmm/src/devices/virtio/vsock/device.rs b/src/vmm/src/devices/virtio/vsock/device.rs index 7fe10d158ad..41b856a2cda 100644 --- a/src/vmm/src/devices/virtio/vsock/device.rs +++ b/src/vmm/src/devices/virtio/vsock/device.rs @@ -50,12 +50,18 @@ pub(crate) const EVQ_INDEX: usize = 2; pub(crate) const VIRTIO_VSOCK_EVENT_TRANSPORT_RESET: u32 = 0; +/// Virtio vsock feature bits +/// Feature bit for SEQPACKET socket support (virtio v1.2+) +pub(crate) const VIRTIO_VSOCK_F_SEQPACKET: u64 = 1; + /// The virtio features supported by our vsock device: /// - VIRTIO_F_VERSION_1: the device conforms to at least version 1.0 of the VirtIO spec. /// - VIRTIO_F_IN_ORDER: the device returns used buffers in the same order that the driver makes /// them available. -pub(crate) const AVAIL_FEATURES: u64 = - (1 << VIRTIO_F_VERSION_1 as u64) | (1 << VIRTIO_F_IN_ORDER as u64); +/// - VIRTIO_VSOCK_F_SEQPACKET: the device supports SEQPACKET socket type. +pub(crate) const AVAIL_FEATURES: u64 = (1 << VIRTIO_F_VERSION_1 as u64) + | (1 << VIRTIO_F_IN_ORDER as u64) + | (1 << VIRTIO_VSOCK_F_SEQPACKET); /// Structure representing the vsock device. #[derive(Debug)] diff --git a/src/vmm/src/devices/virtio/vsock/mod.rs b/src/vmm/src/devices/virtio/vsock/mod.rs index cc9f7746580..8c438f3a206 100644 --- a/src/vmm/src/devices/virtio/vsock/mod.rs +++ b/src/vmm/src/devices/virtio/vsock/mod.rs @@ -84,8 +84,10 @@ mod defs { /// Vsock packet type. /// Defined in `/include/uapi/linux/virtio_vsock.h`. /// - /// Stream / connection-oriented packet (the only currently valid type). + /// Stream / connection-oriented packet. pub const VSOCK_TYPE_STREAM: u16 = 1; + /// Seqpacket / connection-oriented packet with message boundaries. + pub const VSOCK_TYPE_SEQPACKET: u16 = 2; pub const VSOCK_HOST_CID: u64 = 2; } diff --git a/src/vmm/src/devices/virtio/vsock/unix/muxer.rs b/src/vmm/src/devices/virtio/vsock/unix/muxer.rs index ad979b4bdeb..ac458ff1cd9 100644 --- a/src/vmm/src/devices/virtio/vsock/unix/muxer.rs +++ b/src/vmm/src/devices/virtio/vsock/unix/muxer.rs @@ -33,7 +33,7 @@ use std::collections::{HashMap, HashSet}; use std::fmt::Debug; use std::io::Read; -use std::os::unix::io::{AsRawFd, RawFd}; +use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::os::unix::net::{UnixListener, UnixStream}; use log::{debug, error, info, warn}; @@ -202,9 +202,11 @@ impl VsockChannel for VsockMuxer { pkt.hdr ); - // If this packet has an unsupported type (!=stream), we must send back an RST. + // If this packet has an unsupported type (not STREAM or SEQPACKET), we must send back an RST. // - if pkt.hdr.type_() != uapi::VSOCK_TYPE_STREAM { + if pkt.hdr.type_() != uapi::VSOCK_TYPE_STREAM + && pkt.hdr.type_() != uapi::VSOCK_TYPE_SEQPACKET + { self.enq_rst(pkt.hdr.dst_port(), pkt.hdr.src_port()); return Ok(()); } @@ -389,8 +391,10 @@ impl VsockMuxer { Some(EpollListener::LocalStream(_)) => { if let Some(EpollListener::LocalStream(mut stream)) = self.remove_listener(fd) { Self::read_local_stream_port(&mut stream) - .map(|peer_port| (self.allocate_local_port(), peer_port)) - .and_then(|(local_port, peer_port)| { + .map(|(peer_port, socket_type)| { + (self.allocate_local_port(), peer_port, socket_type) + }) + .and_then(|(local_port, peer_port, socket_type)| { self.add_connection( ConnMapKey { local_port, @@ -402,6 +406,7 @@ impl VsockMuxer { self.cid, local_port, peer_port, + socket_type, ), ) }) @@ -421,9 +426,13 @@ impl VsockMuxer { } } - /// Parse a host "connect" command, and extract the destination vsock port. - fn read_local_stream_port(stream: &mut UnixStream) -> Result { - let mut buf = [0u8; 32]; + /// Parse a host "connect" command, and extract the destination vsock port and socket type. + /// Format: "connect [stream|seqpacket]\n" + /// If socket type is not specified, defaults to STREAM for backward compatibility. + fn read_local_stream_port( + stream: &mut UnixStream, + ) -> Result<(u32, u16), VsockUnixBackendError> { + let mut buf = [0u8; 64]; // This is the minimum number of bytes that we should be able to read, when parsing a // valid connection request. I.e. `b"connect 0\n".len()`. @@ -468,6 +477,15 @@ impl VsockMuxer { word.parse::() .map_err(|_| VsockUnixBackendError::InvalidPortRequest) }) + .and_then(|port| { + // Parse optional socket type, default to STREAM for backward compatibility + let socket_type = match word_iter.next() { + Some("seqpacket") => uapi::VSOCK_TYPE_SEQPACKET, + Some("stream") | None => uapi::VSOCK_TYPE_STREAM, + Some(_) => return Err(VsockUnixBackendError::InvalidPortRequest), + }; + Ok((port, socket_type)) + }) .map_err(|_| VsockUnixBackendError::InvalidPortRequest) } @@ -604,6 +622,71 @@ impl VsockMuxer { self.local_port_set.remove(&port); } + /// Create a Unix socket connection with the specified socket type. + /// + /// This function creates a Unix domain socket with either SOCK_STREAM or SOCK_SEQPACKET type + /// and connects it to the specified path. + fn connect_unix_socket( + path: &str, + socket_type: u16, + ) -> Result { + // Determine the libc socket type based on vsock type + let sock_type = match socket_type { + uapi::VSOCK_TYPE_STREAM => libc::SOCK_STREAM, + uapi::VSOCK_TYPE_SEQPACKET => libc::SOCK_SEQPACKET, + _ => return Err(VsockUnixBackendError::InvalidPortRequest), + }; + + // Create socket using raw libc calls to support SEQPACKET + let fd = unsafe { + libc::socket( + libc::AF_UNIX, + sock_type | libc::SOCK_NONBLOCK | libc::SOCK_CLOEXEC, + 0, + ) + }; + + if fd < 0 { + return Err(VsockUnixBackendError::UnixConnect(std::io::Error::last_os_error())); + } + + // Prepare sockaddr_un structure + let mut addr: libc::sockaddr_un = unsafe { std::mem::zeroed() }; + addr.sun_family = libc::AF_UNIX as libc::sa_family_t; + + let path_bytes = path.as_bytes(); + if path_bytes.len() >= addr.sun_path.len() { + unsafe { libc::close(fd) }; + return Err(VsockUnixBackendError::UnixConnect(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "path too long", + ))); + } + + for (i, &byte) in path_bytes.iter().enumerate() { + addr.sun_path[i] = byte as libc::c_char; + } + + // Connect the socket + let ret = unsafe { + libc::connect( + fd, + &addr as *const libc::sockaddr_un as *const libc::sockaddr, + std::mem::size_of::() as libc::socklen_t, + ) + }; + + if ret < 0 { + let err = std::io::Error::last_os_error(); + unsafe { libc::close(fd) }; + return Err(VsockUnixBackendError::UnixConnect(err)); + } + + // Convert raw fd to UnixStream + let stream = unsafe { UnixStream::from_raw_fd(fd) }; + Ok(stream) + } + /// Handle a new connection request comming from our peer (the guest vsock driver). /// /// This will attempt to connect to a host-side Unix socket, expected to be listening at @@ -612,10 +695,9 @@ impl VsockMuxer { /// RST packet will be scheduled for delivery to the guest. fn handle_peer_request_pkt(&mut self, pkt: &VsockPacketTx) { let port_path = format!("{}_{}", self.host_sock_path, pkt.hdr.dst_port()); + let socket_type = pkt.hdr.type_(); - UnixStream::connect(port_path) - .and_then(|stream| stream.set_nonblocking(true).map(|_| stream)) - .map_err(VsockUnixBackendError::UnixConnect) + Self::connect_unix_socket(&port_path, socket_type) .and_then(|stream| { self.add_connection( ConnMapKey { @@ -629,6 +711,7 @@ impl VsockMuxer { pkt.hdr.dst_port(), pkt.hdr.src_port(), pkt.hdr.buf_alloc(), + socket_type, ), ) }) @@ -1534,4 +1617,53 @@ mod tests { // Check that the connection was removed. assert_eq!(METRICS.conns_removed.count(), conns_removed + 1); } + + #[test] + fn test_seqpacket_support() { + const LOCAL_PORT: u32 = 1026; + const PEER_PORT: u32 = 1025; + + let mut ctx = MuxerTestContext::new("seqpacket_support"); + + // Test that SEQPACKET packets are accepted (not rejected with RST) + let tx_pkt = ctx.init_tx_pkt(LOCAL_PORT, PEER_PORT, uapi::VSOCK_OP_REQUEST); + tx_pkt.hdr.set_type(uapi::VSOCK_TYPE_SEQPACKET); + ctx.send(); + + // The packet should be accepted, but since there's no listener, we should get an RST + ctx.recv(); + assert_eq!(ctx.rx_pkt.hdr.op(), uapi::VSOCK_OP_RST); + assert_eq!(ctx.rx_pkt.hdr.src_port(), LOCAL_PORT); + assert_eq!(ctx.rx_pkt.hdr.dst_port(), PEER_PORT); + + // Test that invalid socket types are still rejected + const INVALID_SOCK_TYPE: u16 = 99; + let tx_pkt = ctx.init_tx_pkt(LOCAL_PORT, PEER_PORT, uapi::VSOCK_OP_REQUEST); + tx_pkt.hdr.set_type(INVALID_SOCK_TYPE); + ctx.send(); + + // Should get an RST for invalid socket type + ctx.recv(); + assert_eq!(ctx.rx_pkt.hdr.op(), uapi::VSOCK_OP_RST); + assert_eq!(ctx.rx_pkt.hdr.src_port(), LOCAL_PORT); + assert_eq!(ctx.rx_pkt.hdr.dst_port(), PEER_PORT); + } + + #[test] + fn test_seqpacket_feature_flag() { + use crate::devices::virtio::device::VirtioDevice; + use crate::devices::virtio::vsock::device::VIRTIO_VSOCK_F_SEQPACKET; + + // Test that the SEQPACKET feature flag is included in available features + let ctx = test_utils::TestContext::new(); + let handler_ctx = ctx.create_event_handler_context(); + let device = &handler_ctx.device; + + // Check that SEQPACKET feature is advertised + assert_ne!( + device.avail_features() & (1 << VIRTIO_VSOCK_F_SEQPACKET), + 0, + "SEQPACKET feature should be advertised" + ); + } }