]>
Commit | Line | Data |
---|---|---|
85aaf69f SL |
1 | // Copyright 2015 The Rust Project Developers. See the COPYRIGHT |
2 | // file at the top-level directory of this distribution and at | |
3 | // http://rust-lang.org/COPYRIGHT. | |
4 | // | |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
8 | // option. This file may not be copied, modified, or distributed | |
9 | // except according to those terms. | |
10 | ||
85aaf69f SL |
11 | use ffi::CStr; |
12 | use io; | |
8bb4bdeb XL |
13 | use libc::{self, c_int, c_void, size_t, sockaddr, socklen_t, EAI_SYSTEM, MSG_PEEK}; |
14 | use mem; | |
92a42be0 | 15 | use net::{SocketAddr, Shutdown}; |
85aaf69f | 16 | use str; |
85aaf69f | 17 | use sys::fd::FileDesc; |
c1a9b12d | 18 | use sys_common::{AsInner, FromInner, IntoInner}; |
8bb4bdeb | 19 | use sys_common::net::{getsockopt, setsockopt, sockaddr_to_addr}; |
041b39d2 XL |
20 | use time::{Duration, Instant}; |
21 | use cmp; | |
85aaf69f SL |
22 | |
23 | pub use sys::{cvt, cvt_r}; | |
7453a54e | 24 | pub extern crate libc as netc; |
85aaf69f SL |
25 | |
26 | pub type wrlen_t = size_t; | |
27 | ||
7453a54e SL |
28 | // See below for the usage of SOCK_CLOEXEC, but this constant is only defined on |
29 | // Linux currently (e.g. support doesn't exist on other platforms). In order to | |
30 | // get name resolution to work and things to compile we just define a dummy | |
31 | // SOCK_CLOEXEC here for other platforms. Note that the dummy constant isn't | |
32 | // actually ever used (the blocks below are wrapped in `if cfg!` as well. | |
33 | #[cfg(target_os = "linux")] | |
34 | use libc::SOCK_CLOEXEC; | |
35 | #[cfg(not(target_os = "linux"))] | |
36 | const SOCK_CLOEXEC: c_int = 0; | |
37 | ||
c30ab7b3 SL |
38 | // Another conditional contant for name resolution: Macos et iOS use |
39 | // SO_NOSIGPIPE as a setsockopt flag to disable SIGPIPE emission on socket. | |
40 | // Other platforms do otherwise. | |
41 | #[cfg(target_vendor = "apple")] | |
42 | use libc::SO_NOSIGPIPE; | |
43 | #[cfg(not(target_vendor = "apple"))] | |
44 | const SO_NOSIGPIPE: c_int = 0; | |
45 | ||
85aaf69f SL |
46 | pub struct Socket(FileDesc); |
47 | ||
48 | pub fn init() {} | |
49 | ||
50 | pub fn cvt_gai(err: c_int) -> io::Result<()> { | |
9e0c209e SL |
51 | if err == 0 { |
52 | return Ok(()) | |
53 | } | |
2c00a5a8 XL |
54 | |
55 | // We may need to trigger a glibc workaround. See on_resolver_failure() for details. | |
56 | on_resolver_failure(); | |
57 | ||
9e0c209e SL |
58 | if err == EAI_SYSTEM { |
59 | return Err(io::Error::last_os_error()) | |
60 | } | |
85aaf69f SL |
61 | |
62 | let detail = unsafe { | |
92a42be0 | 63 | str::from_utf8(CStr::from_ptr(libc::gai_strerror(err)).to_bytes()).unwrap() |
e9174d1e | 64 | .to_owned() |
85aaf69f SL |
65 | }; |
66 | Err(io::Error::new(io::ErrorKind::Other, | |
c34b1796 AL |
67 | &format!("failed to lookup address information: {}", |
68 | detail)[..])) | |
85aaf69f SL |
69 | } |
70 | ||
71 | impl Socket { | |
72 | pub fn new(addr: &SocketAddr, ty: c_int) -> io::Result<Socket> { | |
c34b1796 AL |
73 | let fam = match *addr { |
74 | SocketAddr::V4(..) => libc::AF_INET, | |
75 | SocketAddr::V6(..) => libc::AF_INET6, | |
85aaf69f | 76 | }; |
54a0048b SL |
77 | Socket::new_raw(fam, ty) |
78 | } | |
79 | ||
80 | pub fn new_raw(fam: c_int, ty: c_int) -> io::Result<Socket> { | |
85aaf69f | 81 | unsafe { |
7453a54e SL |
82 | // On linux we first attempt to pass the SOCK_CLOEXEC flag to |
83 | // atomically create the socket and set it as CLOEXEC. Support for | |
84 | // this option, however, was added in 2.6.27, and we still support | |
85 | // 2.6.18 as a kernel, so if the returned error is EINVAL we | |
86 | // fallthrough to the fallback. | |
5bcae85e | 87 | if cfg!(target_os = "linux") { |
7453a54e SL |
88 | match cvt(libc::socket(fam, ty | SOCK_CLOEXEC, 0)) { |
89 | Ok(fd) => return Ok(Socket(FileDesc::new(fd))), | |
90 | Err(ref e) if e.raw_os_error() == Some(libc::EINVAL) => {} | |
91 | Err(e) => return Err(e), | |
92 | } | |
93 | } | |
94 | ||
54a0048b | 95 | let fd = cvt(libc::socket(fam, ty, 0))?; |
9346a6ac | 96 | let fd = FileDesc::new(fd); |
3157f602 | 97 | fd.set_cloexec()?; |
c30ab7b3 SL |
98 | let socket = Socket(fd); |
99 | if cfg!(target_vendor = "apple") { | |
100 | setsockopt(&socket, libc::SOL_SOCKET, SO_NOSIGPIPE, 1)?; | |
101 | } | |
102 | Ok(socket) | |
85aaf69f SL |
103 | } |
104 | } | |
105 | ||
54a0048b SL |
106 | pub fn new_pair(fam: c_int, ty: c_int) -> io::Result<(Socket, Socket)> { |
107 | unsafe { | |
108 | let mut fds = [0, 0]; | |
109 | ||
110 | // Like above, see if we can set cloexec atomically | |
5bcae85e | 111 | if cfg!(target_os = "linux") { |
54a0048b SL |
112 | match cvt(libc::socketpair(fam, ty | SOCK_CLOEXEC, 0, fds.as_mut_ptr())) { |
113 | Ok(_) => { | |
114 | return Ok((Socket(FileDesc::new(fds[0])), Socket(FileDesc::new(fds[1])))); | |
115 | } | |
116 | Err(ref e) if e.raw_os_error() == Some(libc::EINVAL) => {}, | |
117 | Err(e) => return Err(e), | |
118 | } | |
119 | } | |
120 | ||
121 | cvt(libc::socketpair(fam, ty, 0, fds.as_mut_ptr()))?; | |
122 | let a = FileDesc::new(fds[0]); | |
54a0048b | 123 | let b = FileDesc::new(fds[1]); |
3157f602 XL |
124 | a.set_cloexec()?; |
125 | b.set_cloexec()?; | |
54a0048b SL |
126 | Ok((Socket(a), Socket(b))) |
127 | } | |
128 | } | |
129 | ||
041b39d2 XL |
130 | pub fn connect_timeout(&self, addr: &SocketAddr, timeout: Duration) -> io::Result<()> { |
131 | self.set_nonblocking(true)?; | |
132 | let r = unsafe { | |
133 | let (addrp, len) = addr.into_inner(); | |
134 | cvt(libc::connect(self.0.raw(), addrp, len)) | |
135 | }; | |
136 | self.set_nonblocking(false)?; | |
137 | ||
138 | match r { | |
139 | Ok(_) => return Ok(()), | |
140 | // there's no ErrorKind for EINPROGRESS :( | |
141 | Err(ref e) if e.raw_os_error() == Some(libc::EINPROGRESS) => {} | |
142 | Err(e) => return Err(e), | |
143 | } | |
144 | ||
145 | let mut pollfd = libc::pollfd { | |
146 | fd: self.0.raw(), | |
147 | events: libc::POLLOUT, | |
148 | revents: 0, | |
149 | }; | |
150 | ||
151 | if timeout.as_secs() == 0 && timeout.subsec_nanos() == 0 { | |
152 | return Err(io::Error::new(io::ErrorKind::InvalidInput, | |
153 | "cannot set a 0 duration timeout")); | |
154 | } | |
155 | ||
156 | let start = Instant::now(); | |
157 | ||
158 | loop { | |
159 | let elapsed = start.elapsed(); | |
160 | if elapsed >= timeout { | |
161 | return Err(io::Error::new(io::ErrorKind::TimedOut, "connection timed out")); | |
162 | } | |
163 | ||
164 | let timeout = timeout - elapsed; | |
165 | let mut timeout = timeout.as_secs() | |
166 | .saturating_mul(1_000) | |
167 | .saturating_add(timeout.subsec_nanos() as u64 / 1_000_000); | |
168 | if timeout == 0 { | |
169 | timeout = 1; | |
170 | } | |
171 | ||
172 | let timeout = cmp::min(timeout, c_int::max_value() as u64) as c_int; | |
173 | ||
174 | match unsafe { libc::poll(&mut pollfd, 1, timeout) } { | |
175 | -1 => { | |
176 | let err = io::Error::last_os_error(); | |
177 | if err.kind() != io::ErrorKind::Interrupted { | |
178 | return Err(err); | |
179 | } | |
180 | } | |
181 | 0 => {} | |
182 | _ => { | |
abe05a73 XL |
183 | // linux returns POLLOUT|POLLERR|POLLHUP for refused connections (!), so look |
184 | // for POLLHUP rather than read readiness | |
185 | if pollfd.revents & libc::POLLHUP != 0 { | |
186 | let e = self.take_error()? | |
187 | .unwrap_or_else(|| { | |
188 | io::Error::new(io::ErrorKind::Other, "no error set after POLLHUP") | |
189 | }); | |
190 | return Err(e); | |
041b39d2 | 191 | } |
abe05a73 | 192 | |
041b39d2 XL |
193 | return Ok(()); |
194 | } | |
195 | } | |
196 | } | |
197 | } | |
198 | ||
7453a54e SL |
199 | pub fn accept(&self, storage: *mut sockaddr, len: *mut socklen_t) |
200 | -> io::Result<Socket> { | |
201 | // Unfortunately the only known way right now to accept a socket and | |
202 | // atomically set the CLOEXEC flag is to use the `accept4` syscall on | |
203 | // Linux. This was added in 2.6.28, however, and because we support | |
204 | // 2.6.18 we must detect this support dynamically. | |
205 | if cfg!(target_os = "linux") { | |
206 | weak! { | |
207 | fn accept4(c_int, *mut sockaddr, *mut socklen_t, c_int) -> c_int | |
208 | } | |
209 | if let Some(accept) = accept4.get() { | |
210 | let res = cvt_r(|| unsafe { | |
211 | accept(self.0.raw(), storage, len, SOCK_CLOEXEC) | |
212 | }); | |
213 | match res { | |
214 | Ok(fd) => return Ok(Socket(FileDesc::new(fd))), | |
215 | Err(ref e) if e.raw_os_error() == Some(libc::ENOSYS) => {} | |
216 | Err(e) => return Err(e), | |
217 | } | |
218 | } | |
219 | } | |
220 | ||
54a0048b | 221 | let fd = cvt_r(|| unsafe { |
85aaf69f | 222 | libc::accept(self.0.raw(), storage, len) |
54a0048b | 223 | })?; |
9346a6ac | 224 | let fd = FileDesc::new(fd); |
3157f602 | 225 | fd.set_cloexec()?; |
9346a6ac | 226 | Ok(Socket(fd)) |
85aaf69f SL |
227 | } |
228 | ||
229 | pub fn duplicate(&self) -> io::Result<Socket> { | |
7453a54e | 230 | self.0.duplicate().map(Socket) |
85aaf69f SL |
231 | } |
232 | ||
8bb4bdeb XL |
233 | fn recv_with_flags(&self, buf: &mut [u8], flags: c_int) -> io::Result<usize> { |
234 | let ret = cvt(unsafe { | |
235 | libc::recv(self.0.raw(), | |
236 | buf.as_mut_ptr() as *mut c_void, | |
237 | buf.len(), | |
238 | flags) | |
239 | })?; | |
240 | Ok(ret as usize) | |
241 | } | |
242 | ||
85aaf69f | 243 | pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> { |
8bb4bdeb XL |
244 | self.recv_with_flags(buf, 0) |
245 | } | |
246 | ||
247 | pub fn peek(&self, buf: &mut [u8]) -> io::Result<usize> { | |
248 | self.recv_with_flags(buf, MSG_PEEK) | |
249 | } | |
250 | ||
251 | fn recv_from_with_flags(&self, buf: &mut [u8], flags: c_int) | |
252 | -> io::Result<(usize, SocketAddr)> { | |
253 | let mut storage: libc::sockaddr_storage = unsafe { mem::zeroed() }; | |
254 | let mut addrlen = mem::size_of_val(&storage) as libc::socklen_t; | |
255 | ||
256 | let n = cvt(unsafe { | |
257 | libc::recvfrom(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 | Ok((n as usize, sockaddr_to_addr(&storage, addrlen as usize)?)) | |
265 | } | |
266 | ||
267 | pub fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { | |
268 | self.recv_from_with_flags(buf, 0) | |
269 | } | |
270 | ||
271 | pub fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { | |
272 | self.recv_from_with_flags(buf, MSG_PEEK) | |
85aaf69f | 273 | } |
62682a34 | 274 | |
54a0048b SL |
275 | pub fn write(&self, buf: &[u8]) -> io::Result<usize> { |
276 | self.0.write(buf) | |
277 | } | |
278 | ||
62682a34 SL |
279 | pub fn set_timeout(&self, dur: Option<Duration>, kind: libc::c_int) -> io::Result<()> { |
280 | let timeout = match dur { | |
281 | Some(dur) => { | |
c1a9b12d | 282 | if dur.as_secs() == 0 && dur.subsec_nanos() == 0 { |
62682a34 SL |
283 | return Err(io::Error::new(io::ErrorKind::InvalidInput, |
284 | "cannot set a 0 duration timeout")); | |
285 | } | |
286 | ||
c1a9b12d | 287 | let secs = if dur.as_secs() > libc::time_t::max_value() as u64 { |
62682a34 SL |
288 | libc::time_t::max_value() |
289 | } else { | |
c1a9b12d | 290 | dur.as_secs() as libc::time_t |
62682a34 SL |
291 | }; |
292 | let mut timeout = libc::timeval { | |
293 | tv_sec: secs, | |
c1a9b12d | 294 | tv_usec: (dur.subsec_nanos() / 1000) as libc::suseconds_t, |
62682a34 SL |
295 | }; |
296 | if timeout.tv_sec == 0 && timeout.tv_usec == 0 { | |
297 | timeout.tv_usec = 1; | |
298 | } | |
299 | timeout | |
300 | } | |
301 | None => { | |
302 | libc::timeval { | |
303 | tv_sec: 0, | |
304 | tv_usec: 0, | |
305 | } | |
306 | } | |
307 | }; | |
308 | setsockopt(self, libc::SOL_SOCKET, kind, timeout) | |
309 | } | |
310 | ||
311 | pub fn timeout(&self, kind: libc::c_int) -> io::Result<Option<Duration>> { | |
54a0048b | 312 | let raw: libc::timeval = getsockopt(self, libc::SOL_SOCKET, kind)?; |
62682a34 SL |
313 | if raw.tv_sec == 0 && raw.tv_usec == 0 { |
314 | Ok(None) | |
315 | } else { | |
316 | let sec = raw.tv_sec as u64; | |
317 | let nsec = (raw.tv_usec as u32) * 1000; | |
318 | Ok(Some(Duration::new(sec, nsec))) | |
319 | } | |
320 | } | |
92a42be0 SL |
321 | |
322 | pub fn shutdown(&self, how: Shutdown) -> io::Result<()> { | |
323 | let how = match how { | |
324 | Shutdown::Write => libc::SHUT_WR, | |
325 | Shutdown::Read => libc::SHUT_RD, | |
326 | Shutdown::Both => libc::SHUT_RDWR, | |
327 | }; | |
54a0048b | 328 | cvt(unsafe { libc::shutdown(self.0.raw(), how) })?; |
92a42be0 SL |
329 | Ok(()) |
330 | } | |
54a0048b SL |
331 | |
332 | pub fn set_nodelay(&self, nodelay: bool) -> io::Result<()> { | |
333 | setsockopt(self, libc::IPPROTO_TCP, libc::TCP_NODELAY, nodelay as c_int) | |
334 | } | |
335 | ||
336 | pub fn nodelay(&self) -> io::Result<bool> { | |
337 | let raw: c_int = getsockopt(self, libc::IPPROTO_TCP, libc::TCP_NODELAY)?; | |
338 | Ok(raw != 0) | |
339 | } | |
340 | ||
341 | pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { | |
9e0c209e | 342 | let mut nonblocking = nonblocking as libc::c_int; |
54a0048b SL |
343 | cvt(unsafe { libc::ioctl(*self.as_inner(), libc::FIONBIO, &mut nonblocking) }).map(|_| ()) |
344 | } | |
345 | ||
346 | pub fn take_error(&self) -> io::Result<Option<io::Error>> { | |
347 | let raw: c_int = getsockopt(self, libc::SOL_SOCKET, libc::SO_ERROR)?; | |
348 | if raw == 0 { | |
349 | Ok(None) | |
350 | } else { | |
351 | Ok(Some(io::Error::from_raw_os_error(raw as i32))) | |
352 | } | |
353 | } | |
85aaf69f SL |
354 | } |
355 | ||
356 | impl AsInner<c_int> for Socket { | |
357 | fn as_inner(&self) -> &c_int { self.0.as_inner() } | |
358 | } | |
c34b1796 AL |
359 | |
360 | impl FromInner<c_int> for Socket { | |
361 | fn from_inner(fd: c_int) -> Socket { Socket(FileDesc::new(fd)) } | |
362 | } | |
c1a9b12d SL |
363 | |
364 | impl IntoInner<c_int> for Socket { | |
365 | fn into_inner(self) -> c_int { self.0.into_raw() } | |
366 | } | |
ea8adc8c XL |
367 | |
368 | // In versions of glibc prior to 2.26, there's a bug where the DNS resolver | |
369 | // will cache the contents of /etc/resolv.conf, so changes to that file on disk | |
370 | // can be ignored by a long-running program. That can break DNS lookups on e.g. | |
371 | // laptops where the network comes and goes. See | |
372 | // https://sourceware.org/bugzilla/show_bug.cgi?id=984. Note however that some | |
373 | // distros including Debian have patched glibc to fix this for a long time. | |
374 | // | |
375 | // A workaround for this bug is to call the res_init libc function, to clear | |
376 | // the cached configs. Unfortunately, while we believe glibc's implementation | |
377 | // of res_init is thread-safe, we know that other implementations are not | |
378 | // (https://github.com/rust-lang/rust/issues/43592). Code here in libstd could | |
379 | // try to synchronize its res_init calls with a Mutex, but that wouldn't | |
380 | // protect programs that call into libc in other ways. So instead of calling | |
381 | // res_init unconditionally, we call it only when we detect we're linking | |
382 | // against glibc version < 2.26. (That is, when we both know its needed and | |
383 | // believe it's thread-safe). | |
2c00a5a8 XL |
384 | #[cfg(target_env = "gnu")] |
385 | fn on_resolver_failure() { | |
ea8adc8c XL |
386 | // If the version fails to parse, we treat it the same as "not glibc". |
387 | if let Some(Ok(version_str)) = glibc_version_cstr().map(CStr::to_str) { | |
388 | if let Some(version) = parse_glibc_version(version_str) { | |
389 | if version < (2, 26) { | |
2c00a5a8 | 390 | unsafe { libc::res_init() }; |
ea8adc8c XL |
391 | } |
392 | } | |
393 | } | |
ea8adc8c XL |
394 | } |
395 | ||
2c00a5a8 XL |
396 | #[cfg(not(target_env = "gnu"))] |
397 | fn on_resolver_failure() {} | |
398 | ||
399 | #[cfg(target_env = "gnu")] | |
ea8adc8c XL |
400 | fn glibc_version_cstr() -> Option<&'static CStr> { |
401 | weak! { | |
402 | fn gnu_get_libc_version() -> *const libc::c_char | |
403 | } | |
404 | if let Some(f) = gnu_get_libc_version.get() { | |
405 | unsafe { Some(CStr::from_ptr(f())) } | |
406 | } else { | |
407 | None | |
408 | } | |
409 | } | |
410 | ||
411 | // Returns Some((major, minor)) if the string is a valid "x.y" version, | |
412 | // ignoring any extra dot-separated parts. Otherwise return None. | |
2c00a5a8 | 413 | #[cfg(target_env = "gnu")] |
ea8adc8c XL |
414 | fn parse_glibc_version(version: &str) -> Option<(usize, usize)> { |
415 | let mut parsed_ints = version.split(".").map(str::parse::<usize>).fuse(); | |
416 | match (parsed_ints.next(), parsed_ints.next()) { | |
417 | (Some(Ok(major)), Some(Ok(minor))) => Some((major, minor)), | |
418 | _ => None | |
419 | } | |
420 | } | |
421 | ||
2c00a5a8 | 422 | #[cfg(all(test, taget_env = "gnu"))] |
ea8adc8c XL |
423 | mod test { |
424 | use super::*; | |
425 | ||
426 | #[test] | |
427 | fn test_res_init() { | |
428 | // This mostly just tests that the weak linkage doesn't panic wildly... | |
429 | res_init_if_glibc_before_2_26().unwrap(); | |
430 | } | |
431 | ||
432 | #[test] | |
433 | fn test_parse_glibc_version() { | |
434 | let cases = [ | |
435 | ("0.0", Some((0, 0))), | |
436 | ("01.+2", Some((1, 2))), | |
437 | ("3.4.5.six", Some((3, 4))), | |
438 | ("1", None), | |
439 | ("1.-2", None), | |
440 | ("1.foo", None), | |
441 | ("foo.1", None), | |
442 | ]; | |
443 | for &(version_str, parsed) in cases.iter() { | |
444 | assert_eq!(parsed, parse_glibc_version(version_str)); | |
445 | } | |
446 | } | |
447 | } |