1use std::io::{self, Read, Write};
6use std::net::Shutdown;
7#[cfg(unix)]
8use std::path::{Path, PathBuf};
9#[cfg(unix)]
10use std::sync::{Arc, Mutex, PoisonError};
11#[cfg(unix)]
12use std::time::{Duration, Instant};
13
14#[cfg(unix)]
15use std::os::fd::{AsFd, BorrowedFd, OwnedFd};
16
17#[cfg(unix)]
18use crate::accept::{Accepted, Acceptor};
19#[cfg(unix)]
20use crate::driver::DriverKind;
21use crate::error::NetError;
22#[cfg(unix)]
23use crate::error::{ConfigReason, LimitKind, TimeoutKind};
24use crate::gate::KillHandle;
25#[cfg(unix)]
26use crate::id::{ConnectionId, ListenerId};
27use crate::info::ConnectionInfo;
28#[cfg(unix)]
29use crate::info::{Endpoint, Origin, PeerCredentials, Transport, UnixPath};
30use crate::limits::Limits;
31#[cfg(unix)]
32use crate::link::{ClockRef, Got, Link, WakeSource};
33#[cfg(unix)]
34use crate::opts::LocalOptions;
35#[cfg(unix)]
36use crate::proxy::ProxyMode;
37#[cfg(unix)]
38use crate::session::Phase;
39#[cfg(unix)]
40use crate::sys::{self, Interest, KillSlot, Ready};
41#[cfg(unix)]
42use crate::tcp::{ListenerInfo, TcpConnection};
43
44#[cfg(unix)]
46const CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
47
48#[cfg(unix)]
57pub struct Descriptors {
58 slots: [Option<OwnedFd>; sys::MAX_FDS],
59 len: usize,
60}
61
62#[cfg(unix)]
63impl Descriptors {
64 #[must_use]
66 pub fn new() -> Descriptors {
67 Descriptors {
68 slots: [const { None }; sys::MAX_FDS],
69 len: 0,
70 }
71 }
72
73 #[must_use]
75 pub fn len(&self) -> usize {
76 self.len
77 }
78
79 #[must_use]
81 pub fn is_empty(&self) -> bool {
82 self.len == 0
83 }
84
85 pub fn take(&mut self) -> impl Iterator<Item = OwnedFd> + '_ {
87 self.len = 0;
88 self.slots.iter_mut().filter_map(Option::take)
89 }
90
91 fn clear(&mut self) {
92 for slot in &mut self.slots {
93 *slot = None;
94 }
95 self.len = 0;
96 }
97}
98
99#[cfg(unix)]
100impl Default for Descriptors {
101 fn default() -> Descriptors {
102 Descriptors::new()
103 }
104}
105
106#[cfg(unix)]
107impl std::fmt::Debug for Descriptors {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 f.debug_struct("Descriptors")
110 .field("len", &self.len)
111 .finish()
112 }
113}
114
115#[cfg(unix)]
117type FileId = (u64, u64);
118
119#[cfg(unix)]
120fn file_id(path: &Path) -> Option<(FileId, bool)> {
121 use std::os::unix::fs::{FileTypeExt, MetadataExt};
122 let meta = std::fs::symlink_metadata(path).ok()?;
124 Some(((meta.dev(), meta.ino()), meta.file_type().is_socket()))
125}
126
127#[cfg(unix)]
129fn path_bytes(path: &Path) -> Result<&[u8], NetError> {
130 let bytes = path.as_os_str().as_encoded_bytes();
131 if bytes.is_empty() {
132 return Err(NetError::InvalidConfig {
133 field: "path",
134 reason: ConfigReason::Zero,
135 });
136 }
137 if bytes.contains(&0) {
138 return Err(NetError::InvalidConfig {
139 field: "path",
140 reason: ConfigReason::Malformed,
141 });
142 }
143 if bytes.len() >= sys::UNIX_PATH_MAX {
144 return Err(NetError::PathTooLong {
145 max: sys::UNIX_PATH_MAX,
146 });
147 }
148 Ok(bytes)
149}
150
151#[cfg(unix)]
152fn os_error(op: &'static str, e: io::Error) -> NetError {
153 match e.kind() {
154 io::ErrorKind::PermissionDenied => NetError::PermissionDenied { what: op },
155 io::ErrorKind::NotFound => NetError::NotFound,
156 _ => NetError::Os { op, source: e },
157 }
158}
159
160#[cfg(unix)]
162fn live(path: &[u8]) -> Result<bool, NetError> {
163 let sock = sys::unix_socket().map_err(|e| NetError::from_os("socket", e))?;
164 let probe = std::os::unix::net::UnixStream::from(sock);
165 let _ = probe.set_write_timeout(Some(Duration::from_secs(1)));
167 match sys::unix_connect(probe.as_fd(), path) {
168 Ok(()) => Ok(true),
169 Err(e) if e.kind() == io::ErrorKind::ConnectionRefused => Ok(false),
170 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(false),
171 Err(e)
173 if matches!(
174 e.kind(),
175 io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut
176 ) =>
177 {
178 Ok(true)
179 }
180 Err(e) => Err(os_error("connect", e)),
181 }
182}
183
184#[cfg(unix)]
195pub struct UnixListener {
196 inner: std::os::unix::net::UnixListener,
197 path: PathBuf,
198 created: Option<FileId>,
199 keep: std::sync::atomic::AtomicBool,
201 shared_path: Arc<UnixPath>,
202 info: ListenerInfo,
203 require_uid: Option<u32>,
204 acceptor: Mutex<Acceptor>,
205}
206
207#[cfg(unix)]
208impl UnixListener {
209 pub fn bind<P: AsRef<Path>>(path: P) -> Result<UnixListener, NetError> {
216 UnixListener::bind_with(path, LocalOptions::new())
217 }
218
219 pub fn bind_with<P: AsRef<Path>>(
227 path: P,
228 options: LocalOptions,
229 ) -> Result<UnixListener, NetError> {
230 UnixListener::create(path.as_ref(), &options, false)
231 }
232
233 pub fn bind_atomic<P: AsRef<Path>>(
241 path: P,
242 options: LocalOptions,
243 ) -> Result<UnixListener, NetError> {
244 UnixListener::create(path.as_ref(), &options, true)
245 }
246
247 fn create(path: &Path, options: &LocalOptions, atomic: bool) -> Result<UnixListener, NetError> {
248 let bytes = path_bytes(path)?;
249 let shared_path = Arc::new(UnixPath::new(bytes).ok_or(NetError::PathTooLong {
250 max: sys::UNIX_PATH_MAX,
251 })?);
252 if let Some((_, is_socket)) = file_id(path) {
254 let stale = is_socket && !live(bytes)?;
255 if !stale {
256 return Err(NetError::StaleSocketInUse {
257 path: path.to_path_buf(),
258 });
259 }
260 if !options.remove_stale {
261 return Err(NetError::StaleSocketInUse {
262 path: path.to_path_buf(),
263 });
264 }
265 if !atomic {
266 std::fs::remove_file(path).map_err(|e| os_error("unlink", e))?;
268 }
269 }
270 let target = if atomic {
271 temporary_name(path)?
272 } else {
273 path.to_path_buf()
274 };
275 let target_bytes = path_bytes(&target)?;
276 let sock = sys::unix_socket().map_err(|e| NetError::from_os("socket", e))?;
277 if let Err(e) = sys::unix_bind(sock.as_fd(), target_bytes) {
278 return Err(match e.kind() {
279 io::ErrorKind::AddrInUse => NetError::StaleSocketInUse {
280 path: path.to_path_buf(),
281 },
282 _ => os_error("bind", e),
283 });
284 }
285 let result = (|| {
286 let created = match file_id(&target) {
287 Some((id, true)) => id,
288 _ => return Err(replaced(&target)),
289 };
290 set_access(&target, options)?;
291 if file_id(&target) != Some((created, true)) {
293 return Err(replaced(&target));
294 }
295 sys::unix_listen(sock.as_fd(), options.backlog)
296 .map_err(|e| NetError::from_os("listen", e))?;
297 if atomic {
298 std::fs::rename(&target, path).map_err(|e| os_error("rename", e))?;
299 }
300 Ok(created)
301 })();
302 let created = match result {
303 Ok(id) => id,
304 Err(e) => {
305 if file_id(&target).is_some_and(|(_, sock)| sock) {
306 let _ = std::fs::remove_file(&target);
307 }
308 return Err(e);
309 }
310 };
311 let inner = std::os::unix::net::UnixListener::from(sock);
312 inner
314 .set_nonblocking(true)
315 .map_err(|e| NetError::from_os("ioctl", e))?;
316 Ok(UnixListener {
317 inner,
318 path: path.to_path_buf(),
319 created: Some(created),
320 keep: std::sync::atomic::AtomicBool::new(false),
321 info: ListenerInfo {
322 id: ListenerId(0),
323 name: None,
324 local: Endpoint::Unix(shared_path.clone()),
325 backlog: if cfg!(target_os = "linux") {
326 crate::tcp::effective_backlog(options.backlog)
327 } else {
328 None
329 },
330 transport: Transport::Unix,
331 proxy: ProxyMode::Off,
332 tls: false,
333 },
334 shared_path,
335 require_uid: options.require_uid,
336 acceptor: Mutex::new(Acceptor::new()),
337 })
338 }
339
340 pub fn accept(&self) -> Result<(UnixConnection, ConnectionInfo), NetError> {
348 match self.accept_until(None)? {
349 Some(pair) => Ok(pair),
350 None => Err(NetError::TimedOut {
351 kind: TimeoutKind::Read,
352 }),
353 }
354 }
355
356 pub fn accept_timeout(
362 &self,
363 timeout: Duration,
364 ) -> Result<Option<(UnixConnection, ConnectionInfo)>, NetError> {
365 self.accept_until(Some(Instant::now() + timeout))
366 }
367
368 fn accept_until(
369 &self,
370 deadline: Option<Instant>,
371 ) -> Result<Option<(UnixConnection, ConnectionInfo)>, NetError> {
372 let mut paused_once = false;
373 loop {
374 let outcome = self
375 .acceptor
376 .lock()
377 .unwrap_or_else(PoisonError::into_inner)
378 .accept_unix(&self.inner);
379 match outcome {
380 Accepted::Conn(stream) => {
381 let credentials = credentials_of(&stream);
382 if !self.permits(credentials) {
383 continue;
384 }
385 let info = ConnectionInfo::new_unix(
386 crate::id::issue_single(),
387 Some(self.info.id),
388 Origin::Accepted,
389 Endpoint::Unix(self.shared_path.clone()),
390 Endpoint::Unnamed,
391 credentials,
392 Instant::now(),
393 );
394 let conn = UnixConnection::from_stream(stream, info.clone(), Phase::Idle);
395 return Ok(Some((conn, info)));
396 }
397 Accepted::Skipped(_) => continue,
398 Accepted::Again => {
399 let timeout = match deadline {
400 None => None,
401 Some(d) => {
402 let left = d.saturating_duration_since(Instant::now());
403 if left.is_zero() {
404 return Ok(None);
405 }
406 Some(left)
407 }
408 };
409 match sys::wait(self.inner.as_fd(), Interest::Read, None, timeout) {
410 Ok(Ready::TimedOut) => return Ok(None),
411 Ok(_) => continue,
412 Err(e) => return Err(NetError::from_os("poll", e)),
413 }
414 }
415 Accepted::Pause(backoff) => {
416 if paused_once {
417 return Err(NetError::Limit {
418 which: LimitKind::Descriptors,
419 });
420 }
421 paused_once = true;
422 std::thread::sleep(backoff);
423 }
424 Accepted::Failed(e) => return Err(NetError::from_os("accept", e)),
425 }
426 }
427 }
428
429 pub(crate) fn adopt(sock: OwnedFd) -> Result<UnixListener, NetError> {
432 let inner = std::os::unix::net::UnixListener::from(sock);
433 inner
434 .set_nonblocking(true)
435 .map_err(|e| NetError::from_os("ioctl", e))?;
436 let path = inner
437 .local_addr()
438 .ok()
439 .and_then(|a| a.as_pathname().map(Path::to_path_buf))
440 .unwrap_or_default();
441 let shared_path = Arc::new(UnixPath::new(path.as_os_str().as_encoded_bytes()).ok_or(
442 NetError::PathTooLong {
443 max: sys::UNIX_PATH_MAX,
444 },
445 )?);
446 let local = if path.as_os_str().is_empty() {
447 Endpoint::Unnamed
448 } else {
449 Endpoint::Unix(shared_path.clone())
450 };
451 Ok(UnixListener {
452 inner,
453 path,
454 created: None,
455 keep: std::sync::atomic::AtomicBool::new(true),
456 info: ListenerInfo {
457 id: ListenerId(0),
458 name: None,
459 local,
460 backlog: None,
461 transport: Transport::Unix,
462 proxy: ProxyMode::Off,
463 tls: false,
464 },
465 shared_path,
466 require_uid: None,
467 acceptor: Mutex::new(Acceptor::new()),
468 })
469 }
470
471 pub(crate) fn keep_file(&self) {
473 self.keep.store(true, std::sync::atomic::Ordering::Release);
474 }
475
476 pub(crate) fn permits(&self, credentials: Option<PeerCredentials>) -> bool {
478 match self.require_uid {
479 None => true,
480 Some(uid) => credentials.and_then(|c| c.uid) == Some(uid),
481 }
482 }
483
484 pub(crate) fn std(&self) -> &std::os::unix::net::UnixListener {
485 &self.inner
486 }
487
488 pub(crate) fn shared_path(&self) -> &Arc<UnixPath> {
489 &self.shared_path
490 }
491
492 pub(crate) fn set_identity(&mut self, id: ListenerId, name: Option<String>) {
493 self.info.id = id;
494 self.info.name = name;
495 }
496
497 pub(crate) fn set_proxy_mode(&mut self, mode: ProxyMode) {
498 self.info.proxy = mode;
499 }
500
501 pub(crate) fn set_tls(&mut self, on: bool) {
502 self.info.tls = on;
503 }
504
505 #[must_use]
507 pub fn info(&self) -> &ListenerInfo {
508 &self.info
509 }
510
511 #[must_use]
514 pub fn path(&self) -> Option<&Path> {
515 (!self.path.as_os_str().is_empty()).then_some(self.path.as_path())
516 }
517}
518
519#[cfg(unix)]
520impl Drop for UnixListener {
521 fn drop(&mut self) {
524 if self.keep.load(std::sync::atomic::Ordering::Acquire) {
525 return;
526 }
527 if let Some(created) = self.created {
528 if file_id(&self.path) == Some((created, true)) {
529 let _ = std::fs::remove_file(&self.path);
530 }
531 }
532 }
533}
534
535#[cfg(unix)]
536impl AsFd for UnixListener {
537 fn as_fd(&self) -> BorrowedFd<'_> {
538 self.inner.as_fd()
539 }
540}
541
542#[cfg(unix)]
543impl std::fmt::Debug for UnixListener {
544 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
545 f.debug_struct("UnixListener")
546 .field("path", &self.path)
547 .field("info", &self.info)
548 .finish()
549 }
550}
551
552#[cfg(unix)]
553fn replaced(path: &Path) -> NetError {
554 NetError::StaleSocketInUse {
555 path: path.to_path_buf(),
556 }
557}
558
559#[cfg(unix)]
562fn set_access(path: &Path, options: &LocalOptions) -> Result<(), NetError> {
563 use std::os::unix::fs::PermissionsExt;
564 std::fs::set_permissions(path, std::fs::Permissions::from_mode(options.mode))
565 .map_err(|e| os_error("chmod", e))?;
566 if options.owner.is_some() || options.group.is_some() {
567 std::os::unix::fs::lchown(path, options.owner, options.group)
568 .map_err(|e| os_error("chown", e))?;
569 }
570 Ok(())
571}
572
573#[cfg(unix)]
575fn temporary_name(path: &Path) -> Result<PathBuf, NetError> {
576 use std::sync::atomic::{AtomicU32, Ordering};
577 static NEXT: AtomicU32 = AtomicU32::new(0);
578 let file = path.file_name().ok_or(NetError::InvalidConfig {
579 field: "path",
580 reason: ConfigReason::Malformed,
581 })?;
582 let mut name = std::ffi::OsString::from(".");
583 name.push(file);
584 name.push(format!(
585 ".{}.{}",
586 std::process::id(),
587 NEXT.fetch_add(1, Ordering::Relaxed)
588 ));
589 Ok(path.with_file_name(name))
590}
591
592#[cfg(unix)]
593pub(crate) fn credentials_of(stream: &std::os::unix::net::UnixStream) -> Option<PeerCredentials> {
594 sys::peer_credentials(stream.as_fd())
595 .ok()
596 .map(|(pid, uid, gid)| PeerCredentials {
597 pid,
598 uid: Some(uid),
599 gid: Some(gid),
600 })
601}
602
603#[cfg(unix)]
612pub struct UnixConnection {
613 inner: TcpConnection,
614}
615
616#[cfg(unix)]
617impl UnixConnection {
618 pub fn connect<P: AsRef<Path>>(path: P) -> Result<UnixConnection, NetError> {
625 let path = path.as_ref();
626 let bytes = path_bytes(path)?;
627 let shared = Arc::new(UnixPath::new(bytes).ok_or(NetError::PathTooLong {
628 max: sys::UNIX_PATH_MAX,
629 })?);
630 let sock = sys::unix_socket().map_err(|e| NetError::from_os("socket", e))?;
631 let stream = std::os::unix::net::UnixStream::from(sock);
632 stream
633 .set_write_timeout(Some(CONNECT_TIMEOUT))
634 .map_err(|e| NetError::from_os("setsockopt", e))?;
635 if let Err(e) = sys::unix_connect(stream.as_fd(), bytes) {
636 return Err(match e.kind() {
637 io::ErrorKind::NotFound | io::ErrorKind::ConnectionRefused => NetError::NotFound,
638 io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut => NetError::TimedOut {
639 kind: TimeoutKind::Connect,
640 },
641 _ => os_error("connect", e),
642 });
643 }
644 stream
645 .set_write_timeout(None)
646 .map_err(|e| NetError::from_os("setsockopt", e))?;
647 if sys::WAIT_BEFORE_RECV {
648 stream
649 .set_nonblocking(true)
650 .map_err(|e| NetError::from_os("ioctl", e))?;
651 }
652 let info = ConnectionInfo::new_unix(
653 crate::id::issue_single(),
654 None,
655 Origin::Dialed,
656 Endpoint::Unnamed,
657 Endpoint::Unix(shared),
658 credentials_of(&stream),
659 Instant::now(),
660 );
661 Ok(UnixConnection::from_stream(stream, info, Phase::Receiving))
662 }
663
664 fn from_stream(
665 stream: std::os::unix::net::UnixStream,
666 info: ConnectionInfo,
667 phase: Phase,
668 ) -> UnixConnection {
669 let limits = Limits::default().effective(DriverKind::Blocking);
670 UnixConnection {
671 inner: TcpConnection::from_link(Link::new(
672 sys::Stream::Unix(stream),
673 info,
674 limits,
675 ClockRef::Monotonic,
676 Arc::new(KillSlot::new()),
677 WakeSource::Thread,
678 phase,
679 )),
680 }
681 }
682
683 pub fn recv(&mut self, buf: &mut [u8]) -> Result<usize, NetError> {
689 self.inner.recv(buf)
690 }
691
692 pub fn recv_exact(&mut self, buf: &mut [u8]) -> Result<(), NetError> {
698 self.inner.recv_exact(buf)
699 }
700
701 pub fn send(&mut self, buf: &[u8]) -> Result<usize, NetError> {
707 self.inner.send(buf)
708 }
709
710 pub fn send_all(&mut self, buf: &[u8]) -> Result<(), NetError> {
716 self.inner.send_all(buf)
717 }
718
719 pub fn shutdown(&mut self, how: Shutdown) -> Result<(), NetError> {
725 self.inner.shutdown(how)
726 }
727
728 pub fn close(self) -> Result<(), NetError> {
734 self.inner.close()
735 }
736
737 pub fn abort(self) {
739 self.inner.abort();
740 }
741
742 fn close_in_place(&mut self) -> Result<(), NetError> {
743 match self.inner.take_link() {
744 Some(link) => TcpConnection::from_link(link).close(),
745 None => Ok(()),
746 }
747 }
748
749 fn abort_in_place(&mut self) {
750 if let Some(link) = self.inner.take_link() {
751 TcpConnection::from_link(link).abort();
752 }
753 }
754
755 #[must_use]
757 pub fn info(&self) -> &ConnectionInfo {
758 self.inner.info()
759 }
760
761 #[must_use]
763 pub fn id(&self) -> ConnectionId {
764 self.inner.id()
765 }
766
767 #[must_use]
769 pub fn kill_handle(&self) -> KillHandle {
770 self.inner.kill_handle()
771 }
772
773 pub fn set_limits(&mut self, limits: Limits) {
775 self.inner.set_limits(limits);
776 }
777
778 pub fn set_phase(&mut self, phase: Phase) {
780 self.inner.set_phase(phase);
781 }
782
783 pub fn set_exchange_deadline(&mut self, at: Option<Instant>) {
785 self.inner.set_exchange_deadline(at);
786 }
787
788 pub fn peer_credentials(&self) -> Result<PeerCredentials, NetError> {
794 self.info().peer_credentials.ok_or(NetError::Unsupported {
795 feature: "peer credentials",
796 platform: std::env::consts::OS,
797 })
798 }
799
800 pub fn send_descriptors(
808 &mut self,
809 body: &[u8],
810 descriptors: &[BorrowedFd<'_>],
811 ) -> Result<(), NetError> {
812 self.inner.link()?.write_with_fds(body, descriptors)
813 }
814
815 pub fn recv_descriptors(
825 &mut self,
826 body: &mut [u8],
827 into: &mut Descriptors,
828 ) -> Result<usize, NetError> {
829 into.clear();
830 let link = self.inner.link()?;
831 loop {
832 match link.read_with_fds(body, &mut into.slots)? {
833 (Got::Data(n), fds) => {
834 into.len = fds;
835 return Ok(n);
836 }
837 (Got::Eof, _) => return Ok(0),
838 (Got::Woken, _) => continue,
839 }
840 }
841 }
842}
843
844#[cfg(unix)]
845impl Read for UnixConnection {
846 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
847 self.inner.read(buf)
848 }
849}
850
851#[cfg(unix)]
852impl Write for UnixConnection {
853 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
854 self.inner.write(buf)
855 }
856
857 fn flush(&mut self) -> io::Result<()> {
858 Ok(())
859 }
860}
861
862#[cfg(unix)]
863impl AsFd for UnixConnection {
864 fn as_fd(&self) -> BorrowedFd<'_> {
865 AsFd::as_fd(&self.inner.link_ref().stream)
866 }
867}
868
869#[cfg(unix)]
870impl std::fmt::Debug for UnixConnection {
871 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
872 f.debug_struct("UnixConnection")
873 .field("info", self.inner.info())
874 .finish()
875 }
876}
877
878#[cfg(unix)]
881macro_rules! inside {
882 ($kind:ident, $this:expr, $v:ident => $body:expr) => {
883 match $this {
884 $kind::Unix($v) => $body,
885 }
886 };
887}
888
889#[cfg(not(unix))]
890macro_rules! inside {
891 ($kind:ident, $this:expr, $v:ident => $body:expr) => {
892 match *$this {}
893 };
894}
895
896#[non_exhaustive]
898#[derive(Debug)]
899pub enum LocalListener {
900 #[cfg(unix)]
902 Unix(UnixListener),
903}
904
905impl LocalListener {
906 pub fn accept(&self) -> Result<(LocalConnection, ConnectionInfo), NetError> {
912 inside!(LocalListener, self, l => l
913 .accept()
914 .map(|(c, info)| (LocalConnection::Unix(c), info)))
915 }
916}
917
918#[non_exhaustive]
920#[derive(Debug)]
921pub enum LocalConnection {
922 #[cfg(unix)]
924 Unix(UnixConnection),
925}
926
927#[cfg_attr(not(unix), allow(unused_variables))]
928impl LocalConnection {
929 pub fn recv(&mut self, buf: &mut [u8]) -> Result<usize, NetError> {
935 inside!(LocalConnection, self, c => c.recv(buf))
936 }
937
938 pub fn recv_exact(&mut self, buf: &mut [u8]) -> Result<(), NetError> {
944 inside!(LocalConnection, self, c => c.recv_exact(buf))
945 }
946
947 pub fn send(&mut self, buf: &[u8]) -> Result<usize, NetError> {
953 inside!(LocalConnection, self, c => c.send(buf))
954 }
955
956 pub fn send_all(&mut self, buf: &[u8]) -> Result<(), NetError> {
962 inside!(LocalConnection, self, c => c.send_all(buf))
963 }
964
965 pub fn shutdown(&mut self, how: Shutdown) -> Result<(), NetError> {
971 inside!(LocalConnection, self, c => c.shutdown(how))
972 }
973
974 pub fn close(mut self) -> Result<(), NetError> {
980 let this = &mut self;
981 inside!(LocalConnection, this, c => c.close_in_place())
982 }
983
984 pub fn abort(mut self) {
986 let this = &mut self;
987 inside!(LocalConnection, this, c => c.abort_in_place());
988 }
989
990 #[must_use]
992 pub fn info(&self) -> &ConnectionInfo {
993 inside!(LocalConnection, self, c => c.info())
994 }
995
996 #[must_use]
998 pub fn kill_handle(&self) -> KillHandle {
999 inside!(LocalConnection, self, c => c.kill_handle())
1000 }
1001
1002 pub fn set_limits(&mut self, limits: Limits) {
1004 inside!(LocalConnection, self, c => c.set_limits(limits));
1005 }
1006}
1007
1008impl Read for LocalConnection {
1009 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
1010 self.recv(buf).map_err(io::Error::from)
1011 }
1012}
1013
1014impl Write for LocalConnection {
1015 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
1016 self.send(buf).map_err(io::Error::from)
1017 }
1018
1019 fn flush(&mut self) -> io::Result<()> {
1020 Ok(())
1021 }
1022}
1023
1024#[cfg(not(unix))]
1026fn unsupported() -> NetError {
1027 NetError::Unsupported {
1028 feature: "local sockets (Windows named pipes arrive in 0.5.0)",
1029 platform: std::env::consts::OS,
1030 }
1031}
1032
1033pub(crate) fn listen(name: &str) -> Result<LocalListener, NetError> {
1035 #[cfg(unix)]
1036 {
1037 UnixListener::bind(name).map(LocalListener::Unix)
1038 }
1039 #[cfg(not(unix))]
1040 {
1041 let _ = name;
1042 Err(unsupported())
1043 }
1044}
1045
1046pub(crate) fn connect(name: &str) -> Result<LocalConnection, NetError> {
1048 #[cfg(unix)]
1049 {
1050 UnixConnection::connect(name).map(LocalConnection::Unix)
1051 }
1052 #[cfg(not(unix))]
1053 {
1054 let _ = name;
1055 Err(unsupported())
1056 }
1057}
1058
1059#[cfg(all(test, unix))]
1060mod tests {
1061 use super::*;
1062 use std::os::unix::fs::PermissionsExt;
1063
1064 fn dir(tag: &str) -> PathBuf {
1066 let d = std::env::temp_dir().join(format!("nio-{}-{tag}", std::process::id()));
1067 let _ = std::fs::remove_dir_all(&d);
1068 std::fs::create_dir(&d).unwrap();
1069 d
1070 }
1071
1072 #[test]
1073 fn test_bind_accept_echo_and_credentials() {
1074 let d = dir("echo");
1075 let path = d.join("s.sock");
1076 let l = UnixListener::bind(&path).unwrap();
1077 let mode = std::fs::metadata(&path).unwrap().permissions().mode() & 0o777;
1078 assert_eq!(mode, 0o600);
1079 assert_eq!(l.path(), Some(path.as_path()));
1080 let p2 = path.clone();
1081 let t = std::thread::spawn(move || {
1082 let mut c = UnixConnection::connect(&p2).unwrap();
1083 assert_eq!(c.info().transport, Transport::Unix);
1084 assert_eq!(c.info().origin, Origin::Dialed);
1085 c.send_all(b"ping").unwrap();
1086 let mut b = [0u8; 4];
1087 c.recv_exact(&mut b).unwrap();
1088 assert_eq!(&b, b"ping");
1089 c.peer_credentials().unwrap()
1090 });
1091 let (mut c, info) = l.accept().unwrap();
1092 assert_eq!(info.transport, Transport::Unix);
1093 assert_eq!(info.peer, Endpoint::Unnamed);
1094 let creds = info.peer_credentials.unwrap();
1095 assert_eq!(creds.pid, Some(std::process::id()));
1096 let mut b = [0u8; 4];
1097 c.recv_exact(&mut b).unwrap();
1098 c.send_all(&b).unwrap();
1099 let theirs = t.join().unwrap();
1100 assert_eq!(theirs.uid, creds.uid);
1101 assert_eq!(c.recv(&mut b).unwrap(), 0);
1102 drop(l);
1103 assert!(!path.exists(), "the listener removed its file");
1104 let _ = std::fs::remove_dir_all(&d);
1105 }
1106
1107 #[test]
1108 fn test_stale_socket_removed_live_one_refused() {
1109 let d = dir("stale");
1110 let path = d.join("s.sock");
1111 {
1113 let s = sys::unix_socket().unwrap();
1114 sys::unix_bind(s.as_fd(), path.as_os_str().as_encoded_bytes()).unwrap();
1115 }
1116 assert!(path.exists());
1117 let refused = UnixListener::bind_with(&path, LocalOptions::new().remove_stale(false));
1118 assert!(matches!(refused, Err(NetError::StaleSocketInUse { .. })));
1119 let l = UnixListener::bind(&path).unwrap();
1120 assert!(matches!(
1122 UnixListener::bind(&path),
1123 Err(NetError::StaleSocketInUse { .. })
1124 ));
1125 drop(l);
1126 std::fs::write(&path, b"data").unwrap();
1128 assert!(matches!(
1129 UnixListener::bind(&path),
1130 Err(NetError::StaleSocketInUse { .. })
1131 ));
1132 assert_eq!(std::fs::read(&path).unwrap(), b"data");
1133 let _ = std::fs::remove_dir_all(&d);
1134 }
1135
1136 #[test]
1139 fn test_longest_path_binds_and_non_sockets_stay() {
1140 let d = dir("max");
1141 let base = d.as_os_str().len() + 1;
1142 let name = "a".repeat(sys::UNIX_PATH_MAX - 1 - base);
1143 let longest = d.join(&name);
1144 assert_eq!(longest.as_os_str().len(), sys::UNIX_PATH_MAX - 1);
1145 let l = UnixListener::bind(&longest).unwrap();
1146 let _c = UnixConnection::connect(&longest).unwrap();
1147 drop(l);
1148 let too_long = d.join(format!("{name}a"));
1149 assert!(matches!(
1150 UnixListener::bind(&too_long),
1151 Err(NetError::PathTooLong { .. })
1152 ));
1153 let sub = d.join("sub");
1154 std::fs::create_dir(&sub).unwrap();
1155 assert!(matches!(
1156 UnixListener::bind(&sub),
1157 Err(NetError::StaleSocketInUse { .. })
1158 ));
1159 assert!(sub.is_dir());
1160 let target = d.join("t.sock");
1161 let live = UnixListener::bind(&target).unwrap();
1162 let link = d.join("link.sock");
1163 std::os::unix::fs::symlink(&target, &link).unwrap();
1164 assert!(matches!(
1165 UnixListener::bind(&link),
1166 Err(NetError::StaleSocketInUse { .. })
1167 ));
1168 assert!(
1169 std::fs::symlink_metadata(&link)
1170 .unwrap()
1171 .file_type()
1172 .is_symlink()
1173 );
1174 drop(live);
1175 let _ = std::fs::remove_dir_all(&d);
1176 }
1177
1178 #[test]
1180 fn test_unknown_credentials_fail_require_uid() {
1181 let d = dir("nocreds");
1182 let path = d.join("s.sock");
1183 let l = UnixListener::bind_with(&path, LocalOptions::new().require_uid(0)).unwrap();
1184 assert!(!l.permits(None));
1185 let no_uid = PeerCredentials {
1186 pid: Some(1),
1187 uid: None,
1188 gid: None,
1189 };
1190 assert!(!l.permits(Some(no_uid)));
1191 let root = PeerCredentials {
1192 pid: None,
1193 uid: Some(0),
1194 gid: None,
1195 };
1196 assert!(l.permits(Some(root)));
1197 let open = UnixListener::bind(d.join("o.sock")).unwrap();
1198 assert!(open.permits(None));
1199 let _ = std::fs::remove_dir_all(&d);
1200 }
1201
1202 #[test]
1203 fn test_drop_leaves_a_replacement_alone() {
1204 let d = dir("replace");
1205 let path = d.join("s.sock");
1206 let l = UnixListener::bind(&path).unwrap();
1207 std::fs::remove_file(&path).unwrap();
1208 let other = UnixListener::bind(&path).unwrap();
1209 drop(l);
1210 assert!(
1211 path.exists(),
1212 "the first listener did not remove the second's file"
1213 );
1214 drop(other);
1215 assert!(!path.exists());
1216 let _ = std::fs::remove_dir_all(&d);
1217 }
1218
1219 #[test]
1220 fn test_bind_atomic_sets_mode_and_leaves_no_temporary() {
1221 let d = dir("atomic");
1222 let path = d.join("s.sock");
1223 let l = UnixListener::bind_atomic(&path, LocalOptions::new().mode(0o640)).unwrap();
1224 let mode = std::fs::metadata(&path).unwrap().permissions().mode() & 0o777;
1225 assert_eq!(mode, 0o640);
1226 let names: Vec<_> = std::fs::read_dir(&d)
1227 .unwrap()
1228 .map(|e| e.unwrap().file_name())
1229 .collect();
1230 assert_eq!(names.len(), 1, "{names:?}");
1231 let c = UnixConnection::connect(&path).unwrap();
1232 let (_s, _) = l.accept().unwrap();
1233 drop(c);
1234 let _ = std::fs::remove_dir_all(&d);
1235 }
1236
1237 #[test]
1238 fn test_path_checks() {
1239 let long = format!("/tmp/{}", "a".repeat(sys::UNIX_PATH_MAX));
1240 assert!(matches!(
1241 UnixListener::bind(&long),
1242 Err(NetError::PathTooLong { max }) if max == sys::UNIX_PATH_MAX
1243 ));
1244 assert!(matches!(
1245 UnixConnection::connect(&long),
1246 Err(NetError::PathTooLong { .. })
1247 ));
1248 let nul = <std::ffi::OsStr as std::os::unix::ffi::OsStrExt>::from_bytes(b"/tmp/a\0b");
1249 assert!(matches!(
1250 UnixListener::bind(nul),
1251 Err(NetError::InvalidConfig {
1252 field: "path",
1253 reason: ConfigReason::Malformed
1254 })
1255 ));
1256 let d = dir("missing");
1257 assert!(matches!(
1258 UnixConnection::connect(d.join("none.sock")),
1259 Err(NetError::NotFound)
1260 ));
1261 let _ = std::fs::remove_dir_all(&d);
1262 }
1263
1264 #[test]
1265 fn test_require_uid_closes_other_users() {
1266 let d = dir("uid");
1267 let path = d.join("s.sock");
1268 let me = std::os::unix::fs::MetadataExt::uid(&std::fs::metadata(&d).unwrap());
1269 let l = UnixListener::bind_with(&path, LocalOptions::new().require_uid(me.wrapping_add(1)))
1270 .unwrap();
1271 let mut c = UnixConnection::connect(&path).unwrap();
1272 assert!(
1273 l.accept_timeout(Duration::from_millis(200))
1274 .unwrap()
1275 .is_none()
1276 );
1277 c.set_limits(
1279 Limits::builder()
1280 .read_timeout(Duration::from_secs(2))
1281 .build()
1282 .unwrap(),
1283 );
1284 let mut b = [0u8; 1];
1285 assert!(matches!(
1286 c.recv(&mut b),
1287 Ok(0) | Err(NetError::ConnectionReset)
1288 ));
1289 drop(l);
1290 let l = UnixListener::bind_with(&path, LocalOptions::new().require_uid(me)).unwrap();
1291 let _c = UnixConnection::connect(&path).unwrap();
1292 assert!(l.accept_timeout(Duration::from_secs(2)).unwrap().is_some());
1293 let _ = std::fs::remove_dir_all(&d);
1294 }
1295
1296 #[test]
1297 fn test_descriptors_travel_with_bytes() {
1298 let d = dir("fds");
1299 let path = d.join("s.sock");
1300 let l = UnixListener::bind(&path).unwrap();
1301 let p2 = path.clone();
1302 let t = std::thread::spawn(move || {
1303 let mut c = UnixConnection::connect(&p2).unwrap();
1304 let (mut mine, theirs) = std::os::unix::net::UnixStream::pair().unwrap();
1305 c.send_descriptors(b"take this", &[theirs.as_fd()]).unwrap();
1306 drop(theirs);
1307 mine.write_all(b"hello").unwrap();
1308 let mut b = [0u8; 1];
1309 let _ = c.recv(&mut b);
1310 });
1311 let (mut c, _) = l.accept().unwrap();
1312 let mut fds = Descriptors::new();
1313 let mut body = [0u8; 32];
1314 let n = c.recv_descriptors(&mut body, &mut fds).unwrap();
1316 assert_eq!(&body[..n], b"take this");
1317 assert_eq!(fds.len(), 1);
1318 let fd = fds.take().next().unwrap();
1319 assert!(fds.is_empty());
1320 let mut s = std::os::unix::net::UnixStream::from(fd);
1321 let mut b = [0u8; 5];
1322 s.read_exact(&mut b).unwrap();
1323 assert_eq!(&b, b"hello");
1324 assert!(matches!(
1325 c.send_descriptors(b"", &[]),
1326 Err(NetError::InvalidConfig { field: "body", .. })
1327 ));
1328 c.close().unwrap();
1329 t.join().unwrap();
1330 let _ = std::fs::remove_dir_all(&d);
1331 }
1332
1333 #[test]
1334 fn test_local_enums_round_trip() {
1335 let d = dir("enums");
1336 let path = d.join("s.sock");
1337 let name = path.to_str().unwrap().to_owned();
1338 let l = listen(&name).unwrap();
1339 let n2 = name.clone();
1340 let t = std::thread::spawn(move || {
1341 let mut c = connect(&n2).unwrap();
1342 c.send_all(b"hi").unwrap();
1343 c.close().unwrap();
1344 });
1345 let (mut c, info) = l.accept().unwrap();
1346 assert_eq!(info.transport, Transport::Unix);
1347 let mut b = [0u8; 2];
1348 c.recv_exact(&mut b).unwrap();
1349 assert_eq!(&b, b"hi");
1350 t.join().unwrap();
1351 let _ = std::fs::remove_dir_all(&d);
1352 }
1353}