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

Implement host lookups #49

Closed
wants to merge 17 commits into from
Closed
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
23 changes: 23 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 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 All @@ -24,6 +24,7 @@ num-derive = "^0.3"
num-traits = "^0.2"
sd-notify = "^0.4"
static_assertions = "1.1.0"
dns-lookup = "1.0.8"

[dev-dependencies]
criterion = "^0.3"
Expand Down
256 changes: 255 additions & 1 deletion src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
* limitations under the License.
*/

use nix::libc;
use anyhow::bail;
use nix::libc::{self};
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 +43,254 @@ pub fn disable_internal_nscd() {
__nss_disable_nscd(do_nothing);
}
}

pub enum LibcIp {
V4([u8; 4]),
V6([u8; 16])
}

mod glibcffi {
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;

pub fn gethostbyaddr_r (
addr: *const libc::c_void,
len: libc::socklen_t,
af: libc::c_int,
ret: *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;
}
}

/// This structure is the Rust counterpart of the `libc::hostent` C
/// function the Libc hostent struct.
///
/// It's mostly used to perform the gethostbyaddr and gethostbyname
/// operations.
///
/// This struct can be serialized to the wire through the
/// `serialize` function or retrieved from the C boundary using the
/// TryFrom `libc:hostent` trait.
#[derive(Clone, Debug)]
pub struct Hostent {
pub name: String,
pub aliases: Vec<String>,
pub addr_type: i32,
pub addr_list: Vec<std::net::IpAddr>,
pub herrno: i32
}

fn from_libc_hostent(value: libc::hostent) -> Result<Hostent, anyhow::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 as *const *const libc::c_char;
while !(unsafe { *h_alias_ptr }).is_null() {
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
// h_addr_list is a pointer to a list of pointers to addresses.
// h_addr_list[0] => ptr to first address
// h_addr_list[1] => null pointer (end of list)
let mut h_addr_ptr = value.h_addr_list as *const *const libc::c_void;
while !(unsafe { *h_addr_ptr }).is_null() {
if value.h_addrtype == libc::AF_INET {
let octets: [u8; 4] =
unsafe { std::ptr::read((*h_addr_ptr) as *const [u8; 4]) };
addr_list.push(std::net::IpAddr::V4(std::net::Ipv4Addr::from(octets)));
} else {
let octets: [u8; 16] =
unsafe { std::ptr::read((*h_addr_ptr) as *const [u8; 16]) };
addr_list.push(std::net::IpAddr::V6(std::net::Ipv6Addr::from(octets)));
}
unsafe { h_addr_ptr = h_addr_ptr.add(1) };
}

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

/// Decodes the result of a gethostbyname/addr call into a `Hostent`
/// Rust struct.
///
/// This decoding algorithm is quite confusing, but that's how it's
/// implemented in Nscd and what the client Glibc expects. We
/// basically always ignore `herrno` except if the resulting
/// `libc::hostent` is set to null by glibc.
fn unmarshal_gethostbyxx(hostent: *mut libc::hostent, herrno: libc::c_int) -> anyhow::Result<Hostent> {
if !hostent.is_null() {
unsafe {
let res = from_libc_hostent(*hostent)?;
Ok(res)
}
} else {
Ok(
// This is a default hostent header we're supposed to use
// to convey a lookup error. This is a glibc quirk, I have
// nothing to do with that, don't blame me :)
Hostent {
name: "".to_string(),
aliases: Vec::new(),
addr_type: -1,
addr_list: Vec::new(),
herrno
}
)
}
}

pub fn gethostbyaddr_r(addr: LibcIp) -> anyhow::Result<Hostent> {

let (addr, len, af) = match addr {
LibcIp::V4(ref ipv4) => (ipv4 as &[u8], 4, libc::AF_INET),
LibcIp::V6(ref ipv6) => (ipv6 as &[u8], 16, libc::AF_INET6)
};

let mut ret_hostent: libc::hostent = libc::hostent {
h_name: ptr::null_mut(),
h_aliases: ptr::null_mut(),
h_addrtype: 0,
h_length: 0,
h_addr_list: ptr::null_mut(),
};
let mut herrno: libc::c_int = 0;
let mut hostent_result = ptr::null_mut();
let mut buf: Vec<u8> = Vec::with_capacity(200);
loop {
let ret = unsafe {
glibcffi::gethostbyaddr_r(
addr.as_ptr() as *const libc::c_void,
len,
af,
&mut ret_hostent,
buf.as_mut_ptr() as *mut libc::c_char,
(buf.capacity() as size_t).try_into().unwrap(),
&mut hostent_result,
&mut herrno
)
};

if ret == libc::ERANGE {
buf.reserve(buf.capacity() * 2);
} else {
break;
}
};
unmarshal_gethostbyxx(hostent_result, herrno)
}

///
///
/// 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 herrno: 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 _: i32 = loop {
let ret = unsafe {
glibcffi::gethostbyname2_r(
name.as_ptr(),
af,
&mut ret_hostent,
buf.as_mut_ptr() as *mut libc::c_char,
(buf.capacity() as size_t).try_into().unwrap(),
&mut hostent_result,
&mut herrno,
)
};
if ret == libc::ERANGE {
// The buffer is too small. Let's x2 its capacity and retry.
buf.reserve(buf.capacity() * 2);
} else {
break ret;
}
};
unmarshal_gethostbyxx(hostent_result, herrno)
}

#[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,
);
result.expect("Should resolve IPv6 localhost.");
}

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

let v4test = LibcIp::V4([127,0,0,1]);
let _ = gethostbyaddr_r(v4test).expect("Should resolve IPv4 localhost with gethostbyaddr");

let v6test = LibcIp::V6([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]);
let _ = gethostbyaddr_r(v6test).expect("Should resolve IPv6 localhost with gethostbyaddr");
}
Loading
Loading