Skip to content
This repository has been archived by the owner on Jan 25, 2024. It is now read-only.

Commit

Permalink
Gethostbyname: use proper glibc function
Browse files Browse the repository at this point in the history
We internally used getai to restpond to the gethostbyname operations.
Sadly, it did not behave as expected and broke some tools (like
hostname --fqdn, see #4.

We FFI the right Glibc gethostbyname_2r (now deprecated) function and
use it to back the GETHOSTBYNAME and GETHOSTBYNAME6 Nscd interfaces.

Fixes  #4
  • Loading branch information
picnoir committed Oct 8, 2023
1 parent 94f54bb commit decb9e5
Show file tree
Hide file tree
Showing 3 changed files with 203 additions and 47 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ authors = [
"Geoffrey Thomas <geoffrey.thomas@twosigma.com>",
"Leif Walsh <leif.walsh@twosigma.com>",
]
edition = "2018"
edition = "2021"
description = "The name service non-caching daemon"
readme = "README.md"
repository = "https://github.com/twosigma/nsncd"
Expand Down
187 changes: 186 additions & 1 deletion src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@
* limitations under the License.
*/

use nix::libc;
use anyhow::bail;
use nix::libc::{self};
use nix::errno::Errno;
use std::ffi::{CStr, CString};
use std::ptr;

#[allow(non_camel_case_types)]
type size_t = ::std::os::raw::c_ulonglong;
Expand All @@ -40,3 +44,184 @@ pub fn disable_internal_nscd() {
__nss_disable_nscd(do_nothing);
}
}

mod ffi {
use nix::libc;
extern "C" {
pub fn gethostbyname2_r (
name: *const libc::c_char,
af: libc::c_int,
result_buf: *mut libc::hostent,
buf: *mut libc::c_char,
buflen: libc::size_t,
result: *mut *mut libc::hostent,
h_errnop: *mut libc::c_int,
) -> libc::c_int;
}
}

#[derive(Clone, Debug)]
pub struct Hostent {
pub name: String,
pub aliases: Vec<String>,
pub addr_type: i32,
pub addr_list: Vec<std::net::IpAddr>,
}

impl TryFrom<libc::hostent> for Hostent {
type Error = anyhow::Error;

fn try_from(value: libc::hostent) -> Result<Self, Self::Error> {
// validate value.h_addtype, and bail out if it's unsupported
if value.h_addrtype != libc::AF_INET && value.h_addrtype != libc::AF_INET6 {
bail!("unsupported address type: {}", value.h_addrtype);
}

// ensure value.h_length matches what we know from this address family
if value.h_addrtype == libc::AF_INET && value.h_length != 4 {
bail!("unsupported h_length for AF_INET: {}", value.h_length);
}
if value.h_addrtype == libc::AF_INET6 && value.h_length != 16 {
bail!("unsupported h_length for AF_INET6: {}", value.h_length);
}

let name = unsafe { CStr::from_ptr(value.h_name).to_str().unwrap().to_string() };

Ok({
// construct the list of aliases. keep adding to value.h_aliases until we encounter a null pointer.
let mut aliases: Vec<String> = Vec::new();
let mut h_alias_ptr = value.h_aliases;
loop {
if unsafe {(*h_alias_ptr).is_null()} {
break;
} else {
aliases.push(unsafe {
CStr::from_ptr(*h_alias_ptr).to_str().unwrap().to_string()
});
// increment
unsafe {
h_alias_ptr = h_alias_ptr.add(1);
}
}
}

// value.h_addrtype

// construct the list of addresses.
let mut addr_list: Vec<std::net::IpAddr> = Vec::new();

// copy the pointer into a private variable that we can mutate
let mut h_addr_ptr = value.h_addr_list;
loop {
if unsafe { (*h_addr_ptr).is_null() } {
break;
} else {
// read the value
let mut b: Vec<u8> = Vec::new();
// h_length is the length of the address in bytes (4 or 16),
// which is ensured by our checks at the beginning of the function.
for _ in 0..value.h_length {
unsafe {
b.push(**h_addr_ptr as u8 );
*h_addr_ptr = (*h_addr_ptr).add(1);
}
}

if value.h_addrtype == libc::AF_INET {
addr_list.push({
let octets: [u8; 4] = b.try_into().unwrap();
std::net::IpAddr::V4(std::net::Ipv4Addr::from(octets))
});
} else {
addr_list.push({
let octets: [u8; 16] = b.try_into().unwrap();
std::net::IpAddr::V6(std::net::Ipv6Addr::from(octets))
});
}

// increment the pointer
unsafe { h_addr_ptr = h_addr_ptr.add(1) };
}
}

Hostent {
name,
aliases,
addr_type: value.h_addrtype,
addr_list,
}
})
}
}

///
///
/// The stream is positioned at the first entry in the directory.
///
/// af is nix::libc::AF_INET6 or nix::libc::AF_INET6
pub fn gethostbyname2_r(name: String, af: libc::c_int) -> anyhow::Result<Hostent> {
let name = CString::new(name).unwrap();

// Prepare a libc::hostent and the pointer to the result list,
// which will passed to the ffi::gethostbyname2_r call.
let mut ret_hostent: libc::hostent = libc::hostent {
h_name: ptr::null_mut(), // <- points to buf
h_aliases: ptr::null_mut(),
h_addrtype: 0,
h_length: 0,
h_addr_list: ptr::null_mut(),
};
let mut err: libc::c_int = 0;
let mut buf: Vec<u8> = Vec::with_capacity(200);
// We absolutely need to point hostent_result to the start of the
// result buffer. Glibc segfaults if we don't.
let mut hostent_result = ptr::null_mut();
let ret: i32 = loop {
let ret = unsafe {
ffi::gethostbyname2_r(
name.as_ptr(),
af,
&mut ret_hostent,
buf.as_mut_ptr() as *mut i8,
(buf.capacity() as size_t).try_into().unwrap(),
&mut hostent_result,
&mut err,
)
};
if ret == libc::ERANGE {
// The buffer is too small. Let's x2 its capacity and retry.
buf.reserve(buf.capacity() * 2);
} else {
break ret;
}
};
if err == 0 && ret == 0 && ! hostent_result.is_null() {
unsafe {
let res = (*hostent_result).try_into().unwrap();
Ok(res)
}
} else {
// errno
Err(anyhow::anyhow!(Errno::from_i32(err)))
}
}

#[test]
fn test_gethostbyname2_r() {
disable_internal_nscd();

let result: Result<Hostent, anyhow::Error> = gethostbyname2_r(
"localhost.".to_string(),
libc::AF_INET,
);

result.expect("Should resolve IPv4 localhost.");

let result: Result<Hostent, anyhow::Error> = gethostbyname2_r(
"localhost.".to_string(),
libc::AF_INET6,
);

let res = result.expect("Should resolve IPv6 localhost.");
println!("{:?}", res);
}
61 changes: 16 additions & 45 deletions src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use nix::unistd::{getgrouplist, Gid, Group, Uid, User};
use slog::{debug, error, Logger};
use std::mem::size_of;

use crate::ffi::gethostbyname2_r;
use crate::protocol::{AiResponse, AiResponseHeader};

use super::config::Config;
Expand Down Expand Up @@ -180,6 +181,7 @@ pub fn handle_request(
let address = IpAddr::from(address_bytes);

let sock = SocketAddr::new(address, 0);
// TODO: why getnameinfo, replace with gethostbyaddr
let host = match getnameinfo(&sock, NI_NUMERICSERV) {
Ok((hostname, _service)) => Ok(Some(Host {
addresses: vec![address],
Expand Down Expand Up @@ -217,60 +219,29 @@ pub fn handle_request(

RequestType::GETHOSTBYNAME => {
let hostname = CStr::from_bytes_with_nul(request.key)?.to_str()?;
let hints = AddrInfoHints {
socktype: SOCK_STREAM,
..AddrInfoHints::default()
let hostent = match gethostbyname2_r(hostname.to_string(), nix::libc::AF_INET) {
Ok(hostent) => hostent,
Err(e) => bail!("error during lookup: {:?}", e)
};

let host = match getaddrinfo(Some(hostname), None, Some(hints)).map(|addrs| {
addrs
.filter_map(|r| r.ok())
.filter(|r| r.sockaddr.is_ipv4())
.map(|a| a.sockaddr.ip())
.collect::<Vec<_>>()
}) {
// no matches found
Ok(addresses) if addresses.len() == 0 => Ok(None),
Ok(addresses) => Ok(Some(Host {
addresses,
hostname: hostname.to_string(),
})),
Err(e) => match e.kind() {
dns_lookup::LookupErrorKind::NoName => Ok(None),
_ => bail!("error during lookup: {:?}", e),
},
let host = Host {
addresses: hostent.addr_list,
hostname: hostent.name
};
Ok(serialize_host(log, host))
host.serialize()
}

RequestType::GETHOSTBYNAMEv6 => {
let hostname = CStr::from_bytes_with_nul(request.key)?.to_str()?;

let hints = AddrInfoHints {
socktype: SOCK_STREAM,
address: AF_INET6, // ai_family
..AddrInfoHints::default()
let hostent = match gethostbyname2_r(hostname.to_string(), nix::libc::AF_INET6) {
Ok(hostent) => hostent,
Err(e) => bail!("error during lookup: {:?}", e)
};

let host = match getaddrinfo(Some(hostname), None, Some(hints)).map(|addrs| {
addrs
.filter_map(|r| r.ok())
.filter(|r| r.sockaddr.is_ipv6())
.map(|a| a.sockaddr.ip())
.collect::<Vec<_>>()
}) {
// No matches found
Ok(addresses) if addresses.len() == 0 => Ok(None),
Ok(addresses) => Ok(Some(Host {
addresses,
hostname: hostname.to_string(),
})),
Err(e) => match e.kind() {
dns_lookup::LookupErrorKind::NoName => Ok(None),
_ => bail!("error during lookup: {:?}", e),
},
let host = Host {
addresses: hostent.addr_list,
hostname: hostent.name
};
Ok(serialize_host(log, host))
host.serialize()
}

// These will normally send an FD pointing to the internal cache structure,
Expand Down

0 comments on commit decb9e5

Please sign in to comment.