Skip to content

Commit a7f1d18

Browse files
hikejsclaude
andcommitted
feat(vsock): implement TSI Phase 4 - Named Pipes Proxy for Windows
Add TsiPipeProxyWindows for Windows Named Pipe operations: - pipe_proxy.rs: Named Pipe proxy with listen/accept/connect - Server mode: CreateNamedPipe + ConnectNamedPipe - Client mode: CreateFileW to open existing pipe - send_data/recv_data for bidirectional communication - PipeStatus state machine: Init → Listening/Connected → Closed - Unit tests for proxy creation and listen operation Phase 4 of 5 for complete TSI Windows implementation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent a8ed47e commit a7f1d18

2 files changed

Lines changed: 234 additions & 0 deletions

File tree

src/devices/src/virtio/vsock/tsi_windows/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
// Phase 1: Windows Socket abstraction layer
33
// Phase 2: TSI Stream Proxy (TCP)
44
// Phase 3: TSI DGRAM Proxy (UDP)
5+
// Phase 4: TSI Named Pipes Proxy
56

67
pub mod socket_wrapper;
78
pub mod stream_proxy;
89
pub mod dgram_proxy;
10+
pub mod pipe_proxy;
911

1012
pub use socket_wrapper::{WindowsSocket, AddressFamily, SockType, ShutdownMode};
1113
pub use stream_proxy::{TsiStreamProxyWindows, ProxyStatus, ProxyError};
1214
pub use dgram_proxy::TsiDgramProxyWindows;
15+
pub use pipe_proxy::{TsiPipeProxyWindows, PipeStatus};
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
// TSI Named Pipes Proxy for Windows
2+
// Handles Windows Named Pipe connections for vsock communication
3+
4+
use super::stream_proxy::ProxyError;
5+
use std::io::{self, Read, Write};
6+
use std::os::windows::io::{AsRawHandle, FromRawHandle, RawHandle};
7+
use std::ptr;
8+
use windows_sys::Win32::Foundation::{CloseHandle, ERROR_IO_PENDING, ERROR_PIPE_BUSY, HANDLE, INVALID_HANDLE_VALUE};
9+
use windows_sys::Win32::Storage::FileSystem::{
10+
CreateFileW, FILE_FLAG_OVERLAPPED, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING,
11+
};
12+
use windows_sys::Win32::System::Pipes::{
13+
ConnectNamedPipe, CreateNamedPipeW, DisconnectNamedPipe, PIPE_ACCESS_DUPLEX,
14+
PIPE_READMODE_BYTE, PIPE_TYPE_BYTE, PIPE_UNLIMITED_INSTANCES, PIPE_WAIT,
15+
};
16+
use windows_sys::Win32::System::IO::{GetOverlappedResult, OVERLAPPED};
17+
18+
/// Named Pipe proxy status
19+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20+
pub enum PipeStatus {
21+
Init,
22+
Listening,
23+
Connected,
24+
Closed,
25+
}
26+
27+
/// TSI Named Pipe Proxy for Windows
28+
pub struct TsiPipeProxyWindows {
29+
pipe_handle: HANDLE,
30+
status: PipeStatus,
31+
pipe_name: String,
32+
}
33+
34+
impl TsiPipeProxyWindows {
35+
/// Create a new pipe proxy
36+
pub fn new() -> Self {
37+
Self {
38+
pipe_handle: INVALID_HANDLE_VALUE,
39+
status: PipeStatus::Init,
40+
pipe_name: String::new(),
41+
}
42+
}
43+
44+
/// Create and listen on a named pipe
45+
pub fn listen(&mut self, pipe_name: &str) -> Result<(), ProxyError> {
46+
if self.status != PipeStatus::Init {
47+
return Err(ProxyError::InvalidState);
48+
}
49+
50+
// Convert pipe name to Windows format: \\.\pipe\name
51+
let full_name = if pipe_name.starts_with("\\\\.\\pipe\\") {
52+
pipe_name.to_string()
53+
} else {
54+
format!("\\\\.\\pipe\\{}", pipe_name)
55+
};
56+
57+
// Convert to wide string
58+
let wide_name: Vec<u16> = full_name.encode_utf16().chain(std::iter::once(0)).collect();
59+
60+
// Create named pipe
61+
let handle = unsafe {
62+
CreateNamedPipeW(
63+
wide_name.as_ptr(),
64+
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
65+
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
66+
PIPE_UNLIMITED_INSTANCES,
67+
4096, // out buffer size
68+
4096, // in buffer size
69+
0, // default timeout
70+
ptr::null_mut(),
71+
)
72+
};
73+
74+
if handle == INVALID_HANDLE_VALUE {
75+
return Err(ProxyError::IoError(io::Error::last_os_error()));
76+
}
77+
78+
self.pipe_handle = handle;
79+
self.pipe_name = full_name;
80+
self.status = PipeStatus::Listening;
81+
Ok(())
82+
}
83+
84+
/// Accept a connection (blocking)
85+
pub fn accept(&mut self) -> Result<(), ProxyError> {
86+
if self.status != PipeStatus::Listening {
87+
return Err(ProxyError::InvalidState);
88+
}
89+
90+
let result = unsafe { ConnectNamedPipe(self.pipe_handle, ptr::null_mut()) };
91+
92+
if result == 0 {
93+
let err = io::Error::last_os_error();
94+
if err.raw_os_error() == Some(ERROR_PIPE_BUSY as i32) {
95+
return Err(ProxyError::WouldBlock);
96+
}
97+
return Err(ProxyError::IoError(err));
98+
}
99+
100+
self.status = PipeStatus::Connected;
101+
Ok(())
102+
}
103+
104+
/// Connect to an existing named pipe (client mode)
105+
pub fn connect(&mut self, pipe_name: &str) -> Result<(), ProxyError> {
106+
if self.status != PipeStatus::Init {
107+
return Err(ProxyError::InvalidState);
108+
}
109+
110+
// Convert pipe name to Windows format
111+
let full_name = if pipe_name.starts_with("\\\\.\\pipe\\") {
112+
pipe_name.to_string()
113+
} else {
114+
format!("\\\\.\\pipe\\{}", pipe_name)
115+
};
116+
117+
let wide_name: Vec<u16> = full_name.encode_utf16().chain(std::iter::once(0)).collect();
118+
119+
// Open existing pipe
120+
let handle = unsafe {
121+
CreateFileW(
122+
wide_name.as_ptr(),
123+
0x80000000 | 0x40000000, // GENERIC_READ | GENERIC_WRITE
124+
FILE_SHARE_READ | FILE_SHARE_WRITE,
125+
ptr::null_mut(),
126+
OPEN_EXISTING,
127+
FILE_FLAG_OVERLAPPED,
128+
0,
129+
)
130+
};
131+
132+
if handle == INVALID_HANDLE_VALUE {
133+
return Err(ProxyError::IoError(io::Error::last_os_error()));
134+
}
135+
136+
self.pipe_handle = handle;
137+
self.pipe_name = full_name;
138+
self.status = PipeStatus::Connected;
139+
Ok(())
140+
}
141+
142+
/// Send data through the pipe
143+
pub fn send_data(&mut self, data: &[u8]) -> Result<usize, ProxyError> {
144+
if self.status != PipeStatus::Connected {
145+
return Err(ProxyError::InvalidState);
146+
}
147+
148+
// Use std::fs::File wrapper for Write trait
149+
let mut file = unsafe { std::fs::File::from_raw_handle(self.pipe_handle as RawHandle) };
150+
let result = file.write(data);
151+
std::mem::forget(file); // Don't close the handle
152+
153+
result.map_err(|e| {
154+
if e.kind() == io::ErrorKind::WouldBlock {
155+
ProxyError::WouldBlock
156+
} else {
157+
ProxyError::IoError(e)
158+
}
159+
})
160+
}
161+
162+
/// Receive data from the pipe
163+
pub fn recv_data(&mut self, buf: &mut [u8]) -> Result<usize, ProxyError> {
164+
if self.status != PipeStatus::Connected {
165+
return Err(ProxyError::InvalidState);
166+
}
167+
168+
let mut file = unsafe { std::fs::File::from_raw_handle(self.pipe_handle as RawHandle) };
169+
let result = file.read(buf);
170+
std::mem::forget(file);
171+
172+
result.map_err(|e| {
173+
if e.kind() == io::ErrorKind::WouldBlock {
174+
ProxyError::WouldBlock
175+
} else {
176+
ProxyError::IoError(e)
177+
}
178+
})
179+
}
180+
181+
/// Disconnect the pipe
182+
pub fn disconnect(&mut self) -> Result<(), ProxyError> {
183+
if self.status == PipeStatus::Connected && self.pipe_handle != INVALID_HANDLE_VALUE {
184+
unsafe {
185+
DisconnectNamedPipe(self.pipe_handle);
186+
}
187+
self.status = PipeStatus::Listening;
188+
}
189+
Ok(())
190+
}
191+
192+
/// Get current status
193+
pub fn status(&self) -> PipeStatus {
194+
self.status
195+
}
196+
197+
/// Get pipe name
198+
pub fn pipe_name(&self) -> &str {
199+
&self.pipe_name
200+
}
201+
}
202+
203+
impl Drop for TsiPipeProxyWindows {
204+
fn drop(&mut self) {
205+
if self.pipe_handle != INVALID_HANDLE_VALUE {
206+
unsafe {
207+
CloseHandle(self.pipe_handle);
208+
}
209+
}
210+
}
211+
}
212+
213+
#[cfg(test)]
214+
mod tests {
215+
use super::*;
216+
217+
#[test]
218+
fn test_pipe_proxy_creation() {
219+
let proxy = TsiPipeProxyWindows::new();
220+
assert_eq!(proxy.status(), PipeStatus::Init);
221+
}
222+
223+
#[test]
224+
#[ignore] // Requires Windows Named Pipe support
225+
fn test_pipe_listen() {
226+
let mut proxy = TsiPipeProxyWindows::new();
227+
let result = proxy.listen("test_pipe_listen");
228+
assert!(result.is_ok());
229+
assert_eq!(proxy.status(), PipeStatus::Listening);
230+
}
231+
}

0 commit comments

Comments
 (0)