323 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			323 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;
 | |
| }
 | 
