From a5a2073680f50b545cce2170330e06504de683c7 Mon Sep 17 00:00:00 2001 From: Erik van der Kouwe Date: Thu, 21 Jan 2010 06:38:17 +0000 Subject: [PATCH] create the getaddrinfo and getnameinfo library functions and friends --- commands/dhcpd/dhcpd.c | 18 +- commands/simple/tcpstat.c | 1 + commands/simple/udpstat.c | 1 + commands/urlget/net.h | 3 + include/arpa/inet.h | 5 + include/net/gen/netdb.h | 70 +++++ lib/ip/Makefile.in | 3 + lib/ip/addrinfo.c | 322 ++++++++++++++++++++++ lib/ip/gai_strerror.c | 64 +++++ lib/ip/gethnmadr.c | 4 - lib/ip/nameinfo.c | 148 ++++++++++ man/man3/getaddrinfo.3 | 66 +++++ test/Makefile | 3 +- test/run | 2 +- test/test48.c | 566 ++++++++++++++++++++++++++++++++++++++ 15 files changed, 1262 insertions(+), 14 deletions(-) create mode 100755 lib/ip/addrinfo.c create mode 100755 lib/ip/gai_strerror.c create mode 100755 lib/ip/nameinfo.c create mode 100755 man/man3/getaddrinfo.3 create mode 100755 test/test48.c diff --git a/commands/dhcpd/dhcpd.c b/commands/dhcpd/dhcpd.c index 129d9e716..05b315a41 100644 --- a/commands/dhcpd/dhcpd.c +++ b/commands/dhcpd/dhcpd.c @@ -386,7 +386,7 @@ static udpport_t portbyname(const char *name) return se->s_port; } -static int send(network_t *np, void *data, size_t len) +static int sendpacket(network_t *np, void *data, size_t len) { /* Send out a packet using a filedescriptor that is probably in async mode, * so first dup() a sync version, then write. Return true on success. @@ -957,7 +957,8 @@ int main(int argc, char **argv) if (!(np->flags & NF_BOUND)) { /* Rebind over Ethernet. */ udp2ether(bp, np); - if (send(np, bp->eth, sizeof(eth_hdr_t) + sizeof(ip_hdr_t) + if (sendpacket(np, bp->eth, + sizeof(eth_hdr_t) + sizeof(ip_hdr_t) + sizeof(udp_hdr_t) + sizeof(dhcp_t))) { if (debug >= 1) { printf("%s: Broadcast DHCP %s\n", @@ -967,7 +968,7 @@ int main(int argc, char **argv) } } else { /* Renew over UDP. */ - if (send(np, bp->udpio, sizeof(udp_io_hdr_t) + if (sendpacket(np, bp->udpio, sizeof(udp_io_hdr_t) + sizeof(dhcp_t))) { if (debug >= 1) { printf("%s: Sent DHCP %s to %s\n", @@ -1123,7 +1124,7 @@ int main(int argc, char **argv) * is in use already. */ make_arp(bp, np); - if (send(np, bp->eth, sizeof(arp46_t))) { + if (sendpacket(np, bp->eth, sizeof(arp46_t))) { if (debug >= 2) { printf("Sent ARP for %s\n", inet_ntoa(np->ip)); } @@ -1191,7 +1192,8 @@ int main(int argc, char **argv) bp->udpio->uih_data_len= sizeof(dhcp_t); udp2ether(bp, np); - if (send(np, bp->eth, sizeof(eth_hdr_t) + sizeof(ip_hdr_t) + if (sendpacket(np, bp->eth, + sizeof(eth_hdr_t) + sizeof(ip_hdr_t) + sizeof(udp_hdr_t) + sizeof(dhcp_t))) { if (debug >= 1) { printf("%s: Broadcast DHCP %s\n", @@ -1255,7 +1257,7 @@ int main(int argc, char **argv) * by DHCP to my own interface. */ icmp_advert(bp, np); - if (send(np, bp->ip, sizeof(ip_hdr_t) + 16)) { + if (sendpacket(np, bp->ip, sizeof(ip_hdr_t) + 16)) { if (debug >= 2) { printf("%s: Sent advert for %s to self\n", np->fdp->device, inet_ntoa(np->gateway)); @@ -1267,7 +1269,7 @@ int main(int argc, char **argv) if (np->sol_ct >= 0 && --np->sol_ct >= 0) { /* Send a router solicitation. */ icmp_solicit(bp); - if (send(np, bp->ip, sizeof(*bp->ip) + 8)) { + if (sendpacket(np, bp->ip, sizeof(*bp->ip) + 8)) { if (debug >= 2) { printf("%s: Broadcast router solicitation\n", np->fdp->device); @@ -1365,7 +1367,7 @@ int main(int argc, char **argv) /* Can we do something with this DHCP packet? */ if ((r= servdhcp(np, bp, r)) > 0) { /* Yes, we have something to send somewhere. */ - if (send(np, bp->udpio, r)) { + if (sendpacket(np, bp->udpio, r)) { if (debug >= 1) { printf("%s: Sent DHCP packet to %s\n", np->fdp->device, diff --git a/commands/simple/tcpstat.c b/commands/simple/tcpstat.c index 2d2a8f235..0afc85233 100644 --- a/commands/simple/tcpstat.c +++ b/commands/simple/tcpstat.c @@ -9,6 +9,7 @@ Created: June 1995 by Philip Homburg #include #undef printf +#undef send #include #include diff --git a/commands/simple/udpstat.c b/commands/simple/udpstat.c index b75484ac3..de26c1282 100644 --- a/commands/simple/udpstat.c +++ b/commands/simple/udpstat.c @@ -9,6 +9,7 @@ Created: March 2001 by Philip Homburg #include #undef printf +#undef send #include #include diff --git a/commands/urlget/net.h b/commands/urlget/net.h index 08e1ac6fd..db9f474da 100644 --- a/commands/urlget/net.h +++ b/commands/urlget/net.h @@ -1,4 +1,7 @@ /* net.h Copyright 2000 by Michael Temari All Rights Reserved */ /* 04/05/2000 Michael Temari */ +/* avoid clash with POSIX connect */ +#define connect _connect _PROTOTYPE(int connect, (char *host, int port)); + diff --git a/include/arpa/inet.h b/include/arpa/inet.h index 4ae82935e..e02459602 100644 --- a/include/arpa/inet.h +++ b/include/arpa/inet.h @@ -35,4 +35,9 @@ _PROTOTYPE( uint16_t ntohs, (uint16_t _netval) ); _PROTOTYPE( int inet_aton, (const char *_cp, struct in_addr *_pin) ); #endif +#ifdef _POSIX_SOURCE +in_addr_t inet_addr(const char *cp); +#endif + #endif /* _ARPA__INET_H */ + diff --git a/include/net/gen/netdb.h b/include/net/gen/netdb.h index 55585e528..b7a8b9f9f 100644 --- a/include/net/gen/netdb.h +++ b/include/net/gen/netdb.h @@ -125,4 +125,74 @@ int servxcheck _ARGS((unsigned long _peer, const char *_service, char *servxfile _ARGS((const char *_file)); #endif +/* + * The definitions below are based on + * http://www.opengroup.org/onlinepubs/009695399/basedefs/netdb.h.html + */ + +#ifdef _POSIX_SOURCE + +/* headers exposed by netdb.h */ +#include +#include +#include + +/* struct for use with getaddrinfo() */ +struct addrinfo +{ + int ai_flags; /* Input flags */ + int ai_family; /* Address family of socket */ + int ai_socktype; /* Socket type */ + int ai_protocol; /* Protocol of socket */ + socklen_t ai_addrlen; /* Length of socket address */ + struct sockaddr *ai_addr; /* Socket address of socket */ + char *ai_canonname; /* Canonical name of service location */ + struct addrinfo *ai_next; /* Pointer to next in list */ +}; + +/* values for struct addrinfo.ai_flags */ +#define AI_PASSIVE 0x00000001 +#define AI_CANONNAME 0x00000002 +#define AI_NUMERICHOST 0x00000004 +#define AI_NUMERICSERV 0x00000008 +/* +#define AI_V4MAPPED 0x00000010 +#define AI_ALL 0x00000020 +#define AI_ADDRCONFIG 0x00000040 +*/ + +/* flags for getnameinfo() */ +/* #define NI_NOFQDN 0x00000001 */ +#define NI_NUMERICHOST 0x00000002 +#define NI_NAMEREQD 0x00000004 +#define NI_NUMERICSERV 0x00000008 +/* #define NI_NUMERICSCOPE 0x00000010 */ +#define NI_DGRAM 0x00000020 + +/* error values for getaddrinfo() and getnameinfo() */ +#define EAI_AGAIN 1 +#define EAI_BADFLAGS 2 +#define EAI_FAIL 3 +#define EAI_FAMILY 4 +#define EAI_MEMORY 5 +#define EAI_NONAME 6 +#define EAI_SERVICE 7 +#define EAI_SOCKTYPE 8 +#define EAI_SYSTEM 9 +#define EAI_OVERFLOW 10 + +/* getaddrinfo() and friends */ +void freeaddrinfo(struct addrinfo *ai); +int getaddrinfo(const char *nodename, + const char *servname, + const struct addrinfo *hints, + struct addrinfo **res); +int getnameinfo(const struct sockaddr *sa, socklen_t salen, + char *node, socklen_t nodelen, char *service, + socklen_t servicelen, int flags); +const char *gai_strerror(int ecode); + +#endif + #endif /* !_NETDB_H_ */ + diff --git a/lib/ip/Makefile.in b/lib/ip/Makefile.in index 784b8fe29..fdf3a18b4 100644 --- a/lib/ip/Makefile.in +++ b/lib/ip/Makefile.in @@ -8,6 +8,7 @@ LIBRARIES=libc libc_FILES=" \ accept.c \ + addrinfo.c \ bind.c \ connect.c \ dhcp_gettag.c \ @@ -18,6 +19,7 @@ libc_FILES=" \ ethere2a.c \ etherh2n.c \ ethern2h.c \ + gai_strerror.c \ getdomain.c \ gethnmadr.c \ gethostent.c \ @@ -41,6 +43,7 @@ libc_FILES=" \ inet_ntoa.c \ listen.c \ memcspn.c \ + nameinfo.c \ oneC_sum.c \ rcmd.c \ recv.c \ diff --git a/lib/ip/addrinfo.c b/lib/ip/addrinfo.c new file mode 100755 index 000000000..25babc175 --- /dev/null +++ b/lib/ip/addrinfo.c @@ -0,0 +1,322 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 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; +} diff --git a/lib/ip/gai_strerror.c b/lib/ip/gai_strerror.c new file mode 100755 index 000000000..ccb0acfa5 --- /dev/null +++ b/lib/ip/gai_strerror.c @@ -0,0 +1,64 @@ +#include +#include +#include +#include + +/* + * gai_strerror is based on + * http://www.opengroup.org/onlinepubs/009695399/functions/gai_strerror.html + */ +const char *gai_strerror(int ecode) +{ + static char buffer[256]; + + /* check for each known error code */ + switch (ecode) + { + case EAI_AGAIN: + return "The name could not be resolved at this time"; + + case EAI_BADFLAGS: + return "The flags had an invalid value"; + + case EAI_FAIL: + return "A non-recoverable error occurred"; + + case EAI_FAMILY: + return "The address family was not recognized or the " + "address length was invalid for the specified " + "family"; + + case EAI_MEMORY: + return "There was a memory allocation failure"; + + case EAI_NONAME: + return "The name does not resolve for the supplied " + "parameters, NI_NAMEREQD is set and the host's " + "name cannot be located, or both nodename and " + "servname were null"; + + case EAI_SERVICE: + return "The service passed was not recognized for the " + "specified socket type"; + + case EAI_SOCKTYPE: + return "The intended socket type was not recognized"; + + case EAI_SYSTEM: + snprintf(buffer, + sizeof(buffer), + "A system error occurred: %s", + strerror(errno)); + return buffer; + + case EAI_OVERFLOW: + return "An argument buffer overflowed"; + } + + /* unknown error code */ + snprintf(buffer, + sizeof(buffer), + "An unknown error code was passed to gai_strerror: %d", + ecode); + return buffer; +} diff --git a/lib/ip/gethnmadr.c b/lib/ip/gethnmadr.c index 5f14875ac..b592a9e5b 100644 --- a/lib/ip/gethnmadr.c +++ b/lib/ip/gethnmadr.c @@ -54,10 +54,6 @@ static char sccsid[] = "@(#)gethostnamadr.c 6.41 (Berkeley) 6/1/90"; static char *h_addr_ptrs[MAXADDRS + 1]; #ifdef _MINIX -struct in_addr -{ - ipaddr_t s_addr; -}; union querybuf; extern int dn_skipname _ARGS(( const u_char *comp_dn, const u_char *eom )); diff --git a/lib/ip/nameinfo.c b/lib/ip/nameinfo.c new file mode 100755 index 000000000..3444c291f --- /dev/null +++ b/lib/ip/nameinfo.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +static size_t first_component_len(const char *s) +{ + const char *first = s; + + /* find the first dot or end of string */ + while (*s && *s != '.') + s++; + + /* return length */ + return s - first; +} + +static int getnameinfo_get_host(const struct sockaddr_in *sockaddr, + char *node, socklen_t nodelen, int flags) +{ + struct hostent *hostent; + const char *ipaddr; + + /* perform look-up */ + if ((flags & NI_NUMERICHOST) != NI_NUMERICHOST) + { + hostent = gethostbyaddr( + (char *) &sockaddr->sin_addr, + sizeof(sockaddr->sin_addr), + AF_INET); + + if (hostent && hostent->h_name) + { + /* return the hostname that was found */ + if (nodelen <= strlen(hostent->h_name)) + return EAI_OVERFLOW; + + strcpy(node, hostent->h_name); + return 0; + } + } + + if ((flags & NI_NAMEREQD) == NI_NAMEREQD) + return EAI_NONAME; + + /* basic implementation to provide numeric values */ + ipaddr = inet_ntoa(sockaddr->sin_addr); + if (nodelen <= strlen(ipaddr)) + return EAI_OVERFLOW; + + strcpy(node, ipaddr); + return 0; +} + +static int getnameinfo_get_serv(const struct sockaddr_in *sockaddr, + char *service, socklen_t servicelen, int flags) +{ + struct servent *servent; + unsigned short port; + + /* perform look-up */ + if ((flags & NI_NUMERICSERV) != NI_NUMERICSERV) + { + servent = getservbyport(sockaddr->sin_port, + ((flags & NI_DGRAM) == NI_DGRAM) ? "udp" : "tcp"); + if (servent && servent->s_name) + { + /* return the service name that was found */ + if (strlen(servent->s_name) >= servicelen) + return EAI_OVERFLOW; + + strcpy(service, servent->s_name); + return 0; + } + } + + /* return port number */ + port = ntohs(sockaddr->sin_port); + if (snprintf(service, servicelen, "%u", port) >= servicelen) + return EAI_OVERFLOW; + + return 0; +} + +/* + * getnameinfo is based on + * http://www.opengroup.org/onlinepubs/009695399/functions/getnameinfo.html + */ +int getnameinfo(const struct sockaddr *sa, socklen_t salen, + char *node, socklen_t nodelen, char *service, + socklen_t servicelen, int flags) +{ + int r; + const struct sockaddr_in *sockaddr; + + /* + * The following flags are really supported: + * - NI_NUMERICHOST + * - NI_NAMEREQD + * - NI_NUMERICSERV + * - NI_DGRAM + * + * The following flag is not supported: + * - NI_NUMERICSCOPE + * + * The following flags could have been supported but is not implemented: + * - NI_NOFQDN + */ + + /* check for invalid parameters; only support IPv4 */ + if (sa == NULL) + { + errno = EINVAL; + return EAI_SYSTEM; + } + + if (sa->sa_family != AF_INET || salen != sizeof(struct sockaddr_in)) + return EAI_FAMILY; + + if (flags & ~(NI_NUMERICHOST | NI_NAMEREQD | NI_NUMERICSERV | NI_DGRAM)) + return EAI_BADFLAGS; + + if ((!node || !nodelen) && (!service || !servicelen)) + return EAI_NONAME; + + /* look up host */ + sockaddr = (const struct sockaddr_in *) sa; + if (node && nodelen > 0) + { + r = getnameinfo_get_host(sockaddr, node, nodelen, flags); + if (r) + return r; + } + + /* look up service */ + if (service && servicelen > 0) + { + r = getnameinfo_get_serv(sockaddr, service, servicelen, flags); + if (r) + return r; + } + + return 0; +} diff --git a/man/man3/getaddrinfo.3 b/man/man3/getaddrinfo.3 new file mode 100755 index 000000000..3dc14ddce --- /dev/null +++ b/man/man3/getaddrinfo.3 @@ -0,0 +1,66 @@ +.TH GETADDRINFO 3 "January 19, 2010" +.UC 4 +.SH NAME +getaddrinfo, freeaddrinfo, gai_strerror, getnameinfo \- address information +.SH SYNOPSIS +.nf +.ft B +#include + +void freeaddrinfo(struct addrinfo *\fIai\fP); +int getaddrinfo(const char *\fInodename\fP, + const char *\fIservname\fP, + const struct addrinfo *\fIhints\fP, + struct addrinfo **\fIres\fP); +int getnameinfo(const struct sockaddr *\fIsa\fP, socklen_t \fIsalen\fP, + char *\fInode\fP, socklen_t \fInodelen\fP, char *\fIservice\fP, + socklen_t \fIservicelen\fP, int \fIflags\fP); +const char *gai_strerror(int \fIecode\fP); +.fi +.SH DESCRIPTION +These functions provide to convert between host and service names on the one +hand and network addresses and ports on the other. +.PP +getaddrinfo converts the hostname specified in \fInodename\fP and/or the service +name in \fIservname\fP into a socket address that can be used to connect to that +service or listen for incoming connections. One of the parameters may be NULL. +If \fInodename\fP is NULL, an address for the local host is provided. If +\fIservname\fP is NULL, the network port is not filled in (and therefore set to +zero). Buffers are allocated to store the results and a pointer to the first +element of a linked list of addresses is stored in \fIai\fP. These buffers must +be freed by calling freeaddrinfo on the pointer returned in \fIai\fP. +.PP +getnameinfo converts the specified socket address into a hostname and/or service +name. These are stored in the buffers pointed to by \fInode\fP and \fIservice\fP +resepectively. \fInodelen\fP and \fIservicelen\fP specify the sizes of these +buffers. If the result string including null terminator does not fit in the +buffer, the function fails and EAI_OVERFLOW is returned. +.PP +For both functions, some flags may be specified to alter their behaviour. For +getaddrinfo these flags are specified in the ai_flags field of the optional +\fIhints\fP parameter. AI_PASSIVE indicates that an address suitable for the +bind function should be returned, otherwise an address suitable for connect is +provided. AI_CANONNAME requests that the canonical name of the host be retrieved +and stored in the ai_canonname field of the result. AI_NUMERICHOST and +AI_NUMERICSERV indicate respectively that \fInodename\fP is an IP address and +\fIservname\fP is a port number, avoiding a lookup. Search can be limited to a +specific socket type by setting \fIhints\fP\->ai_socktype to SOCK_STREAM or +SOCK_DGRAM. \fIhints\fP\->ai_family can be set to AF_UNSPEC or AF_INET but this +doesn't make a difference as MINIX supports only IPv4. Unused fields of +\fIhints\fP must be set to zero. +.PP +Flags for getnameinfo are specified in the \fIflags\fP parameter. NI_NUMERICHOST +and NI_NUMERICSERV respectively cause \fInode\fP to be set to an IP address +and \fIservice\fP to be set to a port number. NI_NAMEREQD causes the function +to fail with EAI_NONAME if a name is not found for the host (otherwise the IP +address is returned). NI_DGRAM indicates that a datagram service is specified, +the default being a stream service. +.SH "RETURN VALUE +If the functions succeed, they return 0. If they fail, one of the EAI_* values +defined in netdb.h is returned. This value can be converted into a string using +gai_strerror. The most important ones are EAI_NONAME (host name not found), +EAI_SERVICE (service name not found) and EAI_OVERFLOW (buffer too small, only +for getnameinfo). +.SH "KNOWN ISSUES +Since MINIX does not support IPv6, the related flags are not supported. +The NI_NOFQDN flag is also not (yet) supported. diff --git a/test/Makefile b/test/Makefile index b74829891..80280aa58 100644 --- a/test/Makefile +++ b/test/Makefile @@ -10,7 +10,7 @@ OBJ= test1 test2 test3 test4 test5 test6 test7 test8 test9 \ test21 test22 test23 test25 test26 test27 test28 test29 \ test30 test31 test32 test34 test35 test36 test37 test38 \ test39 t10a t11a t11b test40 t40a t40b t40c t40d t40e t40f test41 \ - test42 test44 test45 test47 + test42 test44 test45 test47 test48 BIGOBJ= test20 test24 ROOTOBJ= test11 test33 test43 test46 @@ -95,4 +95,5 @@ test45: test45.c test45.h test45-gcc: test45.c test45.h test46: test46.c test47: test47.c +test48: test48.c diff --git a/test/run b/test/run index 1455f3b75..a4dabe1ae 100755 --- a/test/run +++ b/test/run @@ -19,7 +19,7 @@ echo " " # Run all the tests, keeping track of who failed. for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 \ 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 \ - 41 42 43 44 45 45-gcc 46 47 sh1.sh sh2.sh + 41 42 43 44 45 45-gcc 46 47 48 sh1.sh sh2.sh do if [ -x ./test$i ] then diff --git a/test/test48.c b/test/test48.c new file mode 100755 index 000000000..9825c5b92 --- /dev/null +++ b/test/test48.c @@ -0,0 +1,566 @@ +#include +#include +#include +#include +#include +#include +#include + +#define MAX_ERRORS 8 + +static int errct; + +static void quit(void) +{ + if (errct > 0) + { + printf("%d errors\n", errct); + exit(1); + } + else + { + printf("ok\n"); + exit(0); + } +} + +static void err(void) +{ + if (++errct >= MAX_ERRORS) + { + printf("aborted, too many errors\n"); + quit(); + } +} + +static void printstr(const char *s) +{ + if (s) + printf("\"%s\"", s); + else + printf("NULL"); +} + +static void test_getaddrinfo_err( + int n, + const char *nodename, + const char *servname, + int passhints, + int flags, + int family, + int socktype, + const char *exp_result, + const char *result) +{ + printf("error %d: getaddrinfo(", n); + printstr(nodename); + printf(", "); + printstr(servname); + printf(", "); + if (passhints) + printf("{ 0x%x, %d, %d }", flags, family, socktype); + else + printf("NULL"); + + printf("); result: "); + printstr(result); + printf("; expected: "); + printstr(exp_result); + printf("\n"); + err(); +} + +/* yes, this is ugly, but not as ugly as repeating it all every time */ +#define TEST_GETADDRINFO_ERR_PARAMS \ + nodename, servname, passhints, flags, family, socktype + +static void test_getaddrinfo_err_nr( + int n, + const char *nodename, + const char *servname, + int passhints, + int flags, + int family, + int socktype, + int exp_result, + int result) +{ + char exp_result_s[23], result_s[23]; + + /* convert result to string */ + snprintf(exp_result_s, sizeof(exp_result_s), "%d/0x%x", + exp_result, exp_result); + snprintf(result_s, sizeof(result_s), "%d/0x%x", result, result); + test_getaddrinfo_err(n, TEST_GETADDRINFO_ERR_PARAMS, + exp_result_s, result_s); +} + +static void test_getnameinfo_err( + int n, + unsigned long ipaddr, + unsigned short port, + socklen_t nodelen, + socklen_t servicelen, + int flags, + const char *exp_result, + const char *result) +{ + printf("error %d: getnameinfo(0x%.8x, %d, %d, %d, 0x%x); result: ", + n, ntohl(ipaddr), ntohs(port), nodelen, servicelen, flags); + printstr(result); + printf("; expected: "); + printstr(exp_result); + printf("\n"); + err(); +} + +/* yes, this is ugly, but not as ugly as repeating it all every time */ +#define TEST_GETNAMEINFO_ERR_PARAMS ipaddr, port, nodelen, servicelen, flags + +static void test_getnameinfo_err_nr( + int n, + unsigned long ipaddr, + unsigned short port, + socklen_t nodelen, + socklen_t servicelen, + int flags, + int exp_result, + int result) +{ + char exp_result_s[23], result_s[23]; + + /* convert result to string */ + snprintf(exp_result_s, sizeof(exp_result_s), "%d/0x%x", + exp_result, exp_result); + snprintf(result_s, sizeof(result_s), "%d/0x%x", result, result); + test_getnameinfo_err(n, TEST_GETNAMEINFO_ERR_PARAMS, + exp_result_s, result_s); +} + +static void test_getaddrinfo( + const char *nodename, + const char *servname, + int passhints, + int flags, + int family, + int socktype, + int exp_results, + unsigned long exp_ip, + int exp_canonname, + unsigned short exp_port) +{ + struct addrinfo *ai, *ai_cur; + struct addrinfo hints; + struct sockaddr_in *sockaddr_in; + int ai_count_dgram, ai_count_stream, r; + + /* some parameters are only meaningful with hints */ + assert(passhints || !flags); + assert(passhints || family == AF_UNSPEC); + assert(passhints || !socktype); + + /* initialize hints */ + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = flags; + hints.ai_family = family; + hints.ai_socktype = socktype; + hints.ai_family = family; + + /* perform query and test result */ + ai = (struct addrinfo *) 0xDEADBEEF; + r = getaddrinfo(nodename, servname, passhints ? &hints : NULL, &ai); + if (r < 0 || r >= 32 || !((1 << r) & exp_results)) + test_getaddrinfo_err_nr(1, TEST_GETADDRINFO_ERR_PARAMS, exp_results, r); + + if (r) + return; + + /* the function succeeded; do the results make sense? */ + ai_cur = ai; + ai_count_dgram = 0; + ai_count_stream = 0; + while (ai_cur) + { + /* test result fields */ + if (ai_cur->ai_family != AF_INET) + test_getaddrinfo_err_nr(2, TEST_GETADDRINFO_ERR_PARAMS, + AF_INET, ai_cur->ai_family); + + if (socktype && ai_cur->ai_socktype != socktype) + test_getaddrinfo_err_nr(3, TEST_GETADDRINFO_ERR_PARAMS, + socktype, ai_cur->ai_socktype); + + switch (ai_cur->ai_socktype) + { + case SOCK_DGRAM: ai_count_dgram++; break; + case SOCK_STREAM: ai_count_stream++; break; + } + + /* do address and port match? */ + if (ai_cur->ai_addrlen != sizeof(struct sockaddr_in)) + test_getaddrinfo_err_nr(4, TEST_GETADDRINFO_ERR_PARAMS, + sizeof(struct sockaddr_in), + ai_cur->ai_addrlen); + else + { + sockaddr_in = (struct sockaddr_in *) ai_cur->ai_addr; + if (sockaddr_in->sin_addr.s_addr != exp_ip) + test_getaddrinfo_err_nr(5, + TEST_GETADDRINFO_ERR_PARAMS, + ntohl(exp_ip), + ntohl(sockaddr_in->sin_addr.s_addr)); + + if (sockaddr_in->sin_port != exp_port) + test_getaddrinfo_err_nr(6, + TEST_GETADDRINFO_ERR_PARAMS, + ntohs(exp_port), + ntohs(sockaddr_in->sin_port)); + } + + /* is canonical supplied? */ + if (exp_canonname && + (!ai_cur->ai_canonname || !*ai_cur->ai_canonname)) + test_getaddrinfo_err(7, + TEST_GETADDRINFO_ERR_PARAMS, + "(anything)", ai_cur->ai_canonname); + + if (!exp_canonname && ai_cur->ai_canonname) + test_getaddrinfo_err(8, + TEST_GETADDRINFO_ERR_PARAMS, + NULL, ai_cur->ai_canonname); + + /* move to next result */ + ai_cur = ai_cur->ai_next; + } + + /* check number of results */ + if (ai_count_dgram != ((socktype == SOCK_STREAM) ? 0 : 1)) + test_getaddrinfo_err_nr(9, TEST_GETADDRINFO_ERR_PARAMS, + (socktype == SOCK_STREAM) ? 0 : 1, ai_count_dgram); + + if (ai_count_stream != ((socktype == SOCK_DGRAM) ? 0 : 1)) + test_getaddrinfo_err_nr(10, TEST_GETADDRINFO_ERR_PARAMS, + (socktype == SOCK_DGRAM) ? 0 : 1, ai_count_stream); + + /* clean up */ + freeaddrinfo(ai); +} + +static void memsetl(void *s, unsigned long c, size_t n) +{ + unsigned char *p = (unsigned char *) s; + size_t i; + + for (i = 0; i < n; i++) + p[i] = c >> (8 * (i % sizeof(c))); +} + +void test_getnameinfo( + unsigned long ipaddr, + unsigned short port, + const char *exp_node, + socklen_t nodelen, + const char *exp_service, + socklen_t servicelen, + int flags, + int exp_results) +{ + struct sockaddr_in sockaddr; + char node[256], service[256]; + int r; + + /* avoid buffer overflows */ + assert(nodelen <= sizeof(node)); + assert(servicelen <= sizeof(service)); + + /* perform query and test result */ + sockaddr.sin_family = AF_INET; + sockaddr.sin_addr.s_addr = ipaddr; + sockaddr.sin_port = port; + memsetl(node, 0xDEADBEEF, nodelen); + memsetl(service, 0xDEADBEEF, servicelen); + r = getnameinfo((struct sockaddr *) &sockaddr, sizeof(sockaddr), + node, nodelen, service, servicelen, flags); + + if (r < 0 || r >= 32 || !((1 << r) & exp_results)) + test_getnameinfo_err_nr(1, TEST_GETNAMEINFO_ERR_PARAMS, + exp_results, r); + + if (r) + return; + + /* check results */ + if (nodelen && strcmp(exp_node, node) != 0) + test_getnameinfo_err(2, TEST_GETNAMEINFO_ERR_PARAMS, + exp_node, node); + + if (servicelen && strcmp(exp_service, service) != 0) + test_getnameinfo_err(2, TEST_GETNAMEINFO_ERR_PARAMS, + exp_service, service); +} + +static struct +{ + const char *nodename; + unsigned long ipaddr; + int numeric; + int canonname; + int need_network; + int exp_result; +} hosts[] = { + { NULL, 0x7f000001, 1, 1, 0, 0 }, + { "0.0.0.0", 0x00000000, 1, 0, 0, 0, }, + { "0.0.0.255", 0x000000ff, 1, 0, 0, 0, }, + { "0.0.255.0", 0x0000ff00, 1, 0, 0, 0, }, + { "0.255.0.0", 0x00ff0000, 1, 0, 0, 0, }, + { "255.0.0.0", 0xff000000, 1, 0, 0, 0, }, + { "127.0.0.1", 0x7f000001, 1, 0, 0, 0, }, + { "localhost", 0x7f000001, 0, 1, 0, 0, }, + { "minix3.org", 0x82251414, 0, 1, 1, 0, }, + { "", 0x00000000, 1, 0, 0, (1 << EAI_NONAME) }, + { "256.256.256.256", 0x00000000, 1, 0, 0, (1 << EAI_NONAME) }, + { "minix3.xxx", 0x00000000, 0, 0, 1, (1 << EAI_NONAME) }}; + +static struct +{ + const char *servname; + unsigned short port; + int numeric; + int socktype; + int exp_result; +} services[] = { + { NULL, 0, 1, 0, 0 }, + { "0", 0, 1, 0, 0 }, + { "1", 1, 1, 0, 0 }, + { "32767", 32767, 1, 0, 0 }, + { "32768", 32768, 1, 0, 0 }, + { "65535", 65535, 1, 0, 0 }, + { "echo", 7, 0, 0, 0 }, + { "ftp", 21, 0, SOCK_STREAM, 0 }, + { "tftp", 69, 0, SOCK_DGRAM , 0 }, + { "-1", 0, 1, 0, (1 << EAI_SERVICE) }, + { "", 0, 1, 0, (1 << EAI_SERVICE) }, + { "65537", 0, 1, 0, (1 << EAI_SERVICE) }, + { "XXX", 0, 0, 0, (1 << EAI_SERVICE) }}; + +static struct +{ + int value; + int exp_result; +} families[] = { + { AF_UNSPEC, 0 }, + { AF_INET, 0 }, + { AF_UNSPEC + AF_INET + 1, (1 << EAI_FAMILY) }}; + +static struct +{ + int value; + int exp_result; +} socktypes[] = { + { 0, 0 }, + { SOCK_STREAM, 0 }, + { SOCK_DGRAM, 0 }, + { SOCK_STREAM + SOCK_DGRAM + 1, (1 << EAI_SOCKTYPE) }}; + +#define LENGTH(a) (sizeof((a)) / sizeof((a)[0])) + +static void test_getaddrinfo_all(int use_network) +{ + int flag_PASSIVE, flag_CANONNAME, flag_NUMERICHOST, flag_NUMERICSERV; + int exp_results, flags, i, j, k, l, needhints, passhints; + unsigned long ipaddr; + + /* loop through various parameter values */ + for (i = 0; i < LENGTH(hosts); i++) + for (j = 0; j < LENGTH(services); j++) + for (k = 0; k < LENGTH(families); k++) + for (l = 0; l < LENGTH(socktypes); l++) + for (flag_PASSIVE = 0; flag_PASSIVE < 2; flag_PASSIVE++) + for (flag_CANONNAME = 0; flag_CANONNAME < 2; flag_CANONNAME++) + for (flag_NUMERICHOST = 0; flag_NUMERICHOST < 2; flag_NUMERICHOST++) + for (flag_NUMERICSERV = 0; flag_NUMERICSERV < 2; flag_NUMERICSERV++) + { + /* skip tests that need but cannot use network */ + if (!use_network && hosts[i].need_network) + continue; + + /* determine flags */ + flags = (flag_PASSIVE ? AI_PASSIVE : 0) | + (flag_CANONNAME ? AI_CANONNAME : 0) | + (flag_NUMERICHOST ? AI_NUMERICHOST : 0) | + (flag_NUMERICSERV ? AI_NUMERICSERV : 0); + + /* flags may influence IP address */ + ipaddr = hosts[i].ipaddr; + if (!hosts[i].nodename && flag_PASSIVE) + ipaddr = INADDR_ANY; + + /* determine expected result */ + exp_results = + hosts[i].exp_result | + services[j].exp_result | + families[k].exp_result | + socktypes[l].exp_result; + if (!hosts[i].nodename && !services[j].servname) + exp_results |= (1 << EAI_NONAME); + + if (flag_NUMERICHOST && !hosts[i].numeric) + exp_results |= (1 << EAI_NONAME); + + if (flag_NUMERICSERV && !services[j].numeric) + exp_results |= (1 << EAI_SERVICE); + + if (services[j].socktype && socktypes[l].value != services[j].socktype) + exp_results |= (1 << EAI_SERVICE); + + /* with no reason for failure, we demand success */ + if (!exp_results) + exp_results |= (1 << 0); + + /* some options require hints */ + needhints = (families[k].value != AF_UNSPEC || + socktypes[l].value != 0 || flags) ? 1 : 0; + for (passhints = needhints; passhints < 2; passhints++) + { + /* test getaddrinfo function */ + test_getaddrinfo( + hosts[i].nodename, + services[j].servname, + passhints, + flags, + families[k].value, + socktypes[l].value, + exp_results, + htonl(ipaddr), + flag_CANONNAME && hosts[i].canonname, + htons(services[j].port)); + } + } +} + +static struct +{ + const char *nodename; + const char *nodenum; + unsigned long ipaddr; + int havename; +} ipaddrs[] = { + { "0.0.0.0", "0.0.0.0", 0x00000000, 0 }, + { "0.0.0.255", "0.0.0.255", 0x000000ff, 0 }, + { "0.0.255.0", "0.0.255.0", 0x0000ff00, 0 }, + { "0.255.0.0", "0.255.0.0", 0x00ff0000, 0 }, + { "255.0.0.0", "255.0.0.0", 0xff000000, 0 }, + { "localhost", "127.0.0.1", 0x7f000001, 1 }, + /* no reverse DNS unfortunately */ + /* { "minix3.org", "130.37.20.20", 0x82251414, 1 } */}; + +static struct +{ + const char *servname; + const char *servnum; + unsigned short port; + int socktype; +} ports[] = { + { "0", "0", 0, 0 }, + { "tcpmux", "1", 1, SOCK_STREAM }, + { "32767", "32767", 32767, 0 }, + { "32768", "32768", 32768, 0 }, + { "65535", "65535", 65535, 0 }, + { "echo", "7", 7, 0 }, + { "ftp", "21", 21, SOCK_STREAM }, + { "tftp", "69", 69, SOCK_DGRAM }}; + +static int buflens[] = { 0, 1, 2, 3, 4, 5, 6, 9, 10, 11, 255 }; + +static void test_getnameinfo_all(void) +{ + int flag_NUMERICHOST, flag_NAMEREQD, flag_NUMERICSERV, flag_DGRAM; + int exp_results, flags, i, j, k, l, socktypemismatch; + const char *nodename, *servname; + + /* loop through various parameter values */ + for (i = 0; i < LENGTH(ipaddrs); i++) + for (j = 0; j < LENGTH(ports); j++) + for (k = 0; k < LENGTH(buflens); k++) + for (l = 0; l < LENGTH(buflens); l++) + for (flag_NUMERICHOST = 0; flag_NUMERICHOST < 2; flag_NUMERICHOST++) + for (flag_NAMEREQD = 0; flag_NAMEREQD < 2; flag_NAMEREQD++) + for (flag_NUMERICSERV = 0; flag_NUMERICSERV < 2; flag_NUMERICSERV++) + for (flag_DGRAM = 0; flag_DGRAM < 2; flag_DGRAM++) + { + /* determine flags */ + flags = (flag_NUMERICHOST ? NI_NUMERICHOST : 0) | + (flag_NAMEREQD ? NI_NAMEREQD : 0) | + (flag_NUMERICSERV ? NI_NUMERICSERV : 0) | + (flag_DGRAM ? NI_DGRAM : 0); + + /* determine expected result */ + exp_results = 0; + if (!buflens[k] && !buflens[l]) + exp_results |= (1 << EAI_NONAME); + + nodename = flag_NUMERICHOST ? ipaddrs[i].nodenum : ipaddrs[i].nodename; + if (buflens[k] > 0 && buflens[k] <= strlen(nodename)) + exp_results |= (1 << EAI_OVERFLOW); + + socktypemismatch = + (flag_DGRAM && ports[j].socktype == SOCK_STREAM) || + (!flag_DGRAM && ports[j].socktype == SOCK_DGRAM); + servname = (flag_NUMERICSERV || socktypemismatch) ? + ports[j].servnum : ports[j].servname; + if (buflens[l] > 0 && buflens[l] <= strlen(servname)) + exp_results |= (1 << EAI_OVERFLOW); + + if (flag_NAMEREQD && (!ipaddrs[i].havename | flag_NUMERICHOST) && buflens[k]) + exp_results |= (1 << EAI_NONAME); + + /* with no reason for failure, we demand success */ + if (!exp_results) + exp_results |= (1 << 0); + + /* perform the test */ + test_getnameinfo( + htonl(ipaddrs[i].ipaddr), + htons(ports[j].port), + nodename, + buflens[k], + servname, + buflens[l], + flags, + exp_results); + } +} + +static int can_use_network(void) +{ + pid_t pid; + int status; + + /* try to ping minix3.org */ + status = system("ping www.minix3.org > /dev/nul 2>&1"); + if (status == 127) + { + printf("cannot execute ping\n"); + err(); + } + + return status == 0; +} + +int main(void) +{ + int use_network; + + printf("Test 48 "); + fflush(stdout); + + use_network = can_use_network(); + if (!use_network) + printf("Warning: no network\n"); + + test_getaddrinfo_all(use_network); + test_getnameinfo_all(); + + quit(); + return 0; +}