]> git.lizzy.rs Git - rust.git/blob - library/std/src/sys/unix/net.rs
Rollup merge of #81797 - yoshuawuyts:stream_from_iter, r=dtolnay
[rust.git] / library / std / src / sys / unix / net.rs
1 use crate::cmp;
2 use crate::ffi::CStr;
3 use crate::io::{self, IoSlice, IoSliceMut};
4 use crate::mem;
5 use crate::net::{Shutdown, SocketAddr};
6 use crate::str;
7 use crate::sys::fd::FileDesc;
8 use crate::sys_common::net::{getsockopt, setsockopt, sockaddr_to_addr};
9 use crate::sys_common::{AsInner, FromInner, IntoInner};
10 use crate::time::{Duration, Instant};
11
12 use libc::{c_int, c_void, size_t, sockaddr, socklen_t, EAI_SYSTEM, MSG_PEEK};
13
14 pub use crate::sys::{cvt, cvt_r};
15
16 #[allow(unused_extern_crates)]
17 pub extern crate libc as netc;
18
19 pub type wrlen_t = size_t;
20
21 pub struct Socket(FileDesc);
22
23 pub fn init() {}
24
25 pub fn cvt_gai(err: c_int) -> io::Result<()> {
26     if err == 0 {
27         return Ok(());
28     }
29
30     // We may need to trigger a glibc workaround. See on_resolver_failure() for details.
31     on_resolver_failure();
32
33     if err == EAI_SYSTEM {
34         return Err(io::Error::last_os_error());
35     }
36
37     let detail = unsafe {
38         str::from_utf8(CStr::from_ptr(libc::gai_strerror(err)).to_bytes()).unwrap().to_owned()
39     };
40     Err(io::Error::new(
41         io::ErrorKind::Uncategorized,
42         &format!("failed to lookup address information: {}", detail)[..],
43     ))
44 }
45
46 impl Socket {
47     pub fn new(addr: &SocketAddr, ty: c_int) -> io::Result<Socket> {
48         let fam = match *addr {
49             SocketAddr::V4(..) => libc::AF_INET,
50             SocketAddr::V6(..) => libc::AF_INET6,
51         };
52         Socket::new_raw(fam, ty)
53     }
54
55     pub fn new_raw(fam: c_int, ty: c_int) -> io::Result<Socket> {
56         unsafe {
57             cfg_if::cfg_if! {
58                 if #[cfg(any(
59                     target_os = "android",
60                     target_os = "dragonfly",
61                     target_os = "freebsd",
62                     target_os = "illumos",
63                     target_os = "linux",
64                     target_os = "netbsd",
65                     target_os = "openbsd",
66                 ))] {
67                     // On platforms that support it we pass the SOCK_CLOEXEC
68                     // flag to atomically create the socket and set it as
69                     // CLOEXEC. On Linux this was added in 2.6.27.
70                     let fd = cvt(libc::socket(fam, ty | libc::SOCK_CLOEXEC, 0))?;
71                     Ok(Socket(FileDesc::new(fd)))
72                 } else {
73                     let fd = cvt(libc::socket(fam, ty, 0))?;
74                     let fd = FileDesc::new(fd);
75                     fd.set_cloexec()?;
76                     let socket = Socket(fd);
77
78                     // macOS and iOS use `SO_NOSIGPIPE` as a `setsockopt`
79                     // flag to disable `SIGPIPE` emission on socket.
80                     #[cfg(target_vendor = "apple")]
81                     setsockopt(&socket, libc::SOL_SOCKET, libc::SO_NOSIGPIPE, 1)?;
82
83                     Ok(socket)
84                 }
85             }
86         }
87     }
88
89     #[cfg(not(target_os = "vxworks"))]
90     pub fn new_pair(fam: c_int, ty: c_int) -> io::Result<(Socket, Socket)> {
91         unsafe {
92             let mut fds = [0, 0];
93
94             cfg_if::cfg_if! {
95                 if #[cfg(any(
96                     target_os = "android",
97                     target_os = "dragonfly",
98                     target_os = "freebsd",
99                     target_os = "illumos",
100                     target_os = "linux",
101                     target_os = "netbsd",
102                     target_os = "openbsd",
103                 ))] {
104                     // Like above, set cloexec atomically
105                     cvt(libc::socketpair(fam, ty | libc::SOCK_CLOEXEC, 0, fds.as_mut_ptr()))?;
106                     Ok((Socket(FileDesc::new(fds[0])), Socket(FileDesc::new(fds[1]))))
107                 } else {
108                     cvt(libc::socketpair(fam, ty, 0, fds.as_mut_ptr()))?;
109                     let a = FileDesc::new(fds[0]);
110                     let b = FileDesc::new(fds[1]);
111                     a.set_cloexec()?;
112                     b.set_cloexec()?;
113                     Ok((Socket(a), Socket(b)))
114                 }
115             }
116         }
117     }
118
119     #[cfg(target_os = "vxworks")]
120     pub fn new_pair(_fam: c_int, _ty: c_int) -> io::Result<(Socket, Socket)> {
121         unimplemented!()
122     }
123
124     pub fn connect_timeout(&self, addr: &SocketAddr, timeout: Duration) -> io::Result<()> {
125         self.set_nonblocking(true)?;
126         let r = unsafe {
127             let (addrp, len) = addr.into_inner();
128             cvt(libc::connect(self.0.raw(), addrp, len))
129         };
130         self.set_nonblocking(false)?;
131
132         match r {
133             Ok(_) => return Ok(()),
134             // there's no ErrorKind for EINPROGRESS :(
135             Err(ref e) if e.raw_os_error() == Some(libc::EINPROGRESS) => {}
136             Err(e) => return Err(e),
137         }
138
139         let mut pollfd = libc::pollfd { fd: self.0.raw(), events: libc::POLLOUT, revents: 0 };
140
141         if timeout.as_secs() == 0 && timeout.subsec_nanos() == 0 {
142             return Err(io::Error::new_const(
143                 io::ErrorKind::InvalidInput,
144                 &"cannot set a 0 duration timeout",
145             ));
146         }
147
148         let start = Instant::now();
149
150         loop {
151             let elapsed = start.elapsed();
152             if elapsed >= timeout {
153                 return Err(io::Error::new_const(io::ErrorKind::TimedOut, &"connection timed out"));
154             }
155
156             let timeout = timeout - elapsed;
157             let mut timeout = timeout
158                 .as_secs()
159                 .saturating_mul(1_000)
160                 .saturating_add(timeout.subsec_nanos() as u64 / 1_000_000);
161             if timeout == 0 {
162                 timeout = 1;
163             }
164
165             let timeout = cmp::min(timeout, c_int::MAX as u64) as c_int;
166
167             match unsafe { libc::poll(&mut pollfd, 1, timeout) } {
168                 -1 => {
169                     let err = io::Error::last_os_error();
170                     if err.kind() != io::ErrorKind::Interrupted {
171                         return Err(err);
172                     }
173                 }
174                 0 => {}
175                 _ => {
176                     // linux returns POLLOUT|POLLERR|POLLHUP for refused connections (!), so look
177                     // for POLLHUP rather than read readiness
178                     if pollfd.revents & libc::POLLHUP != 0 {
179                         let e = self.take_error()?.unwrap_or_else(|| {
180                             io::Error::new_const(
181                                 io::ErrorKind::Uncategorized,
182                                 &"no error set after POLLHUP",
183                             )
184                         });
185                         return Err(e);
186                     }
187
188                     return Ok(());
189                 }
190             }
191         }
192     }
193
194     pub fn accept(&self, storage: *mut sockaddr, len: *mut socklen_t) -> io::Result<Socket> {
195         // Unfortunately the only known way right now to accept a socket and
196         // atomically set the CLOEXEC flag is to use the `accept4` syscall on
197         // platforms that support it. On Linux, this was added in 2.6.28,
198         // glibc 2.10 and musl 0.9.5.
199         cfg_if::cfg_if! {
200             if #[cfg(any(
201                 target_os = "android",
202                 target_os = "dragonfly",
203                 target_os = "freebsd",
204                 target_os = "illumos",
205                 target_os = "linux",
206                 target_os = "netbsd",
207                 target_os = "openbsd",
208             ))] {
209                 let fd = cvt_r(|| unsafe {
210                     libc::accept4(self.0.raw(), storage, len, libc::SOCK_CLOEXEC)
211                 })?;
212                 Ok(Socket(FileDesc::new(fd)))
213             } else {
214                 let fd = cvt_r(|| unsafe { libc::accept(self.0.raw(), storage, len) })?;
215                 let fd = FileDesc::new(fd);
216                 fd.set_cloexec()?;
217                 Ok(Socket(fd))
218             }
219         }
220     }
221
222     pub fn duplicate(&self) -> io::Result<Socket> {
223         self.0.duplicate().map(Socket)
224     }
225
226     fn recv_with_flags(&self, buf: &mut [u8], flags: c_int) -> io::Result<usize> {
227         let ret = cvt(unsafe {
228             libc::recv(self.0.raw(), buf.as_mut_ptr() as *mut c_void, buf.len(), flags)
229         })?;
230         Ok(ret as usize)
231     }
232
233     pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
234         self.recv_with_flags(buf, 0)
235     }
236
237     pub fn peek(&self, buf: &mut [u8]) -> io::Result<usize> {
238         self.recv_with_flags(buf, MSG_PEEK)
239     }
240
241     pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
242         self.0.read_vectored(bufs)
243     }
244
245     #[inline]
246     pub fn is_read_vectored(&self) -> bool {
247         self.0.is_read_vectored()
248     }
249
250     fn recv_from_with_flags(
251         &self,
252         buf: &mut [u8],
253         flags: c_int,
254     ) -> io::Result<(usize, SocketAddr)> {
255         let mut storage: libc::sockaddr_storage = unsafe { mem::zeroed() };
256         let mut addrlen = mem::size_of_val(&storage) as libc::socklen_t;
257
258         let n = cvt(unsafe {
259             libc::recvfrom(
260                 self.0.raw(),
261                 buf.as_mut_ptr() as *mut c_void,
262                 buf.len(),
263                 flags,
264                 &mut storage as *mut _ as *mut _,
265                 &mut addrlen,
266             )
267         })?;
268         Ok((n as usize, sockaddr_to_addr(&storage, addrlen as usize)?))
269     }
270
271     pub fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
272         self.recv_from_with_flags(buf, 0)
273     }
274
275     #[cfg(any(
276         target_os = "android",
277         target_os = "dragonfly",
278         target_os = "emscripten",
279         target_os = "freebsd",
280         target_os = "linux",
281         target_os = "netbsd",
282         target_os = "openbsd",
283     ))]
284     pub fn recv_msg(&self, msg: &mut libc::msghdr) -> io::Result<usize> {
285         let n = cvt(unsafe { libc::recvmsg(self.0.raw(), msg, libc::MSG_CMSG_CLOEXEC) })?;
286         Ok(n as usize)
287     }
288
289     pub fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
290         self.recv_from_with_flags(buf, MSG_PEEK)
291     }
292
293     pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
294         self.0.write(buf)
295     }
296
297     pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
298         self.0.write_vectored(bufs)
299     }
300
301     #[inline]
302     pub fn is_write_vectored(&self) -> bool {
303         self.0.is_write_vectored()
304     }
305
306     #[cfg(any(
307         target_os = "android",
308         target_os = "dragonfly",
309         target_os = "emscripten",
310         target_os = "freebsd",
311         target_os = "linux",
312         target_os = "netbsd",
313         target_os = "openbsd",
314     ))]
315     pub fn send_msg(&self, msg: &mut libc::msghdr) -> io::Result<usize> {
316         let n = cvt(unsafe { libc::sendmsg(self.0.raw(), msg, 0) })?;
317         Ok(n as usize)
318     }
319
320     pub fn set_timeout(&self, dur: Option<Duration>, kind: libc::c_int) -> io::Result<()> {
321         let timeout = match dur {
322             Some(dur) => {
323                 if dur.as_secs() == 0 && dur.subsec_nanos() == 0 {
324                     return Err(io::Error::new_const(
325                         io::ErrorKind::InvalidInput,
326                         &"cannot set a 0 duration timeout",
327                     ));
328                 }
329
330                 let secs = if dur.as_secs() > libc::time_t::MAX as u64 {
331                     libc::time_t::MAX
332                 } else {
333                     dur.as_secs() as libc::time_t
334                 };
335                 let mut timeout = libc::timeval {
336                     tv_sec: secs,
337                     tv_usec: dur.subsec_micros() as libc::suseconds_t,
338                 };
339                 if timeout.tv_sec == 0 && timeout.tv_usec == 0 {
340                     timeout.tv_usec = 1;
341                 }
342                 timeout
343             }
344             None => libc::timeval { tv_sec: 0, tv_usec: 0 },
345         };
346         setsockopt(self, libc::SOL_SOCKET, kind, timeout)
347     }
348
349     pub fn timeout(&self, kind: libc::c_int) -> io::Result<Option<Duration>> {
350         let raw: libc::timeval = getsockopt(self, libc::SOL_SOCKET, kind)?;
351         if raw.tv_sec == 0 && raw.tv_usec == 0 {
352             Ok(None)
353         } else {
354             let sec = raw.tv_sec as u64;
355             let nsec = (raw.tv_usec as u32) * 1000;
356             Ok(Some(Duration::new(sec, nsec)))
357         }
358     }
359
360     pub fn shutdown(&self, how: Shutdown) -> io::Result<()> {
361         let how = match how {
362             Shutdown::Write => libc::SHUT_WR,
363             Shutdown::Read => libc::SHUT_RD,
364             Shutdown::Both => libc::SHUT_RDWR,
365         };
366         cvt(unsafe { libc::shutdown(self.0.raw(), how) })?;
367         Ok(())
368     }
369
370     pub fn set_nodelay(&self, nodelay: bool) -> io::Result<()> {
371         setsockopt(self, libc::IPPROTO_TCP, libc::TCP_NODELAY, nodelay as c_int)
372     }
373
374     pub fn nodelay(&self) -> io::Result<bool> {
375         let raw: c_int = getsockopt(self, libc::IPPROTO_TCP, libc::TCP_NODELAY)?;
376         Ok(raw != 0)
377     }
378
379     #[cfg(any(target_os = "android", target_os = "linux",))]
380     pub fn set_passcred(&self, passcred: bool) -> io::Result<()> {
381         setsockopt(self, libc::SOL_SOCKET, libc::SO_PASSCRED, passcred as libc::c_int)
382     }
383
384     #[cfg(any(target_os = "android", target_os = "linux",))]
385     pub fn passcred(&self) -> io::Result<bool> {
386         let passcred: libc::c_int = getsockopt(self, libc::SOL_SOCKET, libc::SO_PASSCRED)?;
387         Ok(passcred != 0)
388     }
389
390     #[cfg(not(any(target_os = "solaris", target_os = "illumos")))]
391     pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
392         let mut nonblocking = nonblocking as libc::c_int;
393         cvt(unsafe { libc::ioctl(*self.as_inner(), libc::FIONBIO, &mut nonblocking) }).map(drop)
394     }
395
396     #[cfg(any(target_os = "solaris", target_os = "illumos"))]
397     pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
398         // FIONBIO is inadequate for sockets on illumos/Solaris, so use the
399         // fcntl(F_[GS]ETFL)-based method provided by FileDesc instead.
400         self.0.set_nonblocking(nonblocking)
401     }
402
403     pub fn take_error(&self) -> io::Result<Option<io::Error>> {
404         let raw: c_int = getsockopt(self, libc::SOL_SOCKET, libc::SO_ERROR)?;
405         if raw == 0 { Ok(None) } else { Ok(Some(io::Error::from_raw_os_error(raw as i32))) }
406     }
407 }
408
409 impl AsInner<c_int> for Socket {
410     fn as_inner(&self) -> &c_int {
411         self.0.as_inner()
412     }
413 }
414
415 impl FromInner<c_int> for Socket {
416     fn from_inner(fd: c_int) -> Socket {
417         Socket(FileDesc::new(fd))
418     }
419 }
420
421 impl IntoInner<c_int> for Socket {
422     fn into_inner(self) -> c_int {
423         self.0.into_raw()
424     }
425 }
426
427 // In versions of glibc prior to 2.26, there's a bug where the DNS resolver
428 // will cache the contents of /etc/resolv.conf, so changes to that file on disk
429 // can be ignored by a long-running program. That can break DNS lookups on e.g.
430 // laptops where the network comes and goes. See
431 // https://sourceware.org/bugzilla/show_bug.cgi?id=984. Note however that some
432 // distros including Debian have patched glibc to fix this for a long time.
433 //
434 // A workaround for this bug is to call the res_init libc function, to clear
435 // the cached configs. Unfortunately, while we believe glibc's implementation
436 // of res_init is thread-safe, we know that other implementations are not
437 // (https://github.com/rust-lang/rust/issues/43592). Code here in libstd could
438 // try to synchronize its res_init calls with a Mutex, but that wouldn't
439 // protect programs that call into libc in other ways. So instead of calling
440 // res_init unconditionally, we call it only when we detect we're linking
441 // against glibc version < 2.26. (That is, when we both know its needed and
442 // believe it's thread-safe).
443 #[cfg(all(target_env = "gnu", not(target_os = "vxworks")))]
444 fn on_resolver_failure() {
445     use crate::sys;
446
447     // If the version fails to parse, we treat it the same as "not glibc".
448     if let Some(version) = sys::os::glibc_version() {
449         if version < (2, 26) {
450             unsafe { libc::res_init() };
451         }
452     }
453 }
454
455 #[cfg(any(not(target_env = "gnu"), target_os = "vxworks"))]
456 fn on_resolver_failure() {}