1#![cfg_attr(not(all(unix, feature = "local")), allow(dead_code))]
19
20use std::fmt;
21use std::io;
22#[cfg(unix)]
23use std::sync::Arc;
24
25#[cfg(unix)]
26use crate::info::UnixPath;
27use crate::info::{Endpoint, Transport};
28use crate::sys::{self, KindError, SockKind};
29
30#[non_exhaustive]
36#[derive(Debug)]
37pub enum HandoffError {
38 PeerMismatch,
40 Truncated,
42 CountMismatch,
44 BadMagic,
46 BadVersion,
48 BadChecksum,
50 TooMany {
52 max: u8,
54 },
55 VerifyFailed {
57 index: u8,
59 why: &'static str,
61 },
62 Timeout,
64 Os(io::Error),
66}
67
68impl fmt::Display for HandoffError {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 match self {
71 HandoffError::PeerMismatch => {
72 f.write_str("the hand-off sender is not the expected process or user")
73 }
74 HandoffError::Truncated => f.write_str("the hand-off message was truncated"),
75 HandoffError::CountMismatch => {
76 f.write_str("the descriptor count does not match the manifest")
77 }
78 HandoffError::BadMagic => f.write_str("not a hand-off manifest"),
79 HandoffError::BadVersion => f.write_str("unknown hand-off manifest version"),
80 HandoffError::BadChecksum => {
81 f.write_str("the hand-off manifest checksum does not match")
82 }
83 HandoffError::TooMany { max } => write!(f, "more than {max} listeners in one hand-off"),
84 HandoffError::VerifyFailed { index, why } => {
85 write!(f, "received listener {index} failed: {why}")
86 }
87 HandoffError::Timeout => f.write_str("the hand-off did not finish in time"),
88 HandoffError::Os(e) => write!(f, "{e}"),
89 }
90 }
91}
92
93impl std::error::Error for HandoffError {
94 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
95 match self {
96 HandoffError::Os(e) => Some(e),
97 _ => None,
98 }
99 }
100}
101
102pub struct ListenerHandle {
107 kind: Transport,
108 name: Option<String>,
109 local: Endpoint,
110 sock: sys::OwnedSock,
111}
112
113impl ListenerHandle {
114 pub(crate) fn verified(
117 sock: sys::OwnedSock,
118 name: Option<String>,
119 ) -> Result<ListenerHandle, KindError> {
120 let kind = sys::listener_kind(sys::AsSock::sock(&sock))?;
121 let (kind, local) = endpoint_of(kind)?;
122 Ok(ListenerHandle {
123 kind,
124 name,
125 local,
126 sock,
127 })
128 }
129
130 pub(crate) fn new(
131 kind: Transport,
132 name: Option<String>,
133 local: Endpoint,
134 sock: sys::OwnedSock,
135 ) -> ListenerHandle {
136 ListenerHandle {
137 kind,
138 name,
139 local,
140 sock,
141 }
142 }
143
144 pub(crate) fn into_sock(self) -> sys::OwnedSock {
145 self.sock
146 }
147
148 #[must_use]
150 pub fn kind(&self) -> Transport {
151 self.kind
152 }
153
154 #[must_use]
156 pub fn name(&self) -> Option<&str> {
157 self.name.as_deref()
158 }
159
160 #[must_use]
162 pub fn local(&self) -> &Endpoint {
163 &self.local
164 }
165}
166
167fn endpoint_of(kind: SockKind) -> Result<(Transport, Endpoint), KindError> {
169 match kind {
170 SockKind::Tcp(a) => Ok((Transport::Tcp, Endpoint::Ip(a))),
171 #[cfg(unix)]
172 SockKind::Unix(path) => {
173 if !cfg!(feature = "local") {
174 return Err(KindError::WrongKind);
175 }
176 let local = UnixPath::new(&path)
177 .filter(|_| !path.is_empty())
178 .map_or(Endpoint::Unnamed, |p| Endpoint::Unix(Arc::new(p)));
179 Ok((Transport::Unix, local))
180 }
181 }
182}
183
184impl fmt::Debug for ListenerHandle {
185 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186 f.debug_struct("ListenerHandle")
187 .field("kind", &self.kind)
188 .field("name", &self.name)
189 .field("local", &self.local)
190 .finish()
191 }
192}
193
194#[cfg(unix)]
195impl std::os::fd::AsFd for ListenerHandle {
196 fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> {
197 self.sock.as_fd()
198 }
199}
200
201#[cfg(windows)]
202impl std::os::windows::io::AsSocket for ListenerHandle {
203 fn as_socket(&self) -> std::os::windows::io::BorrowedSocket<'_> {
204 self.sock.as_socket()
205 }
206}
207
208#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
216pub struct HandoffPeer {
217 pub uid: Option<u32>,
219 pub pid: Option<u32>,
221}
222
223const MAGIC: [u8; 4] = *b"NIOH";
224const VERSION: u8 = 1;
225const HEADER: usize = 8;
226const MAX_LISTENERS: usize = 64;
228const MAX_NAME: usize = 64;
230
231#[derive(Clone, Debug, PartialEq, Eq)]
233struct Record {
234 kind: SockKind,
235 name: Option<String>,
236}
237
238fn encode(records: &[Record]) -> Vec<u8> {
240 let mut out = Vec::with_capacity(HEADER + records.len() * 96 + 4);
241 out.extend_from_slice(&MAGIC);
242 out.push(VERSION);
243 out.push(records.len() as u8);
244 out.extend_from_slice(&[0, 0]); for r in records {
246 let mut name = r.name.as_deref().unwrap_or("");
247 if name.len() > MAX_NAME {
248 let mut cut = MAX_NAME;
249 while !name.is_char_boundary(cut) {
250 cut -= 1;
251 }
252 name = &name[..cut];
253 }
254 let mut addr = Vec::with_capacity(32);
255 let kind = match &r.kind {
256 SockKind::Tcp(a) => {
257 match a.ip() {
258 std::net::IpAddr::V4(ip) => {
259 addr.push(4);
260 addr.extend_from_slice(&ip.octets());
261 }
262 std::net::IpAddr::V6(ip) => {
263 addr.push(6);
264 addr.extend_from_slice(&ip.octets());
265 }
266 }
267 addr.extend_from_slice(&a.port().to_be_bytes());
268 1u8
269 }
270 #[cfg(unix)]
271 SockKind::Unix(path) => {
272 addr.extend_from_slice(path);
273 2u8
274 }
275 };
276 out.push(kind);
277 out.push(name.len() as u8);
278 out.extend_from_slice(name.as_bytes());
279 out.extend_from_slice(&(addr.len() as u16).to_be_bytes());
280 out.extend_from_slice(&addr);
281 }
282 let total = (out.len() + 4) as u16;
283 out[6..8].copy_from_slice(&total.to_be_bytes());
284 let mut crc = crate::crc32c::Crc32c::new();
285 crc.update(&out);
286 out.extend_from_slice(&crc.finish().to_be_bytes());
287 out
288}
289
290fn parse_header(h: &[u8; HEADER]) -> Result<(usize, usize), HandoffError> {
292 if h[..4] != MAGIC {
293 return Err(HandoffError::BadMagic);
294 }
295 if h[4] != VERSION {
296 return Err(HandoffError::BadVersion);
297 }
298 let count = usize::from(h[5]);
299 if count > MAX_LISTENERS {
300 return Err(HandoffError::TooMany {
301 max: MAX_LISTENERS as u8,
302 });
303 }
304 let total = usize::from(u16::from_be_bytes([h[6], h[7]]));
305 if total < HEADER + 4 {
306 return Err(HandoffError::Truncated);
307 }
308 Ok((count, total))
309}
310
311fn decode(bytes: &[u8]) -> Result<Vec<Record>, HandoffError> {
313 let header: &[u8; HEADER] = bytes
314 .get(..HEADER)
315 .and_then(|h| h.try_into().ok())
316 .ok_or(HandoffError::Truncated)?;
317 let (count, total) = parse_header(header)?;
318 if bytes.len() != total {
319 return Err(HandoffError::Truncated);
320 }
321 let (body, sum) = bytes.split_at(total - 4);
322 let mut crc = crate::crc32c::Crc32c::new();
323 crc.update(body);
324 if crc.finish().to_be_bytes() != sum {
325 return Err(HandoffError::BadChecksum);
326 }
327 let mut at = HEADER;
328 let mut take = |n: usize| -> Result<&[u8], HandoffError> {
329 let s = body.get(at..at + n).ok_or(HandoffError::Truncated)?;
330 at += n;
331 Ok(s)
332 };
333 let mut records = Vec::with_capacity(count);
334 for i in 0..count {
335 let bad = |why| HandoffError::VerifyFailed {
336 index: i as u8,
337 why,
338 };
339 let kind = take(1)?[0];
340 let name_len = usize::from(take(1)?[0]);
341 if name_len > MAX_NAME {
342 return Err(bad("name longer than 64 bytes"));
343 }
344 let name = std::str::from_utf8(take(name_len)?)
345 .map_err(|_| bad("name is not UTF-8"))?
346 .to_owned();
347 let addr_len = take(2)?;
348 let addr_len = usize::from(u16::from_be_bytes([addr_len[0], addr_len[1]]));
349 let addr = take(addr_len)?;
350 let kind = match (kind, addr) {
351 (1, [4, a, b, c, d, p0, p1]) => SockKind::Tcp(std::net::SocketAddr::from((
352 [*a, *b, *c, *d],
353 u16::from_be_bytes([*p0, *p1]),
354 ))),
355 (1, [6, rest @ ..]) if rest.len() == 18 => {
356 let mut ip = [0u8; 16];
357 ip.copy_from_slice(&rest[..16]);
358 SockKind::Tcp(std::net::SocketAddr::from((
359 ip,
360 u16::from_be_bytes([rest[16], rest[17]]),
361 )))
362 }
363 #[cfg(unix)]
364 (2, path) => SockKind::Unix(path.to_vec()),
365 _ => return Err(bad("unknown kind or malformed address")),
366 };
367 records.push(Record {
368 kind,
369 name: (!name.is_empty()).then_some(name),
370 });
371 }
372 if at != body.len() {
373 return Err(HandoffError::Truncated);
374 }
375 Ok(records)
376}
377
378#[cfg(fuzzing)]
381pub(crate) fn fuzz_manifest(data: &[u8]) {
382 if let Ok(records) = decode(data) {
383 assert_eq!(encode(&records), data);
384 }
385}
386
387#[cfg(all(unix, feature = "local"))]
395pub fn send_listeners(
396 channel: &mut crate::local::UnixConnection,
397 listeners: &[&ListenerHandle],
398) -> Result<(), HandoffError> {
399 use std::os::fd::AsFd;
400 if listeners.len() > MAX_LISTENERS {
401 return Err(HandoffError::TooMany {
402 max: MAX_LISTENERS as u8,
403 });
404 }
405 let mut records = Vec::with_capacity(listeners.len());
406 for (i, l) in listeners.iter().enumerate() {
407 let kind = sys::listener_kind(l.as_fd()).map_err(|e| HandoffError::VerifyFailed {
408 index: i as u8,
409 why: why(&e),
410 })?;
411 records.push(Record {
412 kind,
413 name: l.name.clone(),
414 });
415 }
416 let manifest = encode(&records);
417 let fds: Vec<_> = listeners.iter().map(|l| l.as_fd()).collect();
418 channel
419 .send_descriptors(&manifest, &fds)
420 .map_err(handoff_error)
421}
422
423#[cfg(all(unix, feature = "local"))]
435pub fn recv_listeners(
436 channel: &mut crate::local::UnixConnection,
437 expect: &HandoffPeer,
438 deadline: std::time::Instant,
439) -> Result<Vec<ListenerHandle>, HandoffError> {
440 use crate::local::Descriptors;
441 let creds = channel
442 .peer_credentials()
443 .map_err(|_| HandoffError::PeerMismatch)?;
444 let uid = expect.uid.unwrap_or_else(sys::effective_uid);
445 if creds.uid != Some(uid) || expect.pid.is_some_and(|p| creds.pid != Some(p)) {
446 return Err(HandoffError::PeerMismatch);
447 }
448 channel.set_phase(crate::session::Phase::Receiving);
449 channel.set_exchange_deadline(Some(deadline));
450 let result = receive(channel);
451 channel.set_exchange_deadline(None);
452 let (records, mut fds): (Vec<Record>, Descriptors) = result?;
453 if fds.len() != records.len() {
454 return Err(HandoffError::CountMismatch);
455 }
456 let mut handles = Vec::with_capacity(records.len());
457 for (i, (record, fd)) in records.into_iter().zip(fds.take()).enumerate() {
458 let index = i as u8;
459 let got = sys::listener_kind(std::os::fd::AsFd::as_fd(&fd)).map_err(|e| {
460 HandoffError::VerifyFailed {
461 index,
462 why: why(&e),
463 }
464 })?;
465 if got != record.kind {
466 return Err(HandoffError::VerifyFailed {
467 index,
468 why: "bound to another address than the manifest says",
469 });
470 }
471 let (kind, local) = endpoint_of(got).map_err(|e| HandoffError::VerifyFailed {
472 index,
473 why: why(&e),
474 })?;
475 handles.push(ListenerHandle::new(kind, record.name, local, fd));
476 }
477 Ok(handles)
478}
479
480#[cfg(all(unix, feature = "local"))]
482fn receive(
483 channel: &mut crate::local::UnixConnection,
484) -> Result<(Vec<Record>, crate::local::Descriptors), HandoffError> {
485 use crate::local::Descriptors;
486 let mut fds = Descriptors::new();
487 let mut header = [0u8; HEADER];
488 let mut got = 0;
489 while got < HEADER {
490 let mut extra = Descriptors::new();
492 let into = if got == 0 { &mut fds } else { &mut extra };
493 let n = channel
494 .recv_descriptors(&mut header[got..], into)
495 .map_err(handoff_error)?;
496 if n == 0 {
497 return Err(HandoffError::Truncated);
498 }
499 if !extra.is_empty() {
500 return Err(HandoffError::CountMismatch);
501 }
502 got += n;
503 }
504 let (_, total) = parse_header(&header)?;
505 let mut manifest = vec![0u8; total];
506 manifest[..HEADER].copy_from_slice(&header);
507 let mut at = HEADER;
508 while at < total {
509 let mut extra = Descriptors::new();
510 let n = channel
511 .recv_descriptors(&mut manifest[at..], &mut extra)
512 .map_err(handoff_error)?;
513 if n == 0 {
514 return Err(HandoffError::Truncated);
515 }
516 if !extra.is_empty() {
517 return Err(HandoffError::CountMismatch);
518 }
519 at += n;
520 }
521 let records = decode(&manifest)?;
522 Ok((records, fds))
523}
524
525#[cfg(all(unix, feature = "local"))]
526fn handoff_error(e: crate::error::NetError) -> HandoffError {
527 use crate::error::NetError;
528 match e {
529 NetError::TimedOut { .. } => HandoffError::Timeout,
530 NetError::Handoff(h) => h,
531 NetError::Closed => HandoffError::Truncated,
532 other => HandoffError::Os(io::Error::from(other)),
533 }
534}
535
536fn why(e: &KindError) -> &'static str {
537 match e {
538 KindError::NotASocket => "not a socket",
539 KindError::NotListening => "not listening",
540 KindError::WrongKind => "not a TCP or Unix stream listener",
541 KindError::Os(_) => "could not be inspected",
542 }
543}
544
545#[cfg(test)]
546mod tests {
547 use super::*;
548
549 fn records() -> Vec<Record> {
550 #[cfg_attr(not(unix), allow(unused_mut))]
551 let mut v = vec![
552 Record {
553 kind: SockKind::Tcp("127.0.0.1:8080".parse().unwrap()),
554 name: Some("web".into()),
555 },
556 Record {
557 kind: SockKind::Tcp("[2001:db8::1]:443".parse().unwrap()),
558 name: None,
559 },
560 ];
561 #[cfg(unix)]
562 v.push(Record {
563 kind: SockKind::Unix(b"/run/app/api.sock".to_vec()),
564 name: Some("api".into()),
565 });
566 v
567 }
568
569 #[test]
570 fn test_manifest_round_trips() {
571 let r = records();
572 assert_eq!(decode(&encode(&r)).unwrap(), r);
573 assert_eq!(decode(&encode(&[])).unwrap(), Vec::new());
574 }
575
576 #[test]
577 fn test_manifest_rejects_damage() {
578 let good = encode(&records());
579 let mut m = good.clone();
580 m[0] = b'X';
581 assert!(matches!(decode(&m), Err(HandoffError::BadMagic)));
582 let mut m = good.clone();
583 m[4] = 2;
584 assert!(matches!(decode(&m), Err(HandoffError::BadVersion)));
585 let mut m = good.clone();
586 let mid = m.len() / 2;
587 m[mid] ^= 1;
588 assert!(matches!(decode(&m), Err(HandoffError::BadChecksum)));
589 assert!(matches!(
590 decode(&good[..good.len() - 1]),
591 Err(HandoffError::Truncated)
592 ));
593 let mut m = good.clone();
594 m[5] = 65;
595 assert!(matches!(decode(&m), Err(HandoffError::TooMany { max: 64 })));
596 assert!(matches!(decode(&good[..3]), Err(HandoffError::Truncated)));
597 }
598
599 #[test]
600 fn test_long_names_are_cut_at_a_character_boundary() {
601 let r = [Record {
602 kind: SockKind::Tcp("127.0.0.1:1".parse().unwrap()),
603 name: Some("é".repeat(40)),
604 }];
605 let back = decode(&encode(&r)).unwrap();
606 let name = back[0].name.clone().unwrap();
607 assert_eq!(name.len(), 64);
608 assert!(name.chars().all(|c| c == 'é'));
609 }
610
611 #[cfg(all(unix, feature = "local"))]
613 fn channel(
614 tag: &str,
615 ) -> (
616 crate::local::UnixConnection,
617 crate::local::UnixConnection,
618 std::path::PathBuf,
619 ) {
620 let dir = std::env::temp_dir().join(format!("nio-ho-{}-{tag}", std::process::id()));
621 let _ = std::fs::remove_dir_all(&dir);
622 std::fs::create_dir(&dir).unwrap();
623 let l = crate::local::UnixListener::bind(dir.join("c.sock")).unwrap();
624 let tx = crate::local::UnixConnection::connect(dir.join("c.sock")).unwrap();
625 let (rx, _) = l.accept().unwrap();
626 (tx, rx, dir)
627 }
628
629 #[cfg(all(unix, feature = "local"))]
630 #[test]
631 fn test_count_mismatch_truncation_timeout_and_peer() {
632 use std::os::fd::AsFd;
633 use std::time::{Duration, Instant};
634 let soon = || Instant::now() + Duration::from_secs(5);
635 let l = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
636 let r = Record {
637 kind: SockKind::Tcp(l.local_addr().unwrap()),
638 name: None,
639 };
640 let (mut tx, mut rx, d1) = channel("count");
642 tx.send_descriptors(&encode(&[r.clone(), r.clone()]), &[l.as_fd()])
643 .unwrap();
644 assert!(matches!(
645 recv_listeners(&mut rx, &HandoffPeer::default(), soon()),
646 Err(HandoffError::CountMismatch)
647 ));
648 let (mut tx, mut rx, d2) = channel("dies");
650 let m = encode(std::slice::from_ref(&r));
651 tx.send_descriptors(&m[..m.len() / 2], &[l.as_fd()])
652 .unwrap();
653 drop(tx);
654 assert!(matches!(
655 recv_listeners(&mut rx, &HandoffPeer::default(), soon()),
656 Err(HandoffError::Truncated)
657 ));
658 let (_tx, mut rx, d3) = channel("timeout");
660 let started = Instant::now();
661 assert!(matches!(
662 recv_listeners(
663 &mut rx,
664 &HandoffPeer::default(),
665 Instant::now() + Duration::from_millis(200)
666 ),
667 Err(HandoffError::Timeout)
668 ));
669 assert!(started.elapsed() < Duration::from_secs(3));
670 let (_tx, mut rx, d4) = channel("peer");
672 let other = HandoffPeer {
673 uid: Some(sys::effective_uid().wrapping_add(1)),
674 pid: None,
675 };
676 assert!(matches!(
677 recv_listeners(&mut rx, &other, soon()),
678 Err(HandoffError::PeerMismatch)
679 ));
680 let wrong_pid = HandoffPeer {
681 uid: None,
682 pid: Some(1),
683 };
684 assert!(matches!(
685 recv_listeners(&mut rx, &wrong_pid, soon()),
686 Err(HandoffError::PeerMismatch)
687 ));
688 for d in [d1, d2, d3, d4] {
689 let _ = std::fs::remove_dir_all(d);
690 }
691 }
692
693 #[cfg(all(unix, feature = "local"))]
695 #[test]
696 fn test_address_must_match_the_manifest() {
697 use std::os::fd::AsFd;
698 use std::time::{Duration, Instant};
699 let a = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
700 let b = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
701 let (mut tx, mut rx, dir) = channel("addr");
702 let lie = Record {
703 kind: SockKind::Tcp(b.local_addr().unwrap()),
704 name: None,
705 };
706 tx.send_descriptors(&encode(&[lie]), &[a.as_fd()]).unwrap();
707 assert!(matches!(
708 recv_listeners(
709 &mut rx,
710 &HandoffPeer::default(),
711 Instant::now() + Duration::from_secs(5)
712 ),
713 Err(HandoffError::VerifyFailed { index: 0, .. })
714 ));
715 let _ = std::fs::remove_dir_all(dir);
716 }
717
718 #[test]
719 fn test_handle_verifies_its_socket() {
720 let l = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
721 let addr = l.local_addr().unwrap();
722 let h = ListenerHandle::verified(sys::OwnedSock::from(l), Some("x".into())).unwrap();
723 assert_eq!(h.kind(), Transport::Tcp);
724 assert_eq!(h.local().socket_addr(), Some(addr));
725 assert_eq!(h.name(), Some("x"));
726 let u = std::net::UdpSocket::bind("127.0.0.1:0").unwrap();
727 assert!(matches!(
728 ListenerHandle::verified(sys::OwnedSock::from(u), None),
729 Err(KindError::WrongKind)
730 ));
731 }
732}