Skip to content

Commit 11982ad

Browse files
committed
feat(firecracker,unix): use OsString to allow abstract names
1 parent cee16a2 commit 11982ad

File tree

8 files changed

+123
-24
lines changed

8 files changed

+123
-24
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ version = "0.6.0"
44
edition = "2021"
55
license = "MIT"
66
description = "A Hyper client library allowing access to Unix, VSock and Firecracker sockets"
7-
rust-version = "1.63"
7+
rust-version = "1.74"
88
repository = "https://github.com/rust-firecracker/hyper-client-sockets"
99
readme = "README.md"
1010
keywords = ["hyper", "client", "sockets"]

src/async_io/vsock.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ impl AsyncVsockIo {
5050
// TODO: Once https://github.com/rust-lang/rust/issues/63569 is stable, use `assume_init_mut`:
5151
let buffer = unsafe { &mut *(cursor.as_mut() as *mut [MaybeUninit<u8>] as *mut [u8]) };
5252
let amount = self.0.get_ref().read(buffer);
53-
try_advance_cursor(cursor, self.0.get_ref().read(buffer))
53+
try_advance_cursor(cursor, amount)
5454
}
5555
other => Some(other),
5656
}

src/uri/firecracker.rs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::{
2+
ffi::OsString,
23
io::Result as IoResult,
4+
os::unix::ffi::OsStringExt as _,
35
path::{Path, PathBuf},
46
};
57

@@ -27,26 +29,33 @@ impl FirecrackerUri for Uri {
2729
guest_port: u32,
2830
url: impl AsRef<str>,
2931
) -> Result<Uri, InvalidUri> {
30-
let host = host_socket_path.as_ref().to_string_lossy().to_string();
31-
let authority = encode(format!("{host}:{guest_port}"));
32+
let host = encode(host_socket_path.as_ref().as_os_str().as_encoded_bytes());
33+
let guest_port = encode(guest_port.to_string());
34+
let authority = format!("{host}{:02x}{guest_port}", b':');
3235
let path_and_query = url.as_ref().trim_start_matches('/');
3336
let uri_str = format!("fc://{authority}/{path_and_query}");
3437
uri_str.parse()
3538
}
3639

3740
fn parse_firecracker(&self) -> IoResult<(PathBuf, u32)> {
3841
if self.scheme_str() == Some("fc") {
39-
let host = self.host().ok_or_else(|| io_input_err("URI host must be present"))?;
40-
let hex_decoded = Vec::from_hex(host).map_err(|_| io_input_err("URI host must be hex"))?;
41-
let full_str = String::from_utf8_lossy(&hex_decoded).into_owned();
42-
let splits = full_str
43-
.split_once(':')
44-
.ok_or_else(|| io_input_err("URI host could not be split in halves with a ."))?;
45-
let host_socket_path = PathBuf::from(splits.0);
46-
let guest_port = splits
42+
let host_hex = self.host().ok_or_else(|| io_input_err("URI host must be present"))?;
43+
let mut host_octets =
44+
Vec::from_hex(host_hex).map_err(|_| io_input_err("URI host must be hexadecimal encoded"))?;
45+
46+
let colon_pos = host_octets
47+
.iter()
48+
.rposition(|octet| *octet == b':')
49+
.ok_or_else(|| io_input_err("URI host does not encode port"))?;
50+
51+
let guest_port = String::from_utf8(host_octets.split_off(colon_pos))
52+
.map_err(|_| io_input_err("URI guest port is not valid UTF8"))?
53+
.split_at(1)
4754
.1
4855
.parse::<u32>()
49-
.map_err(|_| io_input_err("URI guest port could not converted to u32"))?;
56+
.map_err(|_| io_input_err("URI guest port could not be parsed"))?;
57+
58+
let host_socket_path = OsString::from_vec(host_octets).into();
5059

5160
Ok((host_socket_path, guest_port))
5261
} else {

src/uri/tests.rs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use vsock::VsockAddr;
66
use crate::uri::{FirecrackerUri, UnixUri, VsockUri};
77

88
#[test]
9-
fn unix_uri_should_be_constructed_correctly() {
9+
fn unix_uri_with_pathname_should_be_constructed_correctly() {
1010
let uri_str = format!("unix://{}/route", hex::encode("/tmp/socket.sock"));
1111
assert_eq!(
1212
Uri::unix("/tmp/socket.sock", "/route").unwrap(),
@@ -15,14 +15,32 @@ fn unix_uri_should_be_constructed_correctly() {
1515
}
1616

1717
#[test]
18-
fn unix_uri_should_be_deconstructed_correctly() {
18+
fn unix_uri_with_abstract_name_should_be_constructed_correctly() {
19+
let uri_str = format!("unix://00{}/route", hex::encode("/tmp/socket.sock"));
20+
assert_eq!(
21+
Uri::unix("\0/tmp/socket.sock", "/route").unwrap(),
22+
uri_str.parse::<Uri>().unwrap()
23+
);
24+
}
25+
26+
#[test]
27+
fn unix_uri_with_pathname_should_be_deconstructed_correctly() {
1928
let uri = format!("unix://{}/route", hex::encode("/tmp/socket.sock"));
2029
assert_eq!(
2130
uri.parse::<Uri>().unwrap().parse_unix().unwrap(),
2231
PathBuf::from("/tmp/socket.sock")
2332
);
2433
}
2534

35+
#[test]
36+
fn unix_uri_with_abstract_name_should_be_deconstructed_correctly() {
37+
let uri = format!("unix://00{}/route", hex::encode("/tmp/socket.sock"));
38+
assert_eq!(
39+
uri.parse::<Uri>().unwrap().parse_unix().unwrap(),
40+
PathBuf::from("\0/tmp/socket.sock")
41+
);
42+
}
43+
2644
#[test]
2745
fn vsock_uri_should_be_constructed_correctly() {
2846
let uri = format!("vsock://{}/route", hex::encode("10.20"));
@@ -41,7 +59,7 @@ fn vsock_uri_should_be_deconstructed_correctly() {
4159
}
4260

4361
#[test]
44-
fn firecracker_uri_should_be_constructed_correctly() {
62+
fn firecracker_uri_with_pathname_should_be_constructed_correctly() {
4563
let uri_str = format!("fc://{}/route", hex::encode("/tmp/socket.sock:1000"));
4664
assert_eq!(
4765
Uri::firecracker("/tmp/socket.sock", 1000, "/route").unwrap(),
@@ -50,11 +68,30 @@ fn firecracker_uri_should_be_constructed_correctly() {
5068
}
5169

5270
#[test]
53-
fn firecracker_uri_should_be_deconstructed_correctly() {
71+
fn firecracker_uri_with_abstract_name_should_be_constructed_correctly() {
72+
let uri_str = format!("fc://00{}/route", hex::encode("/tmp/socket.sock:1000"));
73+
assert_eq!(
74+
Uri::firecracker("\0/tmp/socket.sock", 1000, "/route").unwrap(),
75+
uri_str.parse::<Uri>().unwrap()
76+
);
77+
}
78+
79+
#[test]
80+
fn firecracker_uri_with_pathname_should_be_deconstructed_correctly() {
5481
let uri = format!("fc://{}/route", hex::encode("/tmp/socket.sock:1000"))
5582
.parse::<Uri>()
5683
.unwrap();
5784
let (socket_path, port) = uri.parse_firecracker().unwrap();
5885
assert_eq!(socket_path, PathBuf::from("/tmp/socket.sock"));
5986
assert_eq!(port, 1000);
6087
}
88+
89+
#[test]
90+
fn firecracker_uri_with_abstract_name_should_be_deconstructed_correctly() {
91+
let uri = format!("fc://00{}/route", hex::encode("/tmp/socket.sock:1000"))
92+
.parse::<Uri>()
93+
.unwrap();
94+
let (socket_path, port) = uri.parse_firecracker().unwrap();
95+
assert_eq!(socket_path, PathBuf::from("\0/tmp/socket.sock"));
96+
assert_eq!(port, 1000);
97+
}

src/uri/unix.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::{
2+
ffi::OsString,
23
io::Result as IoResult,
4+
os::unix::ffi::OsStringExt as _,
35
path::{Path, PathBuf},
46
};
57

@@ -19,7 +21,7 @@ pub trait UnixUri {
1921

2022
impl UnixUri for Uri {
2123
fn unix(socket_path: impl AsRef<Path>, url: impl AsRef<str>) -> Result<Uri, InvalidUri> {
22-
let authority = encode(socket_path.as_ref().to_string_lossy().to_string());
24+
let authority = encode(socket_path.as_ref().as_os_str().as_encoded_bytes());
2325
let path_and_query = url.as_ref().trim_start_matches('/');
2426
let uri_str = format!("unix://{authority}/{path_and_query}");
2527
uri_str.parse()
@@ -29,8 +31,8 @@ impl UnixUri for Uri {
2931
if self.scheme_str() == Some("unix") {
3032
match self.host() {
3133
Some(host) => {
32-
let bytes = Vec::from_hex(host).map_err(|_| io_input_err("URI host must be hex"))?;
33-
Ok(PathBuf::from(String::from_utf8_lossy(&bytes).into_owned()))
34+
let octets = Vec::from_hex(host).map_err(|_| io_input_err("URI host must be hexadecimal"))?;
35+
Ok(OsString::from_vec(octets).into())
3436
}
3537
None => Err(io_input_err("URI host must be present")),
3638
}

tests/async_io.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::{future::Future, sync::Arc};
22

33
use async_executor::Executor;
44
use bytes::Bytes;
5-
use common::{check_response, serve_firecracker, serve_unix, serve_vsock};
5+
use common::{check_response, serve_firecracker, serve_unix, serve_unix_abstract, serve_vsock};
66
use http::{Request, Uri};
77
use http_body_util::Full;
88
use hyper::client::conn::http1::handshake;
@@ -18,7 +18,7 @@ use smol_hyper::rt::SmolExecutor;
1818
mod common;
1919

2020
#[test]
21-
fn async_io_unix_raw_connectivity() {
21+
fn async_io_unix_raw_connectivity_with_pathname() {
2222
run(|executor| async move {
2323
let socket_path = serve_unix();
2424
let io = AsyncIoBackend::connect_to_unix_socket(&socket_path).await.unwrap();
@@ -32,6 +32,21 @@ fn async_io_unix_raw_connectivity() {
3232
});
3333
}
3434

35+
#[test]
36+
fn async_io_unix_raw_connectivity_with_abstract_name() {
37+
run(|executor| async move {
38+
let socket_path = serve_unix_abstract();
39+
let io = AsyncIoBackend::connect_to_unix_socket(&socket_path).await.unwrap();
40+
let (mut send_request, conn) = handshake::<_, Full<Bytes>>(io).await.unwrap();
41+
executor.spawn(conn).detach();
42+
let response = send_request
43+
.send_request(Request::new(Full::new(Bytes::new())))
44+
.await
45+
.unwrap();
46+
check_response(response).await;
47+
});
48+
}
49+
3550
#[test]
3651
fn async_io_unix_pooled_connectivity() {
3752
run(|executor| async move {

tests/common.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,29 @@ pub fn serve_unix() -> PathBuf {
3737
socket_path
3838
}
3939

40+
#[allow(unused)]
41+
pub fn serve_unix_abstract() -> PathBuf {
42+
let socket_path = PathBuf::from("\0test").with_extension(Uuid::new_v4().to_string());
43+
44+
let cloned_socket_path = socket_path.clone();
45+
in_tokio_thread(async move {
46+
let listener = UnixListener::bind(cloned_socket_path).unwrap();
47+
48+
loop {
49+
let (stream, _) = listener.accept().await.unwrap();
50+
tokio::spawn(async move {
51+
http1::Builder::new()
52+
.serve_connection(TokioIo::new(stream), service_fn(responder))
53+
.await
54+
.unwrap();
55+
});
56+
}
57+
});
58+
59+
std::thread::sleep(Duration::from_millis(1));
60+
socket_path
61+
}
62+
4063
#[allow(unused)]
4164
pub fn serve_vsock() -> VsockAddr {
4265
let port = fastrand::u32(15000..=65536);

tests/tokio.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use bytes::Bytes;
2-
use common::{check_response, serve_firecracker, serve_unix, serve_vsock};
2+
use common::{check_response, serve_firecracker, serve_unix, serve_unix_abstract, serve_vsock};
33
use http::{Request, Uri};
44
use http_body_util::Full;
55
use hyper::client::conn::http1::handshake;
@@ -14,7 +14,7 @@ use hyper_util::{client::legacy::Client, rt::TokioExecutor};
1414
mod common;
1515

1616
#[tokio::test]
17-
async fn tokio_unix_raw_connectivity() {
17+
async fn tokio_unix_raw_connectivity_with_pathname() {
1818
let socket_path = serve_unix();
1919
let io = TokioBackend::connect_to_unix_socket(&socket_path).await.unwrap();
2020
let (mut send_request, conn) = handshake::<_, Full<Bytes>>(io).await.unwrap();
@@ -26,6 +26,19 @@ async fn tokio_unix_raw_connectivity() {
2626
check_response(response).await;
2727
}
2828

29+
#[tokio::test]
30+
async fn tokio_unix_raw_connectivity_with_abstract_name() {
31+
let socket_path = serve_unix_abstract();
32+
let io = TokioBackend::connect_to_unix_socket(&socket_path).await.unwrap();
33+
let (mut send_request, conn) = handshake::<_, Full<Bytes>>(io).await.unwrap();
34+
tokio::spawn(conn);
35+
let response = send_request
36+
.send_request(Request::new(Full::new(Bytes::new())))
37+
.await
38+
.unwrap();
39+
check_response(response).await;
40+
}
41+
2942
#[tokio::test]
3043
async fn tokio_unix_pooled_connectivity() {
3144
let socket_path = serve_unix();

0 commit comments

Comments
 (0)