Skip to content

Commit ec30383

Browse files
authored
net: add UnixSocket (#6290)
1 parent f80bbec commit ec30383

File tree

7 files changed

+425
-0
lines changed

7 files changed

+425
-0
lines changed

tokio/src/net/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ cfg_net_unix! {
4949
pub use unix::datagram::socket::UnixDatagram;
5050
pub use unix::listener::UnixListener;
5151
pub use unix::stream::UnixStream;
52+
pub use unix::socket::UnixSocket;
5253
}
5354

5455
cfg_net_windows! {

tokio/src/net/unix/datagram/socket.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,16 @@ cfg_net_unix! {
9696
}
9797

9898
impl UnixDatagram {
99+
pub(crate) fn from_mio(sys: mio::net::UnixDatagram) -> io::Result<UnixDatagram> {
100+
let datagram = UnixDatagram::new(sys)?;
101+
102+
if let Some(e) = datagram.io.take_error()? {
103+
return Err(e);
104+
}
105+
106+
Ok(datagram)
107+
}
108+
99109
/// Waits for any of the requested ready states.
100110
///
101111
/// This function is usually paired with `try_recv()` or `try_send()`. It

tokio/src/net/unix/listener.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ cfg_net_unix! {
5050
}
5151

5252
impl UnixListener {
53+
pub(crate) fn new(listener: mio::net::UnixListener) -> io::Result<UnixListener> {
54+
let io = PollEvented::new(listener)?;
55+
Ok(UnixListener { io })
56+
}
57+
5358
/// Creates a new `UnixListener` bound to the specified path.
5459
///
5560
/// # Panics

tokio/src/net/unix/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ pub mod datagram;
77

88
pub(crate) mod listener;
99

10+
pub(crate) mod socket;
11+
1012
mod split;
1113
pub use split::{ReadHalf, WriteHalf};
1214

tokio/src/net/unix/socket.rs

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
use std::io;
2+
use std::path::Path;
3+
4+
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd};
5+
6+
use crate::net::{UnixDatagram, UnixListener, UnixStream};
7+
8+
cfg_net_unix! {
9+
/// A Unix socket that has not yet been converted to a [`UnixStream`], [`UnixDatagram`], or
10+
/// [`UnixListener`].
11+
///
12+
/// `UnixSocket` wraps an operating system socket and enables the caller to
13+
/// configure the socket before establishing a connection or accepting
14+
/// inbound connections. The caller is able to set socket option and explicitly
15+
/// bind the socket with a socket address.
16+
///
17+
/// The underlying socket is closed when the `UnixSocket` value is dropped.
18+
///
19+
/// `UnixSocket` should only be used directly if the default configuration used
20+
/// by [`UnixStream::connect`], [`UnixDatagram::bind`], and [`UnixListener::bind`]
21+
/// does not meet the required use case.
22+
///
23+
/// Calling `UnixStream::connect(path)` effectively performs the same function as:
24+
///
25+
/// ```no_run
26+
/// use tokio::net::UnixSocket;
27+
/// use std::error::Error;
28+
///
29+
/// #[tokio::main]
30+
/// async fn main() -> Result<(), Box<dyn Error>> {
31+
/// let dir = tempfile::tempdir().unwrap();
32+
/// let path = dir.path().join("bind_path");
33+
/// let socket = UnixSocket::new_stream()?;
34+
///
35+
/// let stream = socket.connect(path).await?;
36+
///
37+
/// Ok(())
38+
/// }
39+
/// ```
40+
///
41+
/// Calling `UnixDatagram::bind(path)` effectively performs the same function as:
42+
///
43+
/// ```no_run
44+
/// use tokio::net::UnixSocket;
45+
/// use std::error::Error;
46+
///
47+
/// #[tokio::main]
48+
/// async fn main() -> Result<(), Box<dyn Error>> {
49+
/// let dir = tempfile::tempdir().unwrap();
50+
/// let path = dir.path().join("bind_path");
51+
/// let socket = UnixSocket::new_datagram()?;
52+
/// socket.bind(path)?;
53+
///
54+
/// let datagram = socket.datagram()?;
55+
///
56+
/// Ok(())
57+
/// }
58+
/// ```
59+
///
60+
/// Calling `UnixListener::bind(path)` effectively performs the same function as:
61+
///
62+
/// ```no_run
63+
/// use tokio::net::UnixSocket;
64+
/// use std::error::Error;
65+
///
66+
/// #[tokio::main]
67+
/// async fn main() -> Result<(), Box<dyn Error>> {
68+
/// let dir = tempfile::tempdir().unwrap();
69+
/// let path = dir.path().join("bind_path");
70+
/// let socket = UnixSocket::new_stream()?;
71+
/// socket.bind(path)?;
72+
///
73+
/// let listener = socket.listen(1024)?;
74+
///
75+
/// Ok(())
76+
/// }
77+
/// ```
78+
///
79+
/// Setting socket options not explicitly provided by `UnixSocket` may be done by
80+
/// accessing the [`RawFd`]/[`RawSocket`] using [`AsRawFd`]/[`AsRawSocket`] and
81+
/// setting the option with a crate like [`socket2`].
82+
///
83+
/// [`RawFd`]: std::os::fd::RawFd
84+
/// [`RawSocket`]: https://doc.rust-lang.org/std/os/windows/io/type.RawSocket.html
85+
/// [`AsRawFd`]: std::os::fd::AsRawFd
86+
/// [`AsRawSocket`]: https://doc.rust-lang.org/std/os/windows/io/trait.AsRawSocket.html
87+
/// [`socket2`]: https://docs.rs/socket2/
88+
#[derive(Debug)]
89+
pub struct UnixSocket {
90+
inner: socket2::Socket,
91+
}
92+
}
93+
94+
impl UnixSocket {
95+
fn ty(&self) -> socket2::Type {
96+
self.inner.r#type().unwrap()
97+
}
98+
99+
/// Creates a new Unix datagram socket.
100+
///
101+
/// Calls `socket(2)` with `AF_UNIX` and `SOCK_DGRAM`.
102+
///
103+
/// # Returns
104+
///
105+
/// On success, the newly created [`UnixSocket`] is returned. If an error is
106+
/// encountered, it is returned instead.
107+
pub fn new_datagram() -> io::Result<UnixSocket> {
108+
UnixSocket::new(socket2::Type::DGRAM)
109+
}
110+
111+
/// Creates a new Unix stream socket.
112+
///
113+
/// Calls `socket(2)` with `AF_UNIX` and `SOCK_STREAM`.
114+
///
115+
/// # Returns
116+
///
117+
/// On success, the newly created [`UnixSocket`] is returned. If an error is
118+
/// encountered, it is returned instead.
119+
pub fn new_stream() -> io::Result<UnixSocket> {
120+
UnixSocket::new(socket2::Type::STREAM)
121+
}
122+
123+
fn new(ty: socket2::Type) -> io::Result<UnixSocket> {
124+
#[cfg(any(
125+
target_os = "android",
126+
target_os = "dragonfly",
127+
target_os = "freebsd",
128+
target_os = "fuchsia",
129+
target_os = "illumos",
130+
target_os = "linux",
131+
target_os = "netbsd",
132+
target_os = "openbsd"
133+
))]
134+
let ty = ty.nonblocking();
135+
let inner = socket2::Socket::new(socket2::Domain::UNIX, ty, None)?;
136+
#[cfg(not(any(
137+
target_os = "android",
138+
target_os = "dragonfly",
139+
target_os = "freebsd",
140+
target_os = "fuchsia",
141+
target_os = "illumos",
142+
target_os = "linux",
143+
target_os = "netbsd",
144+
target_os = "openbsd"
145+
)))]
146+
inner.set_nonblocking(true)?;
147+
Ok(UnixSocket { inner })
148+
}
149+
150+
/// Binds the socket to the given address.
151+
///
152+
/// This calls the `bind(2)` operating-system function.
153+
pub fn bind(&self, path: impl AsRef<Path>) -> io::Result<()> {
154+
let addr = socket2::SockAddr::unix(path)?;
155+
self.inner.bind(&addr)
156+
}
157+
158+
/// Converts the socket into a `UnixListener`.
159+
///
160+
/// `backlog` defines the maximum number of pending connections are queued
161+
/// by the operating system at any given time. Connection are removed from
162+
/// the queue with [`UnixListener::accept`]. When the queue is full, the
163+
/// operating-system will start rejecting connections.
164+
///
165+
/// Calling this function on a socket created by [`new_datagram`] will return an error.
166+
///
167+
/// This calls the `listen(2)` operating-system function, marking the socket
168+
/// as a passive socket.
169+
///
170+
/// [`new_datagram`]: `UnixSocket::new_datagram`
171+
pub fn listen(self, backlog: u32) -> io::Result<UnixListener> {
172+
if self.ty() == socket2::Type::DGRAM {
173+
return Err(io::Error::new(
174+
io::ErrorKind::Other,
175+
"listen cannot be called on a datagram socket",
176+
));
177+
}
178+
179+
self.inner.listen(backlog as i32)?;
180+
let mio = {
181+
use std::os::unix::io::{FromRawFd, IntoRawFd};
182+
183+
let raw_fd = self.inner.into_raw_fd();
184+
unsafe { mio::net::UnixListener::from_raw_fd(raw_fd) }
185+
};
186+
187+
UnixListener::new(mio)
188+
}
189+
190+
/// Establishes a Unix connection with a peer at the specified socket address.
191+
///
192+
/// The `UnixSocket` is consumed. Once the connection is established, a
193+
/// connected [`UnixStream`] is returned. If the connection fails, the
194+
/// encountered error is returned.
195+
///
196+
/// Calling this function on a socket created by [`new_datagram`] will return an error.
197+
///
198+
/// This calls the `connect(2)` operating-system function.
199+
///
200+
/// [`new_datagram`]: `UnixSocket::new_datagram`
201+
pub async fn connect(self, path: impl AsRef<Path>) -> io::Result<UnixStream> {
202+
if self.ty() == socket2::Type::DGRAM {
203+
return Err(io::Error::new(
204+
io::ErrorKind::Other,
205+
"connect cannot be called on a datagram socket",
206+
));
207+
}
208+
209+
let addr = socket2::SockAddr::unix(path)?;
210+
if let Err(err) = self.inner.connect(&addr) {
211+
if err.raw_os_error() != Some(libc::EINPROGRESS) {
212+
return Err(err);
213+
}
214+
}
215+
let mio = {
216+
use std::os::unix::io::{FromRawFd, IntoRawFd};
217+
218+
let raw_fd = self.inner.into_raw_fd();
219+
unsafe { mio::net::UnixStream::from_raw_fd(raw_fd) }
220+
};
221+
222+
UnixStream::connect_mio(mio).await
223+
}
224+
225+
/// Converts the socket into a [`UnixDatagram`].
226+
///
227+
/// Calling this function on a socket created by [`new_stream`] will return an error.
228+
///
229+
/// [`new_stream`]: `UnixSocket::new_stream`
230+
pub fn datagram(self) -> io::Result<UnixDatagram> {
231+
if self.ty() == socket2::Type::STREAM {
232+
return Err(io::Error::new(
233+
io::ErrorKind::Other,
234+
"datagram cannot be called on a stream socket",
235+
));
236+
}
237+
let mio = {
238+
use std::os::unix::io::{FromRawFd, IntoRawFd};
239+
240+
let raw_fd = self.inner.into_raw_fd();
241+
unsafe { mio::net::UnixDatagram::from_raw_fd(raw_fd) }
242+
};
243+
244+
UnixDatagram::from_mio(mio)
245+
}
246+
}
247+
248+
impl AsRawFd for UnixSocket {
249+
fn as_raw_fd(&self) -> RawFd {
250+
self.inner.as_raw_fd()
251+
}
252+
}
253+
254+
impl AsFd for UnixSocket {
255+
fn as_fd(&self) -> BorrowedFd<'_> {
256+
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
257+
}
258+
}
259+
260+
impl FromRawFd for UnixSocket {
261+
unsafe fn from_raw_fd(fd: RawFd) -> UnixSocket {
262+
let inner = socket2::Socket::from_raw_fd(fd);
263+
UnixSocket { inner }
264+
}
265+
}
266+
267+
impl IntoRawFd for UnixSocket {
268+
fn into_raw_fd(self) -> RawFd {
269+
self.inner.into_raw_fd()
270+
}
271+
}

tokio/src/net/unix/stream.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,24 @@ cfg_net_unix! {
3939
}
4040

4141
impl UnixStream {
42+
pub(crate) async fn connect_mio(sys: mio::net::UnixStream) -> io::Result<UnixStream> {
43+
let stream = UnixStream::new(sys)?;
44+
45+
// Once we've connected, wait for the stream to be writable as
46+
// that's when the actual connection has been initiated. Once we're
47+
// writable we check for `take_socket_error` to see if the connect
48+
// actually hit an error or not.
49+
//
50+
// If all that succeeded then we ship everything on up.
51+
poll_fn(|cx| stream.io.registration().poll_write_ready(cx)).await?;
52+
53+
if let Some(e) = stream.io.take_error()? {
54+
return Err(e);
55+
}
56+
57+
Ok(stream)
58+
}
59+
4260
/// Connects to the socket named by `path`.
4361
///
4462
/// This function will create a new Unix socket and connect to the path

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy