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