blob: 150e0cfdad93a17ec407a1866b11cf90eb54d63a [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
#endif
#include "nacl_io/host_resolver.h"
#include <arpa/inet.h>
#include <assert.h>
#include <netinet/in.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "nacl_io/kernel_proxy.h"
#include "nacl_io/log.h"
#include "nacl_io/ossocket.h"
#include "nacl_io/pepper_interface.h"
#ifdef PROVIDES_SOCKET_API
namespace {
void HintsToPPHints(const addrinfo* hints, PP_HostResolver_Hint* pp_hints) {
memset(pp_hints, 0, sizeof(*pp_hints));
if (hints->ai_family == AF_INET)
pp_hints->family = PP_NETADDRESS_FAMILY_IPV4;
else if (hints->ai_family == AF_INET6)
pp_hints->family = PP_NETADDRESS_FAMILY_IPV6;
if (hints->ai_flags & AI_CANONNAME)
pp_hints->flags = PP_HOSTRESOLVER_FLAG_CANONNAME;
}
void CreateAddrInfo(const addrinfo* hints,
struct sockaddr* addr,
const char* name,
addrinfo** list_start,
addrinfo** list_end) {
addrinfo* ai = static_cast<addrinfo*>(malloc(sizeof(addrinfo)));
memset(ai, 0, sizeof(*ai));
if (hints && hints->ai_socktype)
ai->ai_socktype = hints->ai_socktype;
else
ai->ai_socktype = SOCK_STREAM;
if (hints && hints->ai_protocol)
ai->ai_protocol = hints->ai_protocol;
if (name)
ai->ai_canonname = strdup(name);
switch (addr->sa_family) {
case AF_INET6: {
sockaddr_in6* in =
static_cast<sockaddr_in6*>(malloc(sizeof(sockaddr_in6)));
*in = *(sockaddr_in6*)addr;
ai->ai_family = AF_INET6;
ai->ai_addr = reinterpret_cast<sockaddr*>(in);
ai->ai_addrlen = sizeof(*in);
break;
}
case AF_INET: {
sockaddr_in* in = static_cast<sockaddr_in*>(malloc(sizeof(sockaddr_in)));
*in = *(sockaddr_in*)addr;
ai->ai_family = AF_INET;
ai->ai_addr = reinterpret_cast<sockaddr*>(in);
ai->ai_addrlen = sizeof(*in);
break;
}
default:
assert(0);
return;
}
if (*list_start == NULL) {
*list_start = ai;
*list_end = ai;
return;
}
(*list_end)->ai_next = ai;
*list_end = ai;
}
} // namespace
namespace nacl_io {
HostResolver::HostResolver() : hostent_(), ppapi_(NULL) {
}
HostResolver::~HostResolver() {
hostent_cleanup();
}
void HostResolver::Init(PepperInterface* ppapi) {
ppapi_ = ppapi;
}
struct hostent* HostResolver::gethostbyname(const char* name) {
h_errno = NETDB_INTERNAL;
struct addrinfo* ai;
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_CANONNAME;
hints.ai_family = AF_INET;
int err = getaddrinfo(name, NULL, &hints, &ai);
if (err) {
switch (err) {
case EAI_SYSTEM:
h_errno = NO_RECOVERY;
break;
case EAI_NONAME:
h_errno = HOST_NOT_FOUND;
break;
default:
h_errno = NETDB_INTERNAL;
break;
}
return NULL;
}
// We use a single hostent struct for all calls to to gethostbyname
// (as explicitly permitted by the spec - gethostbyname is NOT supposed to
// be threadsafe!). However by using a lock around all the global data
// manipulation we can at least ensure that the call doesn't crash.
AUTO_LOCK(gethostbyname_lock_);
// The first thing we do is free any malloced data left over from
// the last call.
hostent_cleanup();
switch (ai->ai_family) {
case AF_INET:
hostent_.h_addrtype = AF_INET;
hostent_.h_length = sizeof(in_addr);
break;
case AF_INET6:
hostent_.h_addrtype = AF_INET6;
hostent_.h_length = sizeof(in6_addr);
break;
default:
return NULL;
}
if (ai->ai_canonname != NULL)
hostent_.h_name = strdup(ai->ai_canonname);
else
hostent_.h_name = strdup(name);
// Aliases aren't supported at the moment, so we just make an empty list.
hostent_.h_aliases = static_cast<char**>(malloc(sizeof(char*)));
if (NULL == hostent_.h_aliases)
return NULL;
hostent_.h_aliases[0] = NULL;
// Count number of address in list
int num_addresses = 0;
struct addrinfo* current = ai;
while (current != NULL) {
// Only count address that have the same type as first address
if (current->ai_family == hostent_.h_addrtype)
num_addresses++;
current = current->ai_next;
}
// Allocate address list
hostent_.h_addr_list = static_cast<char**>(calloc(num_addresses + 1,
sizeof(char*)));
if (NULL == hostent_.h_addr_list)
return NULL;
// Copy all addresses of the relevant family.
current = ai;
char** hostent_addr = hostent_.h_addr_list;
while (current != NULL) {
if (current->ai_family != hostent_.h_addrtype) {
current = current->ai_next;
continue;
}
*hostent_addr = static_cast<char*>(malloc(hostent_.h_length));
switch (current->ai_family) {
case AF_INET: {
sockaddr_in* in = reinterpret_cast<sockaddr_in*>(current->ai_addr);
memcpy(*hostent_addr, &in->sin_addr.s_addr, hostent_.h_length);
break;
}
case AF_INET6: {
sockaddr_in6* in6 = reinterpret_cast<sockaddr_in6*>(current->ai_addr);
memcpy(*hostent_addr, &in6->sin6_addr.s6_addr, hostent_.h_length);
break;
}
}
current = current->ai_next;
hostent_addr++;
}
freeaddrinfo(ai);
#if !defined(h_addr)
// Copy element zero of h_addr_list to h_addr when h_addr is not defined
// as in some libc's h_addr may be a separate member instead of a macro.
hostent_.h_addr = hostent_.h_addr_list[0];
#endif
return &hostent_;
}
void HostResolver::freeaddrinfo(struct addrinfo* res) {
while (res) {
struct addrinfo* cur = res;
res = res->ai_next;
free(cur->ai_addr);
free(cur->ai_canonname);
free(cur);
}
}
int HostResolver::getnameinfo(const struct sockaddr* sa,
socklen_t salen,
char* host,
size_t hostlen,
char* serv,
size_t servlen,
int flags) {
in_port_t port;
const void* addr;
if (host == NULL && serv == NULL) {
LOG_TRACE("host and serv are NULL.");
return EAI_NONAME;
}
// Currently we only handle numeric hosts and services.
if (flags & NI_NAMEREQD)
return EAI_NONAME;
if (sa->sa_family == AF_INET) {
if (salen < sizeof(struct sockaddr_in))
return EAI_FAMILY;
const struct sockaddr_in* sock =
reinterpret_cast<const struct sockaddr_in*>(sa);
port = sock->sin_port;
addr = &sock->sin_addr.s_addr;
} else if (sa->sa_family == AF_INET6) {
if (salen < sizeof(struct sockaddr_in6))
return EAI_FAMILY;
const struct sockaddr_in6* sock =
reinterpret_cast<const struct sockaddr_in6*>(sa);
port = sock->sin6_port;
addr = sock->sin6_addr.s6_addr;
} else {
return EAI_FAMILY;
}
if (host && inet_ntop(sa->sa_family, addr, host, hostlen) == NULL)
return EAI_OVERFLOW;
if (serv && (size_t)snprintf(serv, servlen, "%u", htons(port)) >= servlen)
return EAI_OVERFLOW;
return 0;
}
int HostResolver::getaddrinfo(const char* node,
const char* service,
const struct addrinfo* hints_in,
struct addrinfo** result) {
*result = NULL;
struct addrinfo* end = NULL;
if (node == NULL && service == NULL) {
LOG_TRACE("node and service are NULL.");
return EAI_NONAME;
}
// Check the service name (port). Currently we only handle numeric
// services.
long port = 0;
if (service != NULL) {
char* cp;
port = strtol(service, &cp, 10);
if (port >= 0 && port <= UINT16_MAX && *cp == '\0') {
port = htons(port);
} else {
LOG_TRACE("Service \"%s\" not supported.", service);
return EAI_SERVICE;
}
}
struct addrinfo default_hints;
memset(&default_hints, 0, sizeof(default_hints));
const struct addrinfo* hints = hints_in ? hints_in : &default_hints;
// Verify values passed in hints structure
switch (hints->ai_family) {
case AF_INET6:
case AF_INET:
case AF_UNSPEC:
break;
default:
LOG_TRACE("Unknown family: %d.", hints->ai_family);
return EAI_FAMILY;
}
struct sockaddr_in addr_in;
memset(&addr_in, 0, sizeof(addr_in));
addr_in.sin_family = AF_INET;
addr_in.sin_port = port;
struct sockaddr_in6 addr_in6;
memset(&addr_in6, 0, sizeof(addr_in6));
addr_in6.sin6_family = AF_INET6;
addr_in6.sin6_port = port;
if (node) {
// Handle numeric node name.
if (hints->ai_family == AF_INET || hints->ai_family == AF_UNSPEC) {
in_addr in;
if (inet_pton(AF_INET, node, &in)) {
addr_in.sin_addr = in;
CreateAddrInfo(hints, (sockaddr*)&addr_in, node, result, &end);
return 0;
}
}
if (hints->ai_family == AF_INET6 || hints->ai_family == AF_UNSPEC) {
in6_addr in6;
if (inet_pton(AF_INET6, node, &in6)) {
addr_in6.sin6_addr = in6;
CreateAddrInfo(hints, (sockaddr*)&addr_in6, node, result, &end);
return 0;
}
}
}
// Handle AI_PASSIVE (used for listening sockets, e.g. INADDR_ANY)
if (node == NULL && (hints->ai_flags & AI_PASSIVE)) {
if (hints->ai_family == AF_INET6 || hints->ai_family == AF_UNSPEC) {
const in6_addr in6addr_any = IN6ADDR_ANY_INIT;
memcpy(&addr_in6.sin6_addr.s6_addr, &in6addr_any, sizeof(in6addr_any));
CreateAddrInfo(hints, (sockaddr*)&addr_in6, NULL, result, &end);
}
if (hints->ai_family == AF_INET || hints->ai_family == AF_UNSPEC) {
addr_in.sin_addr.s_addr = INADDR_ANY;
CreateAddrInfo(hints, (sockaddr*)&addr_in, NULL, result, &end);
}
return 0;
}
if (NULL == ppapi_) {
LOG_ERROR("ppapi_ is NULL.");
return EAI_SYSTEM;
}
// Use PPAPI interface to resolve nodename
HostResolverInterface* resolver_iface = ppapi_->GetHostResolverInterface();
VarInterface* var_iface = ppapi_->GetVarInterface();
NetAddressInterface* netaddr_iface = ppapi_->GetNetAddressInterface();
if (!(resolver_iface && var_iface && netaddr_iface)) {
LOG_ERROR("Got NULL interface(s): %s%s%s",
resolver_iface ? "" : "HostResolver ",
var_iface ? "" : "Var ",
netaddr_iface ? "" : "NetAddress");
return EAI_SYSTEM;
}
ScopedResource scoped_resolver(ppapi_,
resolver_iface->Create(ppapi_->GetInstance()));
PP_Resource resolver = scoped_resolver.pp_resource();
struct PP_HostResolver_Hint pp_hints;
HintsToPPHints(hints, &pp_hints);
int err = resolver_iface->Resolve(resolver,
node,
0,
&pp_hints,
PP_BlockUntilComplete());
if (err) {
switch (err) {
case PP_ERROR_NOACCESS:
return EAI_SYSTEM;
case PP_ERROR_NAME_NOT_RESOLVED:
return EAI_NONAME;
default:
return EAI_SYSTEM;
}
}
char* canon_name = NULL;
if (hints->ai_flags & AI_CANONNAME) {
PP_Var name_var = resolver_iface->GetCanonicalName(resolver);
if (PP_VARTYPE_STRING == name_var.type) {
uint32_t len = 0;
const char* tmp = var_iface->VarToUtf8(name_var, &len);
// For some reason GetCanonicalName alway returns an empty
// string so this condition is never true.
// TODO(sbc): investigate this issue with PPAPI team.
if (len > 0) {
// Copy and NULL-terminate the UTF8 string var.
canon_name = static_cast<char*>(malloc(len + 1));
strncpy(canon_name, tmp, len);
canon_name[len] = '\0';
}
}
if (!canon_name)
canon_name = strdup(node);
var_iface->Release(name_var);
}
int num_addresses = resolver_iface->GetNetAddressCount(resolver);
if (0 == num_addresses)
return EAI_NODATA;
// Convert address to sockaddr struct.
for (int i = 0; i < num_addresses; i++) {
ScopedResource addr(ppapi_, resolver_iface->GetNetAddress(resolver, i));
PP_Resource resource = addr.pp_resource();
assert(resource != 0);
assert(PP_ToBool(netaddr_iface->IsNetAddress(resource)));
struct sockaddr* sockaddr = NULL;
switch (netaddr_iface->GetFamily(resource)) {
case PP_NETADDRESS_FAMILY_IPV4: {
struct PP_NetAddress_IPv4 pp_addr;
if (!netaddr_iface->DescribeAsIPv4Address(resource, &pp_addr)) {
assert(false);
break;
}
memcpy(&addr_in.sin_addr.s_addr, pp_addr.addr, sizeof(in_addr_t));
sockaddr = (struct sockaddr*)&addr_in;
break;
}
case PP_NETADDRESS_FAMILY_IPV6: {
struct PP_NetAddress_IPv6 pp_addr;
if (!netaddr_iface->DescribeAsIPv6Address(resource, &pp_addr)) {
assert(false);
break;
}
memcpy(&addr_in6.sin6_addr.s6_addr, pp_addr.addr, sizeof(in6_addr));
sockaddr = (struct sockaddr*)&addr_in6;
break;
}
default:
return EAI_SYSTEM;
}
if (sockaddr != NULL)
CreateAddrInfo(hints, sockaddr, canon_name, result, &end);
if (canon_name) {
free(canon_name);
canon_name = NULL;
}
}
return 0;
}
// Frees all of the deep pointers in a hostent struct. Called between uses of
// gethostbyname, and when the kernel_proxy object is destroyed.
void HostResolver::hostent_cleanup() {
if (NULL != hostent_.h_name) {
free(hostent_.h_name);
}
if (NULL != hostent_.h_aliases) {
for (int i = 0; NULL != hostent_.h_aliases[i]; i++) {
free(hostent_.h_aliases[i]);
}
free(hostent_.h_aliases);
}
if (NULL != hostent_.h_addr_list) {
for (int i = 0; NULL != hostent_.h_addr_list[i]; i++) {
free(hostent_.h_addr_list[i]);
}
free(hostent_.h_addr_list);
}
hostent_.h_name = NULL;
hostent_.h_aliases = NULL;
hostent_.h_addr_list = NULL;
#if !defined(h_addr)
// Initialize h_addr separately in the case where it is not a macro.
hostent_.h_addr = NULL;
#endif
}
} // namespace nacl_io
#endif // PROVIDES_SOCKET_API