minix/lib/ip/addrinfo.c

322 lines
6.9 KiB
C
Executable file

#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
/*
* getaddrinfo and freeaddrinfo are based on
* http://www.opengroup.org/onlinepubs/009695399/functions/getaddrinfo.html
*/
void freeaddrinfo(struct addrinfo *ai)
{
struct addrinfo *next;
while (ai)
{
/* preserve next pointer */
next = ai->ai_next;
/* free each member of the struct and the struct itself */
if (ai->ai_addr) free(ai->ai_addr);
if (ai->ai_canonname) free(ai->ai_canonname);
free(ai);
/* continue to free the next element of the linked list */
ai = next;
}
}
static int getaddrinfo_parse_hints(
const struct addrinfo *hints,
int *flags,
int *socktype,
int *protocol)
{
assert(flags);
assert(socktype);
/*
* if hints is not specified, no flags are specified and all
* socktypes must be returned
*/
if (!hints)
{
*flags = 0;
*socktype = 0;
*protocol = 0;
return 0;
}
/* check hints correctness */
if (hints->ai_addrlen || hints->ai_addr ||
hints->ai_canonname || hints->ai_next)
{
errno = EINVAL;
return EAI_SYSTEM;
}
/* check flags */
*flags = hints->ai_flags;
if (*flags & ~(AI_PASSIVE | AI_CANONNAME |
AI_NUMERICHOST | AI_NUMERICSERV))
return EAI_BADFLAGS;
/* only support IPv4 */
if (hints->ai_family != AF_UNSPEC && hints->ai_family != AF_INET)
return EAI_FAMILY;
/* only support SOCK_STREAM and SOCK_DGRAM */
*socktype = hints->ai_socktype;
switch (*socktype)
{
case 0:
case SOCK_STREAM:
case SOCK_DGRAM: break;
default: return EAI_SOCKTYPE;
}
/* get protocol */
*protocol = hints->ai_protocol;
return 0;
}
static int getaddrinfo_resolve_hostname(
const char *nodename,
int flags,
char ***addr_list,
const char **canonname)
{
static struct in_addr addr;
static char *addr_array[2];
struct hostent *hostent;
assert(addr_list);
assert(canonname);
/* if no hostname is specified, use local address */
if (!nodename)
{
if ((flags & AI_PASSIVE) == AI_PASSIVE)
addr.s_addr = htonl(INADDR_ANY);
else
addr.s_addr = htonl(INADDR_LOOPBACK);
addr_array[0] = (char *) &addr;
addr_array[1] = NULL;
*addr_list = addr_array;
*canonname = "localhost";
return 0;
}
if (!*nodename)
return EAI_NONAME;
/* convert literal IP address */
addr.s_addr = inet_addr(nodename);
if (addr.s_addr != (in_addr_t) -1)
{
addr_array[0] = (char *) &addr;
addr_array[1] = NULL;
*addr_list = addr_array;
*canonname = NULL;
return 0;
}
/* AI_NUMERICHOST avoids DNS lookup */
if ((flags & AI_NUMERICHOST) == AI_NUMERICHOST)
return EAI_NONAME;
/* attempt DNS lookup */
hostent = gethostbyname(nodename);
if (!hostent)
switch(h_errno)
{
case HOST_NOT_FOUND: return EAI_NONAME;
case NO_DATA: return EAI_FAIL;
case NO_RECOVERY: return EAI_FAIL;
case TRY_AGAIN: return EAI_AGAIN;
default: assert(0); return EAI_FAIL;
}
/* assumption: only IPv4 addresses returned */
assert(hostent->h_addrtype == AF_INET);
assert(hostent->h_length == sizeof(addr));
*addr_list = hostent->h_addr_list;
*canonname = hostent->h_name;
return 0;
}
int getaddrinfo_resolve_servname(
const char *servname,
int flags,
int socktype,
unsigned short *port,
int *protocol)
{
char *endptr;
long port_long;
struct protoent *protoent;
struct servent *servent;
assert(port);
assert(protocol);
/* if not specified, set port and protocol to zero */
if (!servname)
{
*port = 0;
*protocol = 0;
return 0;
}
if (!*servname)
return EAI_SERVICE;
/* try to parse port number */
port_long = strtol(servname, &endptr, 0);
if (!*endptr)
{
/* check whether port is within range */
if (port_long < 0 || port_long > (unsigned short) ~0)
return EAI_SERVICE;
*port = htons(port_long);
*protocol = 0;
return 0;
}
/* AI_NUMERICSERV avoids lookup */
if ((flags & AI_NUMERICSERV) == AI_NUMERICSERV)
return EAI_SERVICE;
/* look up port number */
servent = getservbyname(servname,
(socktype == SOCK_STREAM) ? "tcp" : "udp");
if (!servent)
return EAI_SERVICE;
*port = servent->s_port;
/* determine protocol number */
protoent = getprotobyname(servent->s_proto);
*protocol = protoent ? protoent->p_proto : 0;
return 0;
}
int getaddrinfo(
const char *nodename,
const char *servname,
const struct addrinfo *hints,
struct addrinfo **res)
{
struct addrinfo *addrinfo, **addrinfo_p;
char **addr_list;
const char *canonname;
int flags, i, protocol, protocol_spec, r, result;
unsigned short port;
struct sockaddr_in *sockaddr;
int socktype, socktype_spec;
/*
* The following flags are supported:
* - AI_CANONNAME
* - AI_PASSIVE
* - AI_NUMERICHOST
* - AI_NUMERICSERV
*
* The following flags not supported due to lack of IPv6 support:
* - AI_ADDRCONFIG
* - AI_ALL
* - AI_V4MAPPED
*/
/* check arguments */
if ((!nodename && !servname) || !res)
return EAI_NONAME;
/* parse hints */
if ((r = getaddrinfo_parse_hints(hints, &flags, &socktype_spec, &protocol_spec)))
return r;
/* resolve hostname */
if ((r = getaddrinfo_resolve_hostname(nodename, flags, &addr_list, &canonname)))
return r;
/* return a result record for each address */
addrinfo_p = res;
*addrinfo_p = NULL;
result = EAI_NONAME;
while (*addr_list)
{
/* return a result record for each socktype */
for (i = 0; i < 2; i++)
{
/* should current socktype be selected? */
socktype = (i == 0) ? SOCK_STREAM : SOCK_DGRAM;
if (socktype_spec != 0 && socktype_spec != socktype)
continue;
/* resolve port */
if ((r = getaddrinfo_resolve_servname(servname, flags, socktype, &port, &protocol)))
{
freeaddrinfo(*res);
return r;
}
/* enforce matching protocol */
if (!protocol)
protocol = protocol_spec;
else if (protocol_spec && protocol != protocol_spec)
continue;
/* allocate result */
*addrinfo_p = addrinfo = (struct addrinfo *) calloc(1, sizeof(struct addrinfo));
if (!addrinfo)
{
freeaddrinfo(*res);
return EAI_MEMORY;
}
sockaddr = (struct sockaddr_in *) calloc(1, sizeof(struct sockaddr_in));
if (!sockaddr)
{
freeaddrinfo(*res);
return EAI_MEMORY;
}
/* provide information in result */
addrinfo->ai_family = AF_INET;
addrinfo->ai_socktype = socktype;
addrinfo->ai_protocol = protocol;
addrinfo->ai_addrlen = sizeof(*sockaddr);
addrinfo->ai_addr = (struct sockaddr *) sockaddr;
sockaddr->sin_family = AF_INET;
sockaddr->sin_port = port;
memcpy(&sockaddr->sin_addr, *addr_list, sizeof(sockaddr->sin_addr));
if (((flags & AI_CANONNAME) == AI_CANONNAME) && canonname)
{
addrinfo->ai_canonname = strdup(canonname);
if (!addrinfo->ai_canonname)
{
freeaddrinfo(*res);
return EAI_MEMORY;
}
}
result = 0;
/* chain next result to the current one */
addrinfo_p = &addrinfo->ai_next;
}
/* move on to next address */
addr_list++;
}
/* found anything meaningful? */
return result;
}