1 /* domaininfo.c - Gather statistics about accessed domains
2 * Copyright (C) 2017 Werner Koch
4 * This file is part of GnuPG.
6 * GnuPG is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
11 * GnuPG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, see <https://www.gnu.org/licenses/>.
19 * SPDX-License-Identifier: GPL-3.0+
29 /* Number of bucket for the hash array and limit for the length of a
30 * bucket chain. For debugging values of 13 and 10 are more suitable
32 * for j in a b c d e f g h i j k l m n o p q r s t u v w z y z; do \
33 * for i in a b c d e f g h i j k l m n o p q r s t u v w z y z; do \
34 * gpg-connect-agent --dirmngr "wkd_get foo@$i.$j.gnupg.net" /bye \
35 * >/dev/null ; done; done
36 * will quickly add a couple of domains.
38 #define NO_OF_DOMAINBUCKETS 103
39 #define MAX_DOMAINBUCKET_LEN 20
42 /* Object to keep track of a domain name. */
45 struct domaininfo_s *next;
46 unsigned int no_name:1; /* Domain name not found. */
47 unsigned int wkd_not_found:1; /* A WKD query failed. */
48 unsigned int wkd_supported:1; /* One WKD entry was found. */
49 unsigned int wkd_not_supported:1; /* Definitely does not support WKD. */
50 unsigned int keepmark:1; /* Private to insert_or_update(). */
53 typedef struct domaininfo_s *domaininfo_t;
55 /* And the hashed array. */
56 static domaininfo_t domainbuckets[NO_OF_DOMAINBUCKETS];
59 /* The hash function we use. Must not call a system function. */
61 hash_domain (const char *domain)
63 const unsigned char *s = (const unsigned char*)domain;
71 hashval = (hashval << 4) + *s;
72 if ((carry = (hashval & 0xf0000000)))
74 hashval ^= (carry >> 24);
79 return hashval % NO_OF_DOMAINBUCKETS;
84 domaininfo_print_stats (ctrl_t ctrl)
88 int count, no_name, wkd_not_found, wkd_supported, wkd_not_supported;
89 int len, minlen, maxlen;
91 count = no_name = wkd_not_found = wkd_supported = wkd_not_supported = 0;
94 for (bidx = 0; bidx < NO_OF_DOMAINBUCKETS; bidx++)
97 for (di = domainbuckets[bidx]; di; di = di->next)
103 if (di->wkd_not_found)
105 if (di->wkd_supported)
107 if (di->wkd_not_supported)
112 if (minlen == -1 || len < minlen)
116 (ctrl, "domaininfo: items=%d chainlen=%d..%d nn=%d nf=%d ns=%d s=%d\n",
118 minlen > 0? minlen : 0,
120 no_name, wkd_not_found, wkd_not_supported, wkd_supported);
124 /* Return true if DOMAIN definitely does not support WKD. Note that
125 * DOMAIN is expected to be lowercase. */
127 domaininfo_is_wkd_not_supported (const char *domain)
131 for (di = domainbuckets[hash_domain (domain)]; di; di = di->next)
132 if (!strcmp (di->name, domain))
133 return !!di->wkd_not_supported;
135 return 0; /* We don't know. */
139 /* Core update function. DOMAIN is expected to be lowercase.
140 * CALLBACK is called to update the existing or the newly inserted
143 insert_or_update (const char *domain,
144 void (*callback)(domaininfo_t di, int insert_mode))
148 domaininfo_t drop = NULL;
149 domaininfo_t drop_extra = NULL;
155 hash = hash_domain (domain);
156 for (di = domainbuckets[hash]; di; di = di->next)
157 if (!strcmp (di->name, domain))
159 callback (di, 0); /* Update */
163 di_new = xtrycalloc (1, sizeof *di + strlen (domain));
165 return; /* Out of core - we ignore this. */
166 strcpy (di_new->name, domain);
168 /* Need to do another lookup because the malloc is a system call and
169 * thus the hash array may have been changed by another thread. */
170 for (count=0, di = domainbuckets[hash]; di; di = di->next, count++)
171 if (!strcmp (di->name, domain))
173 callback (di, 0); /* Update */
178 /* Before we insert we need to check whether the chain gets too long. */
179 if (count >= MAX_DOMAINBUCKET_LEN)
184 domaininfo_t keep = NULL;
186 /* Unlink from the global list before doing a syscall. */
187 bucket = domainbuckets[hash];
188 domainbuckets[hash] = NULL;
190 array = xtrycalloc (count, sizeof *array);
193 /* That's bad; give up the entire bucket. */
194 log_error ("domaininfo: error allocating helper array: %s\n",
195 gpg_strerror (gpg_err_code_from_syserror ()));
201 /* Move all items into an array for easier processing. */
202 for (di = bucket; di; di = di->next)
203 array[narray++] = di;
204 log_assert (narray == count);
206 /* Mark all item in the array which are flagged to support wkd
207 * but not more than half of the maximum. This way we will at
208 * the end drop half of the items. */
210 for (idx=0; idx < narray; idx++)
213 di->keepmark = 0; /* Clear flag here on the first pass. */
214 if (di->wkd_supported && count < MAX_DOMAINBUCKET_LEN/2)
220 /* Now mark those which are marked as not found. */
221 /* FIXME: we should use an LRU algorithm here. */
222 for (idx=0; idx < narray; idx++)
226 && di->wkd_not_supported && count < MAX_DOMAINBUCKET_LEN/2)
233 /* Build a bucket list and a second list for later freeing the
234 * items (we can't do it directly because a free is a system
235 * call and we want to avoid locks in this module. Note that
236 * the kept items will be reversed order which does not matter. */
237 for (idx=0; idx < narray; idx++)
254 /* In case another thread added new stuff to the domain list we
255 * simply drop them instead all. It would also be possible to
256 * append them to our list but then we can't guarantee that a
257 * bucket list is almost all of the time limited to
258 * MAX_DOMAINBUCKET_LEN. Not sure whether this is really a
259 * sensible strategy. */
260 drop_extra = domainbuckets[hash];
261 domainbuckets[hash] = keep;
265 callback (di_new, 1);
267 di->next = domainbuckets[hash];
268 domainbuckets[hash] = di;
270 if (opt.verbose && (nkept || ndropped))
271 log_info ("domaininfo: bucket=%lu kept=%d purged=%d\n",
272 (unsigned long)hash, nkept, ndropped);
275 /* Remove the dropped items. */
284 di = drop_extra->next;
291 /* Helper for domaininfo_set_no_name. May not do any syscalls. */
293 set_no_name_cb (domaininfo_t di, int insert_mode)
298 /* Obviously the domain is in this case also not supported. */
299 di->wkd_not_supported = 1;
301 /* The next should already be 0 but we clear it anyway in the case
302 * of a temporary DNS failure. */
303 di->wkd_supported = 0;
307 /* Mark DOMAIN as not existent. */
309 domaininfo_set_no_name (const char *domain)
311 insert_or_update (domain, set_no_name_cb);
315 /* Helper for domaininfo_set_wkd_supported. May not do any syscalls. */
317 set_wkd_supported_cb (domaininfo_t di, int insert_mode)
321 di->wkd_supported = 1;
322 /* The next will already be set unless the domain enabled WKD in the
323 * meantime. Thus we need to clear it. */
324 di->wkd_not_supported = 0;
328 /* Mark DOMAIN as supporting WKD. */
330 domaininfo_set_wkd_supported (const char *domain)
332 insert_or_update (domain, set_wkd_supported_cb);
336 /* Helper for domaininfo_set_wkd_not_supported. May not do any syscalls. */
338 set_wkd_not_supported_cb (domaininfo_t di, int insert_mode)
342 di->wkd_not_supported = 1;
343 di->wkd_supported = 0;
347 /* Mark DOMAIN as not supporting WKD queries (e.g. no policy file). */
349 domaininfo_set_wkd_not_supported (const char *domain)
351 insert_or_update (domain, set_wkd_not_supported_cb);
356 /* Helper for domaininfo_set_wkd_not_found. May not do any syscalls. */
358 set_wkd_not_found_cb (domaininfo_t di, int insert_mode)
360 /* Set the not found flag but there is no need to do this if we
361 * already know that the domain either does not support WKD or we
362 * know that it supports WKD. */
364 di->wkd_not_found = 1;
365 else if (!di->wkd_not_supported && !di->wkd_supported)
366 di->wkd_not_found = 1;
368 /* Better clear this flag in case we had a DNS failure in the
374 /* Update a counter for DOMAIN to keep track of failed WKD queries. */
376 domaininfo_set_wkd_not_found (const char *domain)
378 insert_or_update (domain, set_wkd_not_found_cb);