blob: d087ca830e7f6a4920f6544ea0061c449f554c00 [file] [log] [blame]
/* Copyright (c) 2015 Google, Inc.
* Barret Rhoden <brho@cs.berkeley.edu>
* See LICENSE for details.
*
* getaddrinfo/freeaddrinfo.
*
* Supports IPv4, TCP, UDP, and maybe a couple protocols/socktypes.
*
* Doesn't handle service resolution yet, and only returns one addrinfo.
*/
#include <netdb.h>
#include <malloc.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
/* Helper, given the serv string, figures out the port and protocol. Returns 0
* on success or an EAI_ error. */
static int serv_get_portprotocol(const char *serv, unsigned long *port_ret,
int *protocol_ret, struct addrinfo *hints)
{
char *strtoul_end = 0;
unsigned long port = 0; /* uninitialized, up to the main caller */
int protocol = 0; /* any protocol, will assume TCP/UDP later */
if (serv) {
if (serv[0] == '\0')
return EAI_NONAME;
port = strtoul(serv, &strtoul_end, 0);
/* Already checked that serv != \0 */
if (*strtoul_end != '\0') {
if (hints->ai_flags & AI_NUMERICSERV)
return EAI_NONAME;
/* CS lookup */
/* TODO: get a port, maybe a protocol. If we have a
* restriction on the protocol from hints, check that
* here. Probably need to rework this a bit if we have
* multiple protocols. */
return EAI_NONAME;
}
}
*port_ret = port;
*protocol_ret = protocol;
return 0;
}
/* Helper: fills in all of the addr related info in ai for an ipv4 addr/port.
* addr is in network order. port is in host order. */
static void addrinfo_filladdr_ipv4(struct addrinfo *ai, in_addr_t addr,
uint16_t port)
{
struct sockaddr_in *sa_in4 = (struct sockaddr_in*)ai->ai_addr;
ai->ai_family = AF_INET;
ai->ai_addrlen = sizeof(struct sockaddr_in);
sa_in4->sin_family = AF_INET;
sa_in4->sin_addr.s_addr = addr;
sa_in4->sin_port = htons(port);
}
static int resolve_name_to_ipv4(const char *name, struct in_addr *hostaddr)
{
struct hostent host, *result;
char buf[1024];
int _herrno;
int ret;
ret = gethostbyname2_r(name, AF_INET, &host, buf, sizeof(buf),
&result, &_herrno);
/* TODO: deal with the herrno errors and errno errors. */
if (ret)
return -1;
assert(result == &host);
memcpy(hostaddr, host.h_addr_list[0], host.h_length);
return 0;
}
/* Given the node string, fills in the addr related info in ai: the ai_family,
* ai_addr, and ai_addrlen. The ai_addr needs the port too, though that's a big
* ugly. We only support ipv4, so there's just one of these. Returns 0 on
* success, EAI_ error o/w. */
static int node_fill_addrinfo(const char *node, struct addrinfo *ai,
uint16_t port, struct addrinfo *hints)
{
struct in_addr local_addr;
if (!node) {
/* AI_PASSIVE only matters with no node name */
if (hints->ai_flags & AI_PASSIVE) {
/* Caller wants to bind */
addrinfo_filladdr_ipv4(ai, INADDR_ANY, port);
} else {
/* Caller wants to send from localhost */
addrinfo_filladdr_ipv4(ai, INADDR_LOOPBACK, port);
}
} else {
if (inet_pton(AF_INET, node, &local_addr) == 1) {
addrinfo_filladdr_ipv4(ai, local_addr.s_addr, port);
} else {
if (hints->ai_flags & AI_NUMERICHOST)
return EAI_NONAME;
if (resolve_name_to_ipv4(node, &local_addr))
return EAI_FAIL;
addrinfo_filladdr_ipv4(ai, local_addr.s_addr, port);
}
}
return 0;
}
static int proto_to_socktype(int protocol)
{
switch (protocol) {
case IPPROTO_IP:
case IPPROTO_TCP:
return SOCK_STREAM;
case IPPROTO_ICMP:
case IPPROTO_UDP:
return SOCK_DGRAM;
case IPPROTO_RAW:
return SOCK_RAW;
}
return -1;
}
static int socktype_to_proto(int socktype)
{
switch (socktype) {
case SOCK_STREAM:
return IPPROTO_TCP;
case SOCK_DGRAM:
return IPPROTO_UDP;
case SOCK_RAW:
return IPPROTO_RAW;
}
return -1;
}
int getaddrinfo(const char *node, const char *serv,
const struct addrinfo *hints, struct addrinfo **res)
{
struct addrinfo *ai;
struct addrinfo local_hints;
local_hints.ai_socktype = 0;
local_hints.ai_protocol = 0;
local_hints.ai_family = AF_UNSPEC;
local_hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG;
unsigned long port = 0; /* AF agnostic, hope a long is enough */
int protocol = 0;
int ret;
if (hints)
local_hints = *hints;
if (!node && !serv)
return EAI_NONAME;
/* Only support IPv4 for now */
if ((local_hints.ai_family != AF_UNSPEC) &&
(local_hints.ai_family != AF_INET))
return EAI_FAMILY;
ai = malloc(sizeof(struct addrinfo));
memset(ai, 0, sizeof(struct addrinfo));
ai->ai_addr = malloc(sizeof(struct sockaddr_storage));
memset(ai->ai_addr, 0, sizeof(struct sockaddr_storage));
/* Only supporting TCP and UDP for now. If protocol is 0, later we'll
* make addrinfos for both (TODO). Likewise, we only support DGRAM or
* STREAM for socktype. */
if ((ret = serv_get_portprotocol(serv, &port, &protocol, &local_hints)))
{
freeaddrinfo(ai);
return ret;
}
if ((ret = node_fill_addrinfo(node, ai, port, &local_hints))) {
freeaddrinfo(ai);
return ret;
}
/* We have a mostly full addrinfo. Still missing ai_protocol, socktype,
* flags (already 0) and canonname (already 0).
*
* We might have restrictions on our protocol from the hints or from
* what serv specifies. If we don't have a protocol yet (== 0), that
* means we have no restrictions from serv. */
if (local_hints.ai_protocol) {
if (protocol && protocol != local_hints.ai_protocol) {
/* requested protocol wasn't available */
freeaddrinfo(ai);
return EAI_SERVICE;
}
ai->ai_protocol = local_hints.ai_protocol;
} else if (protocol) {
ai->ai_protocol = protocol;
} else if (local_hints.ai_socktype) {
ai->ai_protocol = socktype_to_proto(local_hints.ai_socktype);
} else {
/* no serv restrictions, no preferences */
ai->ai_protocol = IPPROTO_TCP;
/* TODO: consider building a second addrinfo for UDP. while
* you're at it, support IPv6 and a bunch of other options! */
}
if (ai->ai_protocol == -1) {
freeaddrinfo(ai);
return EAI_SERVICE;
}
ai->ai_socktype = proto_to_socktype(ai->ai_protocol);
if (local_hints.ai_socktype &&
(local_hints.ai_socktype != ai->ai_socktype)) {
/* they requested a socktype, but we can't handle it */
freeaddrinfo(ai);
return EAI_SOCKTYPE;
}
/* TODO: support AI_CANONNAME, only in the first ai */
*res = ai;
return 0;
}
libc_hidden_def(getaddrinfo)
void freeaddrinfo(struct addrinfo *ai)
{
struct addrinfo *next;
if (!ai)
return;
free(ai->ai_addr);
free(ai->ai_canonname);
next = ai->ai_next;
free(ai);
freeaddrinfo(next);
}
libc_hidden_def(freeaddrinfo)