Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

replace libc::res_init with res_init_if_glibc_before_2_26 #44965

Merged
merged 1 commit into from
Oct 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/libstd/sys/unix/l4re.rs
Original file line number Diff line number Diff line change
Expand Up @@ -437,5 +437,9 @@ pub mod net {
pub fn lookup_host(_: &str) -> io::Result<LookupHost> {
unimpl!();
}

pub fn res_init_if_glibc_before_2_26() -> io::Result<()> {
unimpl!();
}
}

79 changes: 79 additions & 0 deletions src/libstd/sys/unix/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,3 +355,82 @@ impl FromInner<c_int> for Socket {
impl IntoInner<c_int> for Socket {
fn into_inner(self) -> c_int { self.0.into_raw() }
}

// In versions of glibc prior to 2.26, there's a bug where the DNS resolver
// will cache the contents of /etc/resolv.conf, so changes to that file on disk
// can be ignored by a long-running program. That can break DNS lookups on e.g.
// laptops where the network comes and goes. See
// https://sourceware.org/bugzilla/show_bug.cgi?id=984. Note however that some
// distros including Debian have patched glibc to fix this for a long time.
//
// A workaround for this bug is to call the res_init libc function, to clear
// the cached configs. Unfortunately, while we believe glibc's implementation
// of res_init is thread-safe, we know that other implementations are not
// (https://github.com/rust-lang/rust/issues/43592). Code here in libstd could
// try to synchronize its res_init calls with a Mutex, but that wouldn't
// protect programs that call into libc in other ways. So instead of calling
// res_init unconditionally, we call it only when we detect we're linking
// against glibc version < 2.26. (That is, when we both know its needed and
// believe it's thread-safe).
pub fn res_init_if_glibc_before_2_26() -> io::Result<()> {
// If the version fails to parse, we treat it the same as "not glibc".
if let Some(Ok(version_str)) = glibc_version_cstr().map(CStr::to_str) {
if let Some(version) = parse_glibc_version(version_str) {
if version < (2, 26) {
let ret = unsafe { libc::res_init() };
if ret != 0 {
return Err(io::Error::last_os_error());
}
}
}
}
Ok(())
}

fn glibc_version_cstr() -> Option<&'static CStr> {
weak! {
fn gnu_get_libc_version() -> *const libc::c_char
}
if let Some(f) = gnu_get_libc_version.get() {
unsafe { Some(CStr::from_ptr(f())) }
} else {
None
}
}

// Returns Some((major, minor)) if the string is a valid "x.y" version,
// ignoring any extra dot-separated parts. Otherwise return None.
fn parse_glibc_version(version: &str) -> Option<(usize, usize)> {
let mut parsed_ints = version.split(".").map(str::parse::<usize>).fuse();
match (parsed_ints.next(), parsed_ints.next()) {
(Some(Ok(major)), Some(Ok(minor))) => Some((major, minor)),
_ => None
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_res_init() {
// This mostly just tests that the weak linkage doesn't panic wildly...
res_init_if_glibc_before_2_26().unwrap();
}

#[test]
fn test_parse_glibc_version() {
let cases = [
("0.0", Some((0, 0))),
("01.+2", Some((1, 2))),
("3.4.5.six", Some((3, 4))),
("1", None),
("1.-2", None),
("1.foo", None),
("foo.1", None),
];
for &(version_str, parsed) in cases.iter() {
assert_eq!(parsed, parse_glibc_version(version_str));
}
}
}
13 changes: 9 additions & 4 deletions src/libstd/sys_common/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,15 @@ pub fn lookup_host(host: &str) -> io::Result<LookupHost> {
},
#[cfg(unix)]
Err(e) => {
// The lookup failure could be caused by using a stale /etc/resolv.conf.
// See https://github.com/rust-lang/rust/issues/41570.
// We therefore force a reload of the nameserver information.
c::res_init();
// If we're running glibc prior to version 2.26, the lookup
// failure could be caused by caching a stale /etc/resolv.conf.
// We need to call libc::res_init() to clear the cache. But we
// shouldn't call it in on any other platform, because other
// res_init implementations aren't thread-safe. See
// https://github.com/rust-lang/rust/issues/41570 and
// https://github.com/rust-lang/rust/issues/43592.
use sys::net::res_init_if_glibc_before_2_26;
let _ = res_init_if_glibc_before_2_26();
Err(e)
},
// the cfg is needed here to avoid an "unreachable pattern" warning
Expand Down