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

Commit

Permalink
Implement gethostbyaddr_r
Browse files Browse the repository at this point in the history
We used to rely on the wrong operation under the hood to implement the
gethostbyaddr operation. We FFI to the proper glibc function.

This is a mixed bag. These's some good news and some bad news.

Good news: we can finally get rid of the serialize_host and its
associated hardcoded headers. These do not make much sense, they're
only present in glibc for the very edge case where we're running out
of memory. We were using them wrong here.

Bad news: the IpAddr type coming from the Rust stdlib is not really
convenient for this use case. There's no straightforward way to
extract the V6 variant into a u8 slice. Even though it's internally
encoded as a 16-bytes u8 slice… But it's not publicly exposed.

We had to come up with our own LibcIp type. That's not great.
  • Loading branch information
picnoir committed Oct 8, 2023
1 parent c175727 commit e03ecc3
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 89 deletions.
80 changes: 79 additions & 1 deletion src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ pub fn disable_internal_nscd() {
}
}

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

mod glibcffi {
use nix::libc;
extern "C" {
Expand All @@ -57,6 +62,17 @@ mod glibcffi {
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;
}
}

Expand Down Expand Up @@ -154,6 +170,53 @@ impl TryFrom<libc::hostent> for Hostent {
}
}

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 err: libc::c_int = 0;
let mut hostent_result = ptr::null_mut();
let mut buf: Vec<u8> = Vec::with_capacity(200);
let ret: i32 = 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 i8,
(buf.capacity() as size_t).try_into().unwrap(),
&mut hostent_result,
&mut err
)
};

if ret == libc::ERANGE {
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 {
Err(anyhow::anyhow!(Errno::from_i32(err)))
}
}

///
///
/// The stream is positioned at the first entry in the directory.
Expand Down Expand Up @@ -221,7 +284,22 @@ fn test_gethostbyname2_r() {
"localhost.".to_string(),
libc::AF_INET6,
);

let res = result.expect("Should resolve IPv6 localhost.");
println!("{:?}", res);
}

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

let v4test = LibcIp::V4([127,0,0,1]);
let result: Result<Hostent, anyhow::Error> = gethostbyaddr_r(v4test);
let res = result.expect("Should succeed");
println!("{:?}", res);


let v6test = LibcIp::V6([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]);
let result: Result<Hostent, anyhow::Error> = gethostbyaddr_r(v6test);
let res = result.expect("Should succeed");
println!("{:?}", res);
}
82 changes: 22 additions & 60 deletions src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,17 @@
use std::collections::HashSet;
use std::convert::TryInto;
use std::ffi::{CStr, CString};
use std::net::{IpAddr, SocketAddr};
use std::net::{IpAddr};
use std::os::unix::ffi::OsStrExt;

use anyhow::{bail, Context, Result};
use atoi::atoi;
use dns_lookup::getnameinfo;
use nix::libc::NI_NUMERICSERV;
use nix::sys::socket::AddressFamily;
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::ffi::{gethostbyname2_r, gethostbyaddr_r, LibcIp};
use crate::protocol::{AiResponse, AiResponseHeader};

use super::config::Config;
Expand Down Expand Up @@ -178,21 +176,15 @@ pub fn handle_request(
bail!("Invalid key len: {}, expected 4", key.len());
}
let address_bytes: [u8; 4] = key.try_into()?;
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],
hostname,
})),
Err(e) => match e.kind() {
dns_lookup::LookupErrorKind::NoName => Ok(None),
_ => bail!("error during lookup: {:?}", e),
},
let hostent = match gethostbyaddr_r(LibcIp::V4(address_bytes)) {
Ok(hostent) => hostent,
Err(e) => bail!("error during lookup: {:?}", e)
};
Ok(serialize_host(log, host))
let host = Host {
addresses: hostent.addr_list,
hostname: hostent.name
};
host.serialize()
}
RequestType::GETHOSTBYADDRv6 => {
let key = request.key;
Expand All @@ -201,20 +193,15 @@ pub fn handle_request(
bail!("Invalid key len: {}, expected 16", key.len());
}
let address_bytes: [u8; 16] = key.try_into()?;
let address = IpAddr::from(address_bytes);

let sock = SocketAddr::new(address, 0);
let host = match getnameinfo(&sock, NI_NUMERICSERV) {
Ok((hostname, _service)) => Ok(Some(Host {
addresses: vec![address],
hostname,
})),
Err(e) => match e.kind() {
dns_lookup::LookupErrorKind::NoName => Ok(None),
_ => bail!("error during lookup: {:?}", e),
},
let hostent = match gethostbyaddr_r (LibcIp::V6(address_bytes)){
Ok(hostent) => hostent,
Err(e) => bail!("error during lookup: {:?}", e)
};
Ok(serialize_host(log, host))
let host = Host {
addresses: hostent.addr_list,
hostname: hostent.name
};
host.serialize()
}

RequestType::GETHOSTBYNAME => {
Expand Down Expand Up @@ -533,25 +520,6 @@ fn serialize_address_info(resp: &AiResponse) -> Result<Vec<u8>> {
}
}

/// Send a gethostby{addr,name}{,v6} entry back to the client,
/// or a response indicating the lookup failed.
fn serialize_host(log: &slog::Logger, host: Result<Option<Host>>) -> Vec<u8> {
// if we didn't get an error take the inner value (Option<Host>) and then,
host.and_then(|maybe_host| {
// if it is Some(host) then serialize, else return the error.
maybe_host
.map(|h| h.serialize())
// if the "host" is None then return a default error response.
.unwrap_or_else(|| Ok(protocol::HstResponseHeader::ERRNO_HOST_NOT_FOUND.to_vec()))
})
// if after all of the above we still have to deal with an error,
// return the error response.
.unwrap_or_else(|e| {
error!(log, "parsing request"; "err" => %e);
protocol::HstResponseHeader::ERRNO_NETDB_INTERNAL.to_vec()
})
}

#[cfg(test)]
mod test {
use super::super::config::Config;
Expand Down Expand Up @@ -655,13 +623,10 @@ mod test {
key: &[127, 0, 0, 1],
};

let expected = serialize_host(
&test_logger(),
Ok(Some(Host {
let expected = (Host {
addresses: vec![IpAddr::from(Ipv4Addr::new(127, 0, 0, 1))],
hostname: "localhost".to_string(),
})),
);
}).serialize().expect("must succeed");

let output = handle_request(&test_logger(), &Config::default(), &request)
.expect("should handle request with no error");
Expand All @@ -688,13 +653,10 @@ mod test {
key: &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
};

let expected = serialize_host(
&test_logger(),
Ok(Some(Host {
let expected = (Host {
addresses: vec![IpAddr::from(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))],
hostname: "localhost".to_string(),
})),
);
}).serialize().expect("must succeed");

let output = handle_request(&test_logger(), &Config::default(), &request)
.expect("should handle request with no error");
Expand Down
28 changes: 0 additions & 28 deletions src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ pub const VERSION: i32 = 2;
/// Errors used in {Ai,Hst}ResponseHeader structs.
/// See NSCD's resolv/netdb.h for the complete list.
pub const H_ERRNO_NETDB_SUCCESS: i32 = 0;
pub const H_ERRNO_NETDB_INTERNAL: i32 = -1;
pub const H_ERRNO_HOST_NOT_FOUND: i32 = 1; // Authoritative Answer Host not found.
#[allow(dead_code)]
pub const H_ERRNO_TRY_AGAIN: i32 = 2; // Non-Authoritative Host not found
Expand Down Expand Up @@ -264,33 +263,6 @@ impl HstResponseHeader {
let p = self as *const _ as *const u8;
unsafe { std::slice::from_raw_parts(p, size_of::<Self>()) }
}

/// Return the serialized header as vector of bytes
pub fn to_vec(self) -> Vec<u8> {
self.as_slice().to_vec()
}

pub const ERRNO_HOST_NOT_FOUND: Self = Self {
version: VERSION,
found: 0,
h_name_len: 0,
h_aliases_cnt: 0,
h_addrtype: -1,
h_length: -1,
h_addr_list_cnt: 0,
error: H_ERRNO_HOST_NOT_FOUND,
};

pub const ERRNO_NETDB_INTERNAL: Self = Self {
version: VERSION,
found: 0,
h_name_len: 0,
h_aliases_cnt: 0,
h_addrtype: -1,
h_length: -1,
h_addr_list_cnt: H_ERRNO_NETDB_INTERNAL,
error: 0,
};
}

#[cfg(test)]
Expand Down

0 comments on commit e03ecc3

Please sign in to comment.