| /* 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) |