dirmngr: Use IPv4 or IPv6 interface only if available.
authorWerner Koch <wk@gnupg.org>
Tue, 12 Nov 2019 19:29:47 +0000 (20:29 +0100)
committerWerner Koch <wk@gnupg.org>
Tue, 12 Nov 2019 19:35:12 +0000 (20:35 +0100)
* dirmngr/dns-stuff.c (cached_inet_support): New variable.
(dns_stuff_housekeeping): New.
(check_inet_support): New.
* dirmngr/http.c (connect_server): Use only detected interfaces.
* dirmngr/dirmngr.c (housekeeping_thread): Flush the new cache.
--

This currently works only for Windows but that is where users really
ran into problems.  The old workaround was to configure disable-ipv4
or disable-ipv6.

Signed-off-by: Werner Koch <wk@gnupg.org>
dirmngr/dirmngr.c
dirmngr/dns-stuff.c
dirmngr/dns-stuff.h
dirmngr/http.c

index f7dd029..7948e3f 100644 (file)
@@ -1962,6 +1962,7 @@ housekeeping_thread (void *arg)
   memset (&ctrlbuf, 0, sizeof ctrlbuf);
   dirmngr_init_default_ctrl (&ctrlbuf);
 
+  dns_stuff_housekeeping ();
   ks_hkp_housekeeping (curtime);
   if (network_activity_seen)
     {
index e48aca7..4e381c0 100644 (file)
@@ -149,6 +149,15 @@ static char tor_nameserver[40+20];
 static char tor_socks_user[30];
 static char tor_socks_password[20];
 
+/* To avoid checking the interface too often we cache the result.  */
+static struct
+{
+  unsigned int valid:1;
+  unsigned int v4:1;
+  unsigned int v6:1;
+} cached_inet_support;
+
+
 
 #ifdef USE_LIBDNS
 /* Libdns global data.  */
@@ -686,6 +695,21 @@ reload_dns_stuff (int force)
 #else
   (void)force;
 #endif
+
+  /* We also flush the IPv4/v6 support flag cache.  */
+  cached_inet_support.valid = 0;
+}
+
+
+/* Called from time to time from the housekeeping thread.  */
+void
+dns_stuff_housekeeping (void)
+{
+  /* With the current housekeeping interval of 10 minutes we flush
+   * that case so that a new or removed interface will be detected not
+   * later than 10 minutes after it changed.  This way the user does
+   * not need a reload.  */
+  cached_inet_support.valid = 0;
 }
 
 
@@ -2387,3 +2411,83 @@ get_dns_cname (ctrl_t ctrl, const char *name, char **r_cname)
                err ? gpg_strerror (err) : *r_cname);
   return err;
 }
+
+
+/* Check whether the machine has any usable inet devices up and
+ * running.  We put this into dns because on Windows this is
+ * implemented using getaddrinfo and thus easiest done here.  */
+void
+check_inet_support (int *r_v4, int *r_v6)
+{
+  if (cached_inet_support.valid)
+    {
+      *r_v4 = cached_inet_support.v4;
+      *r_v6 = cached_inet_support.v6;
+      return;
+    }
+
+  *r_v4 = *r_v6 = 0;
+
+#ifdef HAVE_W32_SYSTEM
+  {
+    gpg_error_t err;
+    int ret;
+    struct addrinfo *aibuf = NULL;
+    struct addrinfo *ai;
+
+    ret = getaddrinfo ("..localmachine", NULL, NULL, &aibuf);
+    if (ret)
+      {
+        err = map_eai_to_gpg_error (ret);
+        log_error ("%s: getaddrinfo failed: %s\n",__func__, gpg_strerror (err));
+        aibuf = NULL;
+      }
+
+    for (ai = aibuf; ai; ai = ai->ai_next)
+      {
+        if (opt_debug)
+          {
+            log_debug ("%s:  family: %d\n", __func__, ai->ai_family);
+            if (ai->ai_family == AF_INET6 || ai->ai_family == AF_INET)
+              {
+                char buffer[46];
+                DWORD buflen;
+                buflen = sizeof buffer;
+                if (WSAAddressToString (ai->ai_addr, (DWORD)ai->ai_addrlen,
+                                        NULL, buffer, &buflen))
+                  log_debug ("%s: WSAAddressToString failed: ec=%u\n",
+                             __func__, (unsigned int)WSAGetLastError ());
+                else
+                  log_debug ("%s:     addr: %s\n", __func__, buffer);
+              }
+          }
+        if (ai->ai_family == AF_INET6)
+          {
+            struct sockaddr_in6 *v6addr = (struct sockaddr_in6 *)ai->ai_addr;
+            if (!IN6_IS_ADDR_LINKLOCAL (&v6addr->sin6_addr))
+              *r_v6 = 1;
+          }
+        else if (ai->ai_family == AF_INET)
+          {
+            *r_v4 = 1;
+          }
+      }
+
+    if (aibuf)
+      freeaddrinfo (aibuf);
+  }
+#else /*!HAVE_W32_SYSTEM*/
+  {
+    /* For now we assume that we have both protocols.  */
+    *r_v4 = *r_v6 = 1;
+  }
+#endif /*!HAVE_W32_SYSTEM*/
+
+  if (opt_verbose)
+    log_info ("detected interfaces:%s%s\n",
+              *r_v4? " IPv4":"", *r_v6? " IPv4":"");
+
+  cached_inet_support.valid = 1;
+  cached_inet_support.v4 = *r_v4;
+  cached_inet_support.v6 = *r_v6;
+}
index 06a4312..415eac5 100644 (file)
@@ -134,6 +134,9 @@ void set_dns_nameserver (const char *ipaddr);
 /* SIGHUP action handler for this module.  */
 void reload_dns_stuff (int force);
 
+/* Housekeeping for this module.  */
+void dns_stuff_housekeeping (void);
+
 void free_dns_addrinfo (dns_addrinfo_t ai);
 
 /* Function similar to getaddrinfo.  */
@@ -170,4 +173,8 @@ gpg_error_t get_dns_srv (ctrl_t ctrl,
                          struct srventry **list, unsigned int *r_count);
 
 
+/* Store true in the variable if such an IP interface is usable.  */
+void check_inet_support (int *r_v4, int *r_v6);
+
+
 #endif /*GNUPG_DIRMNGR_DNS_STUFF_H*/
index 8e0701f..c6dc077 100644 (file)
@@ -2908,7 +2908,7 @@ connect_server (ctrl_t ctrl, const char *server, unsigned short port,
   unsigned int srvcount = 0;
   int hostfound = 0;
   int anyhostaddr = 0;
-  int srv, connected;
+  int srv, connected, v4_valid, v6_valid;
   gpg_error_t last_err = 0;
   struct srventry *serverlist = NULL;
 
@@ -2918,6 +2918,8 @@ connect_server (ctrl_t ctrl, const char *server, unsigned short port,
   init_sockets ();
 #endif /*Windows*/
 
+  check_inet_support (&v4_valid, &v6_valid);
+
   /* Onion addresses require special treatment.  */
   if (is_onion_address (server))
     {
@@ -2996,9 +2998,11 @@ connect_server (ctrl_t ctrl, const char *server, unsigned short port,
 
       for (ai = aibuf; ai && !connected; ai = ai->next)
         {
-          if (ai->family == AF_INET && (flags & HTTP_FLAG_IGNORE_IPv4))
+          if (ai->family == AF_INET
+              && ((flags & HTTP_FLAG_IGNORE_IPv4) || !v4_valid))
             continue;
-          if (ai->family == AF_INET6 && (flags & HTTP_FLAG_IGNORE_IPv6))
+          if (ai->family == AF_INET6
+              && ((flags & HTTP_FLAG_IGNORE_IPv6) || !v6_valid))
             continue;
 
           if (sock != ASSUAN_INVALID_FD)