]> git.lizzy.rs Git - rust.git/blob - library/std/src/sys/unix/net.rs
Rollup merge of #78070 - RalfJung:const-panic-test, r=oli-obk
[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(target_os = "linux")] {
59                     // On Linux we pass the SOCK_CLOEXEC flag to atomically create
60                     // the socket and set it as CLOEXEC, added in 2.6.27.
61                     let fd = cvt(libc::socket(fam, ty | libc::SOCK_CLOEXEC, 0))?;
62                     Ok(Socket(FileDesc::new(fd)))
63                 } else {
64                     let fd = cvt(libc::socket(fam, ty, 0))?;
65                     let fd = FileDesc::new(fd);
66                     fd.set_cloexec()?;
67                     let socket = Socket(fd);
68
69                     // macOS and iOS use `SO_NOSIGPIPE` as a `setsockopt`
70                     // flag to disable `SIGPIPE` emission on socket.
71                     #[cfg(target_vendor = "apple")]
72                     setsockopt(&socket, libc::SOL_SOCKET, libc::SO_NOSIGPIPE, 1)?;
73
74                     Ok(socket)
75                 }
76             }
77         }
78     }
79
80     #[cfg(not(target_os = "vxworks"))]
81     pub fn new_pair(fam: c_int, ty: c_int) -> io::Result<(Socket, Socket)> {
82         unsafe {
83             let mut fds = [0, 0];
84
85             cfg_if::cfg_if! {
86                 if #[cfg(target_os = "linux")] {
87                     // Like above, set cloexec atomically
88                     cvt(libc::socketpair(fam, ty | libc::SOCK_CLOEXEC, 0, fds.as_mut_ptr()))?;
89                     Ok((Socket(FileDesc::new(fds[0])), Socket(FileDesc::new(fds[1]))))
90                 } else {
91                     cvt(libc::socketpair(fam, ty, 0, fds.as_mut_ptr()))?;
92                     let a = FileDesc::new(fds[0]);
93                     let b = FileDesc::new(fds[1]);
94                     a.set_cloexec()?;
95                     b.set_cloexec()?;
96                     Ok((Socket(a), Socket(b)))
97                 }
98             }
99         }
100     }
101
102     #[cfg(target_os = "vxworks")]
103     pub fn new_pair(_fam: c_int, _ty: c_int) -> io::Result<(Socket, Socket)> {
104         unimplemented!()
105     }
106
107     pub fn connect_timeout(&self, addr: &SocketAddr, timeout: Duration) -> io::Result<()> {
108         self.set_nonblocking(true)?;
109         let r = unsafe {
110             let (addrp, len) = addr.into_inner();
111             cvt(libc::connect(self.0.raw(), addrp, len))
112         };
113         self.set_nonblocking(false)?;
114
115         match r {
116             Ok(_) => return Ok(()),
117             // there's no ErrorKind for EINPROGRESS :(
118             Err(ref e) if e.raw_os_error() == Some(libc::EINPROGRESS) => {}
119             Err(e) => return Err(e),
120         }
121
122         let mut pollfd = libc::pollfd { fd: self.0.raw(), events: libc::POLLOUT, revents: 0 };
123
124         if timeout.as_secs() == 0 && timeout.subsec_nanos() == 0 {
125             return Err(io::Error::new(
126                 io::ErrorKind::InvalidInput,
127                 "cannot set a 0 duration timeout",
128             ));
129         }
130
131         let start = Instant::now();
132
133         loop {
134             let elapsed = start.elapsed();
135             if elapsed >= timeout {
136                 return Err(io::Error::new(io::ErrorKind::TimedOut, "connection timed out"));
137             }
138
139             let timeout = timeout - elapsed;
140             let mut timeout = timeout
141                 .as_secs()
142                 .saturating_mul(1_000)
143                 .saturating_add(timeout.subsec_nanos() as u64 / 1_000_000);
144             if timeout == 0 {
145                 timeout = 1;
146             }
147
148             let timeout = cmp::min(timeout, c_int::MAX as u64) as c_int;
149
150             match unsafe { libc::poll(&mut pollfd, 1, timeout) } {
151                 -1 => {
152                     let err = io::Error::last_os_error();
153                     if err.kind() != io::ErrorKind::Interrupted {
154                         return Err(err);
155                     }
156                 }
157                 0 => {}
158                 _ => {
159                     // linux returns POLLOUT|POLLERR|POLLHUP for refused connections (!), so look
160                     // for POLLHUP rather than read readiness
161                     if pollfd.revents & libc::POLLHUP != 0 {
162                         let e = self.take_error()?.unwrap_or_else(|| {
163                             io::Error::new(io::ErrorKind::Other, "no error set after POLLHUP")
164                         });
165                         return Err(e);
166                     }
167
168                     return Ok(());
169                 }
170             }
171         }
172     }
173
174     pub fn accept(&self, storage: *mut sockaddr, len: *mut socklen_t) -> io::Result<Socket> {
175         // Unfortunately the only known way right now to accept a socket and
176         // atomically set the CLOEXEC flag is to use the `accept4` syscall on
177         // Linux. This was added in 2.6.28, glibc 2.10 and musl 0.9.5.
178         cfg_if::cfg_if! {
179             if #[cfg(target_os = "linux")] {
180                 let fd = cvt_r(|| unsafe {
181                     libc::accept4(self.0.raw(), storage, len, libc::SOCK_CLOEXEC)
182                 })?;
183                 Ok(Socket(FileDesc::new(fd)))
184             } else {
185                 let fd = cvt_r(|| unsafe { libc::accept(self.0.raw(), storage, len) })?;
186                 let fd = FileDesc::new(fd);
187                 fd.set_cloexec()?;
188                 Ok(Socket(fd))
189             }
190         }
191     }
192
193     pub fn duplicate(&self) -> io::Result<Socket> {
194         self.0.duplicate().map(Socket)
195     }
196
197     fn recv_with_flags(&self, buf: &mut [u8], flags: c_int) -> io::Result<usize> {
198         let ret = cvt(unsafe {
199             libc::recv(self.0.raw(), buf.as_mut_ptr() as *mut c_void, buf.len(), flags)
200         })?;
201         Ok(ret as usize)
202     }
203
204     pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
205         self.recv_with_flags(buf, 0)
206     }
207
208     pub fn peek(&self, buf: &mut [u8]) -> io::Result<usize> {
209         self.recv_with_flags(buf, MSG_PEEK)
210     }
211
212     pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
213         self.0.read_vectored(bufs)
214     }
215
216     #[inline]
217     pub fn is_read_vectored(&self) -> bool {
218         self.0.is_read_vectored()
219     }
220
221     fn recv_from_with_flags(
222         &self,
223         buf: &mut [u8],
224         flags: c_int,
225     ) -> io::Result<(usize, SocketAddr)> {
226         let mut storage: libc::sockaddr_storage = unsafe { mem::zeroed() };
227         let mut addrlen = mem::size_of_val(&storage) as libc::socklen_t;
228
229         let n = cvt(unsafe {
230             libc::recvfrom(
231                 self.0.raw(),
232                 buf.as_mut_ptr() as *mut c_void,
233                 buf.len(),
234                 flags,
235                 &mut storage as *mut _ as *mut _,
236                 &mut addrlen,
237             )
238         })?;
239         Ok((n as usize, sockaddr_to_addr(&storage, addrlen as usize)?))
240     }
241
242     pub fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
243         self.recv_from_with_flags(buf, 0)
244     }
245
246     pub fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
247         self.recv_from_with_flags(buf, MSG_PEEK)
248     }
249
250     pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
251         self.0.write(buf)
252     }
253
254     pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
255         self.0.write_vectored(bufs)
256     }
257
258     #[inline]
259     pub fn is_write_vectored(&self) -> bool {
260         self.0.is_write_vectored()
261     }
262
263     pub fn set_timeout(&self, dur: Option<Duration>, kind: libc::c_int) -> io::Result<()> {
264         let timeout = match dur {
265             Some(dur) => {
266                 if dur.as_secs() == 0 && dur.subsec_nanos() == 0 {
267                     return Err(io::Error::new(
268                         io::ErrorKind::InvalidInput,
269                         "cannot set a 0 duration timeout",
270                     ));
271                 }
272
273                 let secs = if dur.as_secs() > libc::time_t::MAX as u64 {
274                     libc::time_t::MAX
275                 } else {
276                     dur.as_secs() as libc::time_t
277                 };
278                 let mut timeout = libc::timeval {
279                     tv_sec: secs,
280                     tv_usec: dur.subsec_micros() as libc::suseconds_t,
281                 };
282                 if timeout.tv_sec == 0 && timeout.tv_usec == 0 {
283                     timeout.tv_usec = 1;
284                 }
285                 timeout
286             }
287             None => libc::timeval { tv_sec: 0, tv_usec: 0 },
288         };
289         setsockopt(self, libc::SOL_SOCKET, kind, timeout)
290     }
291
292     pub fn timeout(&self, kind: libc::c_int) -> io::Result<Option<Duration>> {
293         let raw: libc::timeval = getsockopt(self, libc::SOL_SOCKET, kind)?;
294         if raw.tv_sec == 0 && raw.tv_usec == 0 {
295             Ok(None)
296         } else {
297             let sec = raw.tv_sec as u64;
298             let nsec = (raw.tv_usec as u32) * 1000;
299             Ok(Some(Duration::new(sec, nsec)))
300         }
301     }
302
303     pub fn shutdown(&self, how: Shutdown) -> io::Result<()> {
304         let how = match how {
305             Shutdown::Write => libc::SHUT_WR,
306             Shutdown::Read => libc::SHUT_RD,
307             Shutdown::Both => libc::SHUT_RDWR,
308         };
309         cvt(unsafe { libc::shutdown(self.0.raw(), how) })?;
310         Ok(())
311     }
312
313     pub fn set_nodelay(&self, nodelay: bool) -> io::Result<()> {
314         setsockopt(self, libc::IPPROTO_TCP, libc::TCP_NODELAY, nodelay as c_int)
315     }
316
317     pub fn nodelay(&self) -> io::Result<bool> {
318         let raw: c_int = getsockopt(self, libc::IPPROTO_TCP, libc::TCP_NODELAY)?;
319         Ok(raw != 0)
320     }
321
322     #[cfg(not(any(target_os = "solaris", target_os = "illumos")))]
323     pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
324         let mut nonblocking = nonblocking as libc::c_int;
325         cvt(unsafe { libc::ioctl(*self.as_inner(), libc::FIONBIO, &mut nonblocking) }).map(drop)
326     }
327
328     #[cfg(any(target_os = "solaris", target_os = "illumos"))]
329     pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
330         // FIONBIO is inadequate for sockets on illumos/Solaris, so use the
331         // fcntl(F_[GS]ETFL)-based method provided by FileDesc instead.
332         self.0.set_nonblocking(nonblocking)
333     }
334
335     pub fn take_error(&self) -> io::Result<Option<io::Error>> {
336         let raw: c_int = getsockopt(self, libc::SOL_SOCKET, libc::SO_ERROR)?;
337         if raw == 0 { Ok(None) } else { Ok(Some(io::Error::from_raw_os_error(raw as i32))) }
338     }
339 }
340
341 impl AsInner<c_int> for Socket {
342     fn as_inner(&self) -> &c_int {
343         self.0.as_inner()
344     }
345 }
346
347 impl FromInner<c_int> for Socket {
348     fn from_inner(fd: c_int) -> Socket {
349         Socket(FileDesc::new(fd))
350     }
351 }
352
353 impl IntoInner<c_int> for Socket {
354     fn into_inner(self) -> c_int {
355         self.0.into_raw()
356     }
357 }
358
359 // In versions of glibc prior to 2.26, there's a bug where the DNS resolver
360 // will cache the contents of /etc/resolv.conf, so changes to that file on disk
361 // can be ignored by a long-running program. That can break DNS lookups on e.g.
362 // laptops where the network comes and goes. See
363 // https://sourceware.org/bugzilla/show_bug.cgi?id=984. Note however that some
364 // distros including Debian have patched glibc to fix this for a long time.
365 //
366 // A workaround for this bug is to call the res_init libc function, to clear
367 // the cached configs. Unfortunately, while we believe glibc's implementation
368 // of res_init is thread-safe, we know that other implementations are not
369 // (https://github.com/rust-lang/rust/issues/43592). Code here in libstd could
370 // try to synchronize its res_init calls with a Mutex, but that wouldn't
371 // protect programs that call into libc in other ways. So instead of calling
372 // res_init unconditionally, we call it only when we detect we're linking
373 // against glibc version < 2.26. (That is, when we both know its needed and
374 // believe it's thread-safe).
375 #[cfg(all(target_env = "gnu", not(target_os = "vxworks")))]
376 fn on_resolver_failure() {
377     use crate::sys;
378
379     // If the version fails to parse, we treat it the same as "not glibc".
380     if let Some(version) = sys::os::glibc_version() {
381         if version < (2, 26) {
382             unsafe { libc::res_init() };
383         }
384     }
385 }
386
387 #[cfg(any(not(target_env = "gnu"), target_os = "vxworks"))]
388 fn on_resolver_failure() {}