diff options
Diffstat (limited to 'libc/inet/getaddrinfo.c')
-rw-r--r-- | libc/inet/getaddrinfo.c | 97 |
1 files changed, 94 insertions, 3 deletions
diff --git a/libc/inet/getaddrinfo.c b/libc/inet/getaddrinfo.c index 168adb115..090d7e96e 100644 --- a/libc/inet/getaddrinfo.c +++ b/libc/inet/getaddrinfo.c @@ -62,6 +62,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <stdlib.h> #include <string.h> #include <unistd.h> +#include <stdbool.h> #include <arpa/inet.h> #include <sys/socket.h> #include <netinet/in.h> @@ -70,6 +71,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <sys/utsname.h> #include <net/if.h> #include <ifaddrs.h> +#include "internal/parse_config.h" #define GAIH_OKIFUNSPEC 0x0100 #define GAIH_EAI ~(GAIH_OKIFUNSPEC) @@ -348,6 +350,11 @@ gaih_inet_serv(const char *servicename, const struct gaih_typeproto *tp, return 0; } +#if defined __UCLIBC_HAS_IPV6__ +static uint8_t __gai_precedence = 0; /* =1 - IPv6, IPv4 + =2 - IPv4, IPv6 */ +#endif + /* NB: also uses h,pat,rc,no_data variables */ #define gethosts(_family, _type) \ { \ @@ -552,21 +559,32 @@ gaih_inet(const char *name, const struct gaih_service *service, if (at->family == AF_UNSPEC && !(req->ai_flags & AI_NUMERICHOST)) { struct hostent *h; struct gaih_addrtuple **pat = &at; - int no_data = 0; - int no_inet6_data; + int no_data, no_inet6_data; +#if defined __UCLIBC_HAS_IPV6__ + bool first_try = true; +#endif /* * If we are looking for both IPv4 and IPv6 address we don't want * the lookup functions to automatically promote IPv4 addresses to * IPv6 addresses. */ + no_inet6_data = no_data = 0; #if defined __UCLIBC_HAS_IPV6__ + if (__gai_precedence == 2) + goto try_v4; + + try_v6: if (req->ai_family == AF_UNSPEC || req->ai_family == AF_INET6) if (!(req->ai_flags & AI_ADDRCONFIG) || (seen & SEEN_IPV6)) gethosts(AF_INET6, struct in6_addr); -#endif no_inet6_data = no_data; + if (!first_try) + goto tried_all; + first_try = false; + try_v4: +#endif if (req->ai_family == AF_INET || (!v4mapped && req->ai_family == AF_UNSPEC) || (v4mapped && (no_inet6_data != 0 || (req->ai_flags & AI_ALL))) @@ -574,7 +592,14 @@ gaih_inet(const char *name, const struct gaih_service *service, if (!(req->ai_flags & AI_ADDRCONFIG) || (seen & SEEN_IPV4)) gethosts(AF_INET, struct in_addr); } +#if defined __UCLIBC_HAS_IPV6__ + if (first_try) { + first_try = false; + goto try_v6; + } + tried_all: +#endif if (no_data != 0 && no_inet6_data != 0) { /* If both requests timed out report this. */ if (no_data == EAI_AGAIN && no_inet6_data == EAI_AGAIN) @@ -775,6 +800,71 @@ static const struct gaih gaih[] = { { PF_UNSPEC, NULL } }; +#if defined __UCLIBC_HAS_IPV6__ + +/* + * A call to getaddrinfo might return multiple answers. To provide + * possibility to change the sorting we must use /etc/gai.conf file, + * like glibc. + * + * gai.conf format: + * + * label <netmask> <precedence> + * The value is added to the label table used in + * the RFC 3484 sorting. If any label definition + * is present in the configuration file is present, + * the default table is not used. All the label + * definitions of the default table which are to + * be maintained have to be duplicated. + * precedence <netmask> <precedence> + * This keyword is similar to label, but instead + * the value is added to the precedence table as + * specified in RFC 3484. Once again, the presence + * of a single precedence line in the configuration + * file causes the default table to not be used. + * + * The simplified uclibc's implementation allows to change the IPv4/IPv6 + * sorting order for a whole address space only, i.e + * "precedence ::ffff:0:0/96 100" is only supported. + */ +static void __gai_conf_parse(void) +{ + /* NO reread of /etc/gai.conf on change. */ + if (__gai_precedence != 0) + return; + + __gai_precedence = 1; /* default IPv6 */ + + parser_t *parser; + char **tok = NULL; + + parser = config_open("/etc/gai.conf"); + if (!parser) + return; + + while (config_read(parser, &tok, 3, 3, "# \t", PARSE_NORMAL)) { + if (strcmp(tok[0], "precedence") == 0) { + char *pfx; + struct in6_addr mask; + + pfx = strchr(tok[1], '/'); + if (!pfx) + continue; + *(pfx++) = 0; + if (inet_pton(AF_INET6, tok[1], &mask) <= 0) + continue; + if (IN6_IS_ADDR_V4MAPPED(&mask) + && mask.s6_addr32[3] == 0 + && atoi(pfx) == 96 && atoi(tok[2]) == 100) + __gai_precedence = 2; /* IPv4 first */ + } + } + config_close(parser); +} +#else +# define __gai_conf_parse(x) +#endif /* __UCLIBC_HAS_IPV6__ */ + void freeaddrinfo(struct addrinfo *ai) { @@ -834,6 +924,7 @@ getaddrinfo(const char *name, const char *service, } else pservice = NULL; + __gai_conf_parse(); g = gaih; pg = NULL; p = NULL; |