]> git.lizzy.rs Git - rust.git/blob - library/std/src/sys/unix/net.rs
71c6aa5a0e7ea43e0a000b1a6182c382a9d766f8
[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::Other,
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 = "opensbd",
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 = "opensbd",
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(
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(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(io::ErrorKind::Other, "no error set after POLLHUP")
181                         });
182                         return Err(e);
183                     }
184
185                     return Ok(());
186                 }
187             }
188         }
189     }
190
191     pub fn accept(&self, storage: *mut sockaddr, len: *mut socklen_t) -> io::Result<Socket> {
192         // Unfortunately the only known way right now to accept a socket and
193         // atomically set the CLOEXEC flag is to use the `accept4` syscall on
194         // platforms that support it. On Linux, this was added in 2.6.28,
195         // glibc 2.10 and musl 0.9.5.
196         cfg_if::cfg_if! {
197             if #[cfg(any(
198                 target_os = "android",
199                 target_os = "dragonfly",
200                 target_os = "freebsd",
201                 target_os = "illumos",
202                 target_os = "linux",
203                 target_os = "netbsd",
204                 target_os = "opensbd",
205             ))] {
206                 let fd = cvt_r(|| unsafe {
207                     libc::accept4(self.0.raw(), storage, len, libc::SOCK_CLOEXEC)
208                 })?;
209                 Ok(Socket(FileDesc::new(fd)))
210             } else {
211                 let fd = cvt_r(|| unsafe { libc::accept(self.0.raw(), storage, len) })?;
212                 let fd = FileDesc::new(fd);
213                 fd.set_cloexec()?;
214                 Ok(Socket(fd))
215             }
216         }
217     }
218
219     pub fn duplicate(&self) -> io::Result<Socket> {
220         self.0.duplicate().map(Socket)
221     }
222
223     fn recv_with_flags(&self, buf: &mut [u8], flags: c_int) -> io::Result<usize> {
224         let ret = cvt(unsafe {
225             libc::recv(self.0.raw(), buf.as_mut_ptr() as *mut c_void, buf.len(), flags)
226         })?;
227         Ok(ret as usize)
228     }
229
230     pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
231         self.recv_with_flags(buf, 0)
232     }
233
234     pub fn peek(&self, buf: &mut [u8]) -> io::Result<usize> {
235         self.recv_with_flags(buf, MSG_PEEK)
236     }
237
238     pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
239         self.0.read_vectored(bufs)
240     }
241
242     #[inline]
243     pub fn is_read_vectored(&self) -> bool {
244         self.0.is_read_vectored()
245     }
246
247     fn recv_from_with_flags(
248         &self,
249         buf: &mut [u8],
250         flags: c_int,
251     ) -> io::Result<(usize, SocketAddr)> {
252         let mut storage: libc::sockaddr_storage = unsafe { mem::zeroed() };
253         let mut addrlen = mem::size_of_val(&storage) as libc::socklen_t;
254
255         let n = cvt(unsafe {
256             libc::recvfrom(
257                 self.0.raw(),
258                 buf.as_mut_ptr() as *mut c_void,
259                 buf.len(),
260                 flags,
261                 &mut storage as *mut _ as *mut _,
262                 &mut addrlen,
263             )
264         })?;
265         Ok((n as usize, sockaddr_to_addr(&storage, addrlen as usize)?))
266     }
267
268     pub fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
269         self.recv_from_with_flags(buf, 0)
270     }
271
272     pub fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
273         self.recv_from_with_flags(buf, MSG_PEEK)
274     }
275
276     pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
277         self.0.write(buf)
278     }
279
280     pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
281         self.0.write_vectored(bufs)
282     }
283
284     #[inline]
285     pub fn is_write_vectored(&self) -> bool {
286         self.0.is_write_vectored()
287     }
288
289     pub fn set_timeout(&self, dur: Option<Duration>, kind: libc::c_int) -> io::Result<()> {
290         let timeout = match dur {
291             Some(dur) => {
292                 if dur.as_secs() == 0 && dur.subsec_nanos() == 0 {
293                     return Err(io::Error::new(
294                         io::ErrorKind::InvalidInput,
295                         "cannot set a 0 duration timeout",
296                     ));
297                 }
298
299                 let secs = if dur.as_secs() > libc::time_t::MAX as u64 {
300                     libc::time_t::MAX
301                 } else {
302                     dur.as_secs() as libc::time_t
303                 };
304                 let mut timeout = libc::timeval {
305                     tv_sec: secs,
306                     tv_usec: dur.subsec_micros() as libc::suseconds_t,
307                 };
308                 if timeout.tv_sec == 0 && timeout.tv_usec == 0 {
309                     timeout.tv_usec = 1;
310                 }
311                 timeout
312             }
313             None => libc::timeval { tv_sec: 0, tv_usec: 0 },
314         };
315         setsockopt(self, libc::SOL_SOCKET, kind, timeout)
316     }
317
318     pub fn timeout(&self, kind: libc::c_int) -> io::Result<Option<Duration>> {
319         let raw: libc::timeval = getsockopt(self, libc::SOL_SOCKET, kind)?;
320         if raw.tv_sec == 0 && raw.tv_usec == 0 {
321             Ok(None)
322         } else {
323             let sec = raw.tv_sec as u64;
324             let nsec = (raw.tv_usec as u32) * 1000;
325             Ok(Some(Duration::new(sec, nsec)))
326         }
327     }
328
329     pub fn shutdown(&self, how: Shutdown) -> io::Result<()> {
330         let how = match how {
331             Shutdown::Write => libc::SHUT_WR,
332             Shutdown::Read => libc::SHUT_RD,
333             Shutdown::Both => libc::SHUT_RDWR,
334         };
335         cvt(unsafe { libc::shutdown(self.0.raw(), how) })?;
336         Ok(())
337     }
338
339     pub fn set_nodelay(&self, nodelay: bool) -> io::Result<()> {
340         setsockopt(self, libc::IPPROTO_TCP, libc::TCP_NODELAY, nodelay as c_int)
341     }
342
343     pub fn nodelay(&self) -> io::Result<bool> {
344         let raw: c_int = getsockopt(self, libc::IPPROTO_TCP, libc::TCP_NODELAY)?;
345         Ok(raw != 0)
346     }
347
348     #[cfg(not(any(target_os = "solaris", target_os = "illumos")))]
349     pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
350         let mut nonblocking = nonblocking as libc::c_int;
351         cvt(unsafe { libc::ioctl(*self.as_inner(), libc::FIONBIO, &mut nonblocking) }).map(drop)
352     }
353
354     #[cfg(any(target_os = "solaris", target_os = "illumos"))]
355     pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
356         // FIONBIO is inadequate for sockets on illumos/Solaris, so use the
357         // fcntl(F_[GS]ETFL)-based method provided by FileDesc instead.
358         self.0.set_nonblocking(nonblocking)
359     }
360
361     pub fn take_error(&self) -> io::Result<Option<io::Error>> {
362         let raw: c_int = getsockopt(self, libc::SOL_SOCKET, libc::SO_ERROR)?;
363         if raw == 0 { Ok(None) } else { Ok(Some(io::Error::from_raw_os_error(raw as i32))) }
364     }
365 }
366
367 impl AsInner<c_int> for Socket {
368     fn as_inner(&self) -> &c_int {
369         self.0.as_inner()
370     }
371 }
372
373 impl FromInner<c_int> for Socket {
374     fn from_inner(fd: c_int) -> Socket {
375         Socket(FileDesc::new(fd))
376     }
377 }
378
379 impl IntoInner<c_int> for Socket {
380     fn into_inner(self) -> c_int {
381         self.0.into_raw()
382     }
383 }
384
385 // In versions of glibc prior to 2.26, there's a bug where the DNS resolver
386 // will cache the contents of /etc/resolv.conf, so changes to that file on disk
387 // can be ignored by a long-running program. That can break DNS lookups on e.g.
388 // laptops where the network comes and goes. See
389 // https://sourceware.org/bugzilla/show_bug.cgi?id=984. Note however that some
390 // distros including Debian have patched glibc to fix this for a long time.
391 //
392 // A workaround for this bug is to call the res_init libc function, to clear
393 // the cached configs. Unfortunately, while we believe glibc's implementation
394 // of res_init is thread-safe, we know that other implementations are not
395 // (https://github.com/rust-lang/rust/issues/43592). Code here in libstd could
396 // try to synchronize its res_init calls with a Mutex, but that wouldn't
397 // protect programs that call into libc in other ways. So instead of calling
398 // res_init unconditionally, we call it only when we detect we're linking
399 // against glibc version < 2.26. (That is, when we both know its needed and
400 // believe it's thread-safe).
401 #[cfg(all(target_env = "gnu", not(target_os = "vxworks")))]
402 fn on_resolver_failure() {
403     use crate::sys;
404
405     // If the version fails to parse, we treat it the same as "not glibc".
406     if let Some(version) = sys::os::glibc_version() {
407         if version < (2, 26) {
408             unsafe { libc::res_init() };
409         }
410     }
411 }
412
413 #[cfg(any(not(target_env = "gnu"), target_os = "vxworks"))]
414 fn on_resolver_failure() {}