Browse Source

inet/getaddrinfo: simplest /etc/gai.conf to control IPv6/IPv4 addresses sort order

Implement simplest variant of /etc/gai.conf to control getaddrinfo
IPv6/IPv4 addresses sorting. Keep the default sort order - IPv6 first,
IPv4 second. To invert it, create /etc/gai.conf containing single line:

 precedence ::ffff:0:0/96  100

Example before:

$ nslookup security.debian.org 8.8.8.8
Server:    8.8.8.8
Address 1: 8.8.8.8 google-public-dns-a.google.com

Name:      security.debian.org
Address 1: 2001:a78:5:0:216:35ff:fe7f:be4f villa.debian.org
Address 2: 2001:a78:5:1:216:35ff:fe7f:6ceb lobos.debian.org
Address 3: 195.20.242.89 wieck.debian.org
Address 4: 212.211.132.250 lobos.debian.org
Address 5: 212.211.132.32 villa.debian.org

After patch & precedence set in /etc/gai.conf:

$ nslookup security.debian.org 8.8.8.8
Server:    8.8.8.8
Address 1: 8.8.8.8 google-public-dns-a.google.com

Name:      security.debian.org
Address 1: 195.20.242.89 wieck.debian.org
Address 2: 212.211.132.250 lobos.debian.org
Address 3: 212.211.132.32 villa.debian.org
Address 4: 2001:a78:5:0:216:35ff:fe7f:be4f villa.debian.org
Address 5: 2001:a78:5:1:216:35ff:fe7f:6ceb lobos.debian.org

 bloat-o-meter report:
function                                             old     new   delta
getaddrinfo                                          726    1138    +412
gaih_inet                                           2660    2692     +32
.rodata                                            16618   16643     +25
__gai_precedence                                       -       1      +1
------------------------------------------------------------------------------
(add/remove: 1/0 grow/shrink: 4/0 up/down: 882/0)             Total: 470 bytes

Signed-off-by: Leonid Lisovskiy <lly.dev@gmail.com>
Leonid Lisovskiy 9 years ago
parent
commit
bad1263e33
1 changed files with 94 additions and 3 deletions
  1. 94 3
      libc/inet/getaddrinfo.c

+ 94 - 3
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;