From f1e9f510ec70f2bb64f1b61b2b040d8d3103af32 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 12 Apr 2011 16:30:08 +0200 Subject: [PATCH] Add code for explicit selection of pooled A records. To better cope with round robin pooled A records like keys.gnupg.net we need to keep some information on unresponsive hosts etc. What we do now is to resolve the hostnames, remember them and select a random one. If a host is dead it will be marked and a different one selected. This is intended to solve the problem of long timeouts due to unresponsive hosts. The code is not yet finished but selection works. --- common/ChangeLog | 4 + common/sysutils.c | 11 + common/sysutils.h | 1 + dirmngr/ChangeLog | 10 + dirmngr/ks-action.c | 2 +- dirmngr/ks-engine-hkp.c | 496 +++++++++++++++++++++++++++++++++------- dirmngr/ks-engine.h | 1 + dirmngr/server.c | 11 +- 8 files changed, 455 insertions(+), 81 deletions(-) diff --git a/common/ChangeLog b/common/ChangeLog index 625386739..ba7794e08 100644 --- a/common/ChangeLog +++ b/common/ChangeLog @@ -1,3 +1,7 @@ +2011-04-01 Werner Koch + + * sysutils.c (get_uint_nonce): New. + 2011-03-03 Werner Koch * estream.c (struct estream_list): Rename to estream_list_s and diff --git a/common/sysutils.c b/common/sysutils.c index a94d1fca5..648e70fe6 100644 --- a/common/sysutils.c +++ b/common/sysutils.c @@ -150,6 +150,17 @@ get_session_marker (size_t *rlen) return marker; } +/* Return a random number in an unsigned int. */ +unsigned int +get_uint_nonce (void) +{ + unsigned int value; + + gcry_create_nonce (&value, sizeof value); + return value; +} + + #if 0 /* not yet needed - Note that this will require inclusion of cmacros.am in Makefile.am */ diff --git a/common/sysutils.h b/common/sysutils.h index a2f74f936..3559b348e 100644 --- a/common/sysutils.h +++ b/common/sysutils.h @@ -41,6 +41,7 @@ void trap_unaligned (void); int disable_core_dumps (void); int enable_core_dumps (void); const unsigned char *get_session_marker (size_t *rlen); +unsigned int get_uint_nonce (void); /*int check_permissions (const char *path,int extension,int checkonly);*/ void gnupg_sleep (unsigned int seconds); int translate_sys2libc_fd (gnupg_fd_t fd, int for_write); diff --git a/dirmngr/ChangeLog b/dirmngr/ChangeLog index bb40fe1ea..f7ac88719 100644 --- a/dirmngr/ChangeLog +++ b/dirmngr/ChangeLog @@ -1,3 +1,13 @@ +2011-04-12 Werner Koch + + * ks-engine-hkp.c (ks_hkp_search, ks_hkp_get, ks_hkp_put): Factor + code out to .. + (make_host_part): new. + (hostinfo_s): New. + (create_new_hostinfo, find_hostinfo, sort_hostpool) + (select_random_host, map_host, mark_host_dead) + (ks_hkp_print_hosttable): New. + 2011-02-23 Werner Koch * certcache.c (get_cert_bysubject): Take care of a NULL argument. diff --git a/dirmngr/ks-action.c b/dirmngr/ks-action.c index 1f876d07b..14de4d6c0 100644 --- a/dirmngr/ks-action.c +++ b/dirmngr/ks-action.c @@ -76,7 +76,7 @@ ks_action_help (ctrl_t ctrl, const char *url) return err; } - /* Call all engines to geive them a chance to print a help sting. */ + /* Call all engines to give them a chance to print a help sting. */ err = ks_hkp_help (ctrl, parsed_uri); if (!err) err = ks_http_help (ctrl, parsed_uri); diff --git a/dirmngr/ks-engine-hkp.c b/dirmngr/ks-engine-hkp.c index 5ad61fd4d..0dd9a646d 100644 --- a/dirmngr/ks-engine-hkp.c +++ b/dirmngr/ks-engine-hkp.c @@ -17,12 +17,20 @@ * along with this program; if not, see . */ +#warning fixme Windows part not yet done #include #include #include #include #include +#ifdef HAVE_W32_SYSTEM +# include +#else /*!HAVE_W32_SYSTEM*/ +# include +# include +# include +#endif /*!HAVE_W32_SYSTEM*/ #include "dirmngr.h" #include "misc.h" @@ -36,6 +44,359 @@ /* How many redirections do we allow. */ #define MAX_REDIRECTS 2 +/* Objects used to maintain information about hosts. */ +struct hostinfo_s; +typedef struct hostinfo_s *hostinfo_t; +struct hostinfo_s +{ + time_t lastfail; /* Time we tried to connect and failed. */ + time_t lastused; /* Time of last use. */ + int *pool; /* A -1 terminated array with indices into + HOSTTABLE or NULL if NAME is not a pool + name. */ + int poolidx; /* Index into POOL with the used host. */ + unsigned int v4:1; /* Host supports AF_INET. */ + unsigned int v6:1; /* Host supports AF_INET6. */ + unsigned int dead:1; /* Host is currently unresponsive. */ + char name[1]; /* The hostname. */ +}; + + +/* An array of hostinfo_t for all hosts requested by the caller or + resolved from a pool name and its allocated size.*/ +static hostinfo_t *hosttable; +static int hosttable_size; + +/* The number of host slots we initally allocate for HOSTTABLE. */ +#define INITIAL_HOSTTABLE_SIZE 10 + + +/* Create a new hostinfo object, fill in NAME and put it into + HOSTTABLE. Return the index into hosttable on success or -1 on + error. */ +static int +create_new_hostinfo (const char *name) +{ + hostinfo_t hi, *newtable; + int newsize; + int idx, rc; + + hi = xtrymalloc (sizeof *hi + strlen (name)); + if (!hi) + return -1; + strcpy (hi->name, name); + hi->pool = NULL; + hi->poolidx = -1; + hi->lastused = (time_t)(-1); + hi->lastfail = (time_t)(-1); + hi->v4 = 0; + hi->v6 = 0; + + /* Add it to the hosttable. */ + for (idx=0; idx < hosttable_size; idx++) + if (!hosttable[idx]) + { + hosttable[idx] = hi; + return idx; + } + /* Need to extend the hosttable. */ + newsize = hosttable_size + INITIAL_HOSTTABLE_SIZE; + newtable = xtryrealloc (hosttable, newsize * sizeof *hosttable); + if (!newtable) + { + xfree (hi); + return -1; + } + hosttable = newtable; + idx = hosttable_size; + hosttable_size = newsize; + rc = idx; + hosttable[idx++] = hi; + while (idx < hosttable_size) + hosttable[idx++] = NULL; + + return rc; +} + + +/* Find the host NAME in our table. Return the index into the + hosttable or -1 if not found. */ +static int +find_hostinfo (const char *name) +{ + int idx; + + for (idx=0; idx < hosttable_size; idx++) + if (hosttable[idx] && !ascii_strcasecmp (hosttable[idx]->name, name)) + return idx; + return -1; +} + + +static int +sort_hostpool (const void *xa, const void *xb) +{ + int a = *(int *)xa; + int b = *(int *)xb; + + assert (a >= 0 && a < hosttable_size); + assert (b >= 0 && b < hosttable_size); + assert (hosttable[a]); + assert (hosttable[b]); + + return ascii_strcasecmp (hosttable[a]->name, hosttable[b]->name); +} + + +/* Select a random host. Consult TABLE which indices into the global + hosttable. Returns index into TABLE or -1 if no host could be + selected. */ +static int +select_random_host (int *table) +{ + int *tbl; + size_t tblsize; + int pidx, idx; + + /* We create a new table so that we select only from currently alive + hosts. */ + for (idx=0, tblsize=0; (pidx = table[idx]) != -1; idx++) + if (hosttable[pidx] && !hosttable[pidx]->dead) + tblsize++; + if (!tblsize) + return -1; /* No hosts. */ + + tbl = xtrymalloc (tblsize * sizeof *tbl); + if (!tbl) + return -1; + for (idx=0, tblsize=0; (pidx = table[idx]) != -1; idx++) + if (hosttable[pidx] && !hosttable[pidx]->dead) + tbl[tblsize++] = pidx; + + if (tblsize == 1) /* Save a get_uint_nonce. */ + pidx = tbl[0]; + else + pidx = get_uint_nonce () % tblsize; + + xfree (tbl); + return pidx; +} + + +/* Map the host name NAME to the actual to be used host name. This + allows us to manage round robin DNS names. We use our own strategy + to choose one of the hosts. For example we skip those hosts which + failed for some time and we stick to one host for a time + independent of DNS retry times. */ +static char * +map_host (const char *name) +{ + hostinfo_t hi; + int idx; + + /* No hostname means localhost. */ + if (!name || !*name) + return xtrystrdup ("localhost"); + + /* See whether the host is in our table. */ + idx = find_hostinfo (name); + if (idx == -1) + { + /* We never saw this host. Allocate a new entry. */ + struct addrinfo hints, *aibuf, *ai; + int *reftbl; + size_t reftblsize; + int refidx; + + reftblsize = 100; + reftbl = xmalloc (reftblsize * sizeof *reftbl); + if (!reftbl) + return NULL; + refidx = 0; + + idx = create_new_hostinfo (name); + if (idx == -1) + { + xfree (reftbl); + return NULL; + } + hi = hosttable[idx]; + + /* Find all A records for this entry and put them into the pool + list - if any. */ + memset (&hints, 0, sizeof (hints)); + hints.ai_socktype = SOCK_STREAM; + if (!getaddrinfo (name, NULL, &hints, &aibuf)) + { + for (ai = aibuf; ai; ai = ai->ai_next) + { + char tmphost[NI_MAXHOST]; + int tmpidx; + int ec; + int i; + + if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) + continue; + + log_printhex ("getaddrinfo returned", ai->ai_addr,ai->ai_addrlen); + if ((ec=getnameinfo (ai->ai_addr, ai->ai_addrlen, + tmphost, sizeof tmphost, + NULL, 0, NI_NAMEREQD))) + log_info ("getnameinfo failed while checking `%s': %s\n", + name, gai_strerror (ec)); + else if (refidx+1 >= reftblsize) + { + log_error ("getnameinfo returned for `%s': `%s'" + " [index table full - ignored]\n", name, tmphost); + } + else + { + + if ((tmpidx = find_hostinfo (tmphost)) != -1) + { + log_info ("getnameinfo returned for `%s': `%s'" + " [already known]\n", name, tmphost); + if (ai->ai_family == AF_INET) + hosttable[tmpidx]->v4 = 1; + if (ai->ai_family == AF_INET6) + hosttable[tmpidx]->v6 = 1; + + for (i=0; i < refidx; i++) + if (reftbl[i] == tmpidx) + break; + if (!(i < refidx) && tmpidx != idx) + reftbl[refidx++] = tmpidx; + } + else + { + log_info ("getnameinfo returned for `%s': `%s'\n", + name, tmphost); + /* Create a new entry. */ + tmpidx = create_new_hostinfo (tmphost); + if (tmpidx == -1) + log_error ("map_host for `%s' problem: %s - `%s'" + " [ignored]\n", + name, strerror (errno), tmphost); + else + { + if (ai->ai_family == AF_INET) + hosttable[tmpidx]->v4 = 1; + if (ai->ai_family == AF_INET6) + hosttable[tmpidx]->v6 = 1; + + for (i=0; i < refidx; i++) + if (reftbl[i] == tmpidx) + break; + if (!(i < refidx) && tmpidx != idx) + reftbl[refidx++] = tmpidx; + } + } + } + } + } + reftbl[refidx] = -1; + if (refidx) + { + assert (!hi->pool); + hi->pool = xtryrealloc (reftbl, (refidx+1) * sizeof *reftbl); + if (!hi->pool) + { + log_error ("shrinking index table in map_host failed: %s\n", + strerror (errno)); + xfree (reftbl); + } + qsort (reftbl, refidx, sizeof *reftbl, sort_hostpool); + } + else + xfree (reftbl); + } + + hi = hosttable[idx]; + if (hi->pool) + { + /* If the currently selected host is now marked dead, force a + re-selection . */ + if (hi->poolidx >= 0 && hi->poolidx < hosttable_size + && hosttable[hi->poolidx] && hosttable[hi->poolidx]->dead) + hi->poolidx = -1; + + /* Select a host if needed. */ + if (hi->poolidx == -1) + { + hi->poolidx = select_random_host (hi->pool); + if (hi->poolidx == -1) + { + log_error ("no alive host found in pool `%s'\n", name); + return NULL; + } + } + + assert (hi->poolidx >= 0 && hi->poolidx < hosttable_size); + hi = hosttable[hi->poolidx]; + assert (hi); + } + + if (hi->dead) + { + log_error ("host `%s' marked as dead\n", hi->name); + return NULL; + } + + return xtrystrdup (hi->name); +} + + +/* Mark the host NAME as dead. */ +static void +mark_host_dead (const char *name) +{ + hostinfo_t hi; + int idx; + + if (!name || !*name || !strcmp (name, "localhost")) + return; + + idx = find_hostinfo (name); + if (idx == -1) + return; + hi = hosttable[idx]; + log_info ("marking host `%s' as dead%s\n", hi->name, hi->dead? " (again)":""); + hi->dead = 1; +} + + +/* Debug function to print the entire hosttable. */ +void +ks_hkp_print_hosttable (void) +{ + int idx, idx2; + hostinfo_t hi; + + for (idx=0; idx < hosttable_size; idx++) + if ((hi=hosttable[idx])) + { + log_info ("hosttable %3d %s %s %s %s\n", + idx, hi->v4? "4":" ", hi->v6? "6":" ", + hi->dead? "d":" ", hi->name); + if (hi->pool) + { + log_info (" -->"); + for (idx2=0; hi->pool[idx2] != -1; idx2++) + { + log_printf (" %d", hi->pool[idx2]); + if (hi->poolidx == idx2) + log_printf ("*"); + } + log_printf ("\n"); + /* for (idx2=0; hi->pool[idx2] != -1; idx2++) */ + /* log_info (" (%s)\n", */ + /* hosttable[hi->pool[idx2]]->name); */ + } + } +} + + + /* Print a help output for the schemata supported by this module. */ gpg_error_t ks_hkp_help (ctrl_t ctrl, parsed_uri_t uri) @@ -57,6 +418,44 @@ ks_hkp_help (ctrl_t ctrl, parsed_uri_t uri) } +/* Build the remote part or the URL from SCHEME, HOST and an optional + PORT. Returns an allocated string or NULL on failure and sets + ERRNO. */ +static char * +make_host_part (const char *scheme, const char *host, unsigned short port) +{ + char portstr[10]; + char *hostname; + char *hostport; + + /* Map scheme and port. */ + if (!strcmp (scheme, "hkps") || !strcmp (scheme,"https")) + { + scheme = "https"; + strcpy (portstr, "443"); + } + else /* HKP or HTTP. */ + { + scheme = "http"; + strcpy (portstr, "11371"); + } + if (port) + snprintf (portstr, sizeof portstr, "%hu", port); + else + { + /*fixme_do_srv_lookup ()*/ + } + + hostname = map_host (host); + if (!hostname) + return NULL; + + hostport = strconcat (scheme, "://", hostname, ":", portstr, NULL); + xfree (hostname); + return hostport; +} + + /* Send an HTTP request. On success returns an estream object at R_FP. HOSTPORTSTR is only used for diagnostics. If POST_CB is not NULL a post request is used and that callback is called to allow @@ -73,6 +472,7 @@ send_request (ctrl_t ctrl, const char *request, const char *hostportstr, char *request_buffer = NULL; *r_fp = NULL; + return gpg_error (GPG_ERR_NOT_SUPPORTED); once_more: err = http_open (&http, post_cb? HTTP_REQ_POST : HTTP_REQ_GET, @@ -244,8 +644,6 @@ ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, gpg_error_t err; KEYDB_SEARCH_DESC desc; char fprbuf[2+40+1]; - const char *scheme; - char portstr[10]; char *hostport = NULL; char *request = NULL; estream_t fp = NULL; @@ -289,29 +687,11 @@ ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, return gpg_error (GPG_ERR_INV_USER_ID); } - /* Map scheme and port. */ - if (!strcmp (uri->scheme,"hkps") || !strcmp (uri->scheme,"https")) - { - scheme = "https"; - strcpy (portstr, "443"); - } - else /* HKP or HTTP. */ - { - scheme = "http"; - strcpy (portstr, "11371"); - } - if (uri->port) - snprintf (portstr, sizeof portstr, "%hu", uri->port); - else - {} /*fixme_do_srv_lookup ()*/ - /* Build the request string. */ { char *searchkey; - hostport = strconcat (scheme, "://", - *uri->host? uri->host: "localhost", - ":", portstr, NULL); + hostport = make_host_part (uri->scheme, uri->host, uri->port); if (!hostport) { err = gpg_error_from_syserror (); @@ -382,8 +762,6 @@ ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp) gpg_error_t err; KEYDB_SEARCH_DESC desc; char kidbuf[8+1]; - const char *scheme; - char portstr[10]; char *hostport = NULL; char *request = NULL; estream_t fp = NULL; @@ -416,43 +794,23 @@ ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp) return gpg_error (GPG_ERR_INV_USER_ID); } - /* Map scheme and port. */ - if (!strcmp (uri->scheme,"hkps") || !strcmp (uri->scheme,"https")) - { - scheme = "https"; - strcpy (portstr, "443"); - } - else /* HKP or HTTP. */ - { - scheme = "http"; - strcpy (portstr, "11371"); - } - if (uri->port) - snprintf (portstr, sizeof portstr, "%hu", uri->port); - else - {} /*fixme_do_srv_lookup ()*/ - /* Build the request string. */ - { - hostport = strconcat (scheme, "://", - *uri->host? uri->host: "localhost", - ":", portstr, NULL); - if (!hostport) - { - err = gpg_error_from_syserror (); - goto leave; - } + hostport = make_host_part (uri->scheme, uri->host, uri->port); + if (!hostport) + { + err = gpg_error_from_syserror (); + goto leave; + } - request = strconcat (hostport, - "/pks/lookup?op=get&options=mr&search=0x", - kidbuf, - NULL); - if (!request) - { - err = gpg_error_from_syserror (); - goto leave; - } - } + request = strconcat (hostport, + "/pks/lookup?op=get&options=mr&search=0x", + kidbuf, + NULL); + if (!request) + { + err = gpg_error_from_syserror (); + goto leave; + } /* Send the request. */ err = send_request (ctrl, request, hostport, NULL, NULL, &fp); @@ -507,8 +865,6 @@ gpg_error_t ks_hkp_put (ctrl_t ctrl, parsed_uri_t uri, const void *data, size_t datalen) { gpg_error_t err; - const char *scheme; - char portstr[10]; char *hostport = NULL; char *request = NULL; estream_t fp = NULL; @@ -517,22 +873,6 @@ ks_hkp_put (ctrl_t ctrl, parsed_uri_t uri, const void *data, size_t datalen) parm.datastring = NULL; - /* Map scheme and port. */ - if (!strcmp (uri->scheme,"hkps") || !strcmp (uri->scheme,"https")) - { - scheme = "https"; - strcpy (portstr, "443"); - } - else /* HKP or HTTP. */ - { - scheme = "http"; - strcpy (portstr, "11371"); - } - if (uri->port) - snprintf (portstr, sizeof portstr, "%hu", uri->port); - else - {} /*fixme_do_srv_lookup ()*/ - err = armor_data (&armored, data, datalen); if (err) goto leave; @@ -547,9 +887,7 @@ ks_hkp_put (ctrl_t ctrl, parsed_uri_t uri, const void *data, size_t datalen) armored = NULL; /* Build the request string. */ - hostport = strconcat (scheme, "://", - *uri->host? uri->host: "localhost", - ":", portstr, NULL); + hostport = make_host_part (uri->scheme, uri->host, uri->port); if (!hostport) { err = gpg_error_from_syserror (); diff --git a/dirmngr/ks-engine.h b/dirmngr/ks-engine.h index 8b5514473..cda31a735 100644 --- a/dirmngr/ks-engine.h +++ b/dirmngr/ks-engine.h @@ -27,6 +27,7 @@ gpg_error_t ks_print_help (ctrl_t ctrl, const char *text); /*-- ks-engine-hkp.c --*/ +void ks_hkp_print_hosttable (void); gpg_error_t ks_hkp_help (ctrl_t ctrl, parsed_uri_t uri); gpg_error_t ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, estream_t *r_fp); diff --git a/dirmngr/server.c b/dirmngr/server.c index 1a244c896..76d36c132 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -42,6 +42,7 @@ #include "misc.h" #include "ldap-wrapper.h" #include "ks-action.h" +#include "ks-engine.h" /* (ks_hkp_print_hosttable) */ /* To avoid DoS attacks we limit the size of a certificate to something reasonable. */ @@ -1374,12 +1375,13 @@ cmd_keyserver (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; - int clear_flag, add_flag, help_flag; + int clear_flag, add_flag, help_flag, host_flag; uri_item_t item = NULL; /* gcc 4.4.5 is not able to detect that it is always initialized. */ clear_flag = has_option (line, "--clear"); help_flag = has_option (line, "--help"); + host_flag = has_option (line, "--print-hosttable"); line = skip_options (line); add_flag = !!*line; @@ -1389,6 +1391,13 @@ cmd_keyserver (assuan_context_t ctx, char *line) goto leave; } + if (host_flag) + { + ks_hkp_print_hosttable (); + err = 0; + goto leave; + } + if (add_flag) { item = xtrymalloc (sizeof *item + strlen (line));