From 146638ab9423f1d83bfa601f1b40cd04df29dc4c Mon Sep 17 00:00:00 2001
From: Melinda Shore
Date: Wed, 28 Jun 2017 22:11:30 -0800
Subject: [PATCH 01/10] Modified Dockerfile to check out getdns master and to
use unbound-anchor to install dnssec trust root
---
src/tools/Dockerfile | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/tools/Dockerfile b/src/tools/Dockerfile
index 9ae731a3..20bde4c5 100644
--- a/src/tools/Dockerfile
+++ b/src/tools/Dockerfile
@@ -24,7 +24,7 @@ RUN set -ex \
&& cd /usr/src \
&& git clone https://github.com/getdnsapi/getdns.git \
&& cd /usr/src/getdns \
- && git checkout release/1.1.1 \
+ && git checkout master \
&& git submodule update --init \
&& libtoolize -ci \
&& autoreconf -fi \
@@ -35,7 +35,7 @@ RUN set -ex \
&& cp src/tools/stubby.conf /etc \
&& mkdir -p /etc/unbound \
&& cd /etc/unbound \
- && wget http://www.nomountain.net/getdns-root.key
+ && unbound-anchor -a /etc/unbound/getdns-root.key || :
EXPOSE 53
From c5acb3769b3267c515b939de067189a1226f57bd Mon Sep 17 00:00:00 2001
From: Willem Toorop
Date: Thu, 6 Jul 2017 21:28:34 +0200
Subject: [PATCH 02/10] Exit with error when answers were bogus
---
src/tools/getdns_query.c | 33 ++++++++++++++++++++++++++++++---
1 file changed, 30 insertions(+), 3 deletions(-)
diff --git a/src/tools/getdns_query.c b/src/tools/getdns_query.c
index 58fb70c6..f237585c 100644
--- a/src/tools/getdns_query.c
+++ b/src/tools/getdns_query.c
@@ -81,6 +81,8 @@ static uint16_t request_type = GETDNS_RRTYPE_NS;
static int timeout, edns0_size, padding_blocksize;
static int async = 0, interactive = 0;
static enum { GENERAL, ADDRESS, HOSTNAME, SERVICE } calltype = GENERAL;
+static int bogus_answers = 0;
+static int check_dnssec = 0;
static int get_rrtype(const char *t)
{
@@ -317,6 +319,7 @@ static getdns_return_t validate_chain(getdns_dict *response)
break;
case GETDNS_DNSSEC_BOGUS:
if (verbosity) fprintf(stdout, "GETDNS_DNSSEC_BOGUS\n");
+ bogus_answers += 1;
break;
case GETDNS_DNSSEC_INDETERMINATE:
if (verbosity) fprintf(stdout, "GETDNS_DNSSEC_INDETERMINATE\n");
@@ -346,6 +349,7 @@ static getdns_return_t validate_chain(getdns_dict *response)
break;
case GETDNS_DNSSEC_BOGUS:
if (verbosity) fprintf(stdout, "GETDNS_DNSSEC_BOGUS\n");
+ bogus_answers += 1;
break;
case GETDNS_DNSSEC_INDETERMINATE:
if (verbosity) fprintf(stdout, "GETDNS_DNSSEC_INDETERMINATE\n");
@@ -389,6 +393,14 @@ void callback(getdns_context *context, getdns_callback_type_t callback_type,
if (callback_type == GETDNS_CALLBACK_COMPLETE) {
if (verbosity) printf("Response code was: GOOD. Status was: Callback with ID %"PRIu64" was successful.\n",
trans_id);
+ if (check_dnssec) {
+ uint32_t dnssec_status = GETDNS_DNSSEC_SECURE;
+
+ (void )getdns_dict_get_int(response,
+ "/replies_tree/0/dnssec_status", &dnssec_status);
+ if (dnssec_status == GETDNS_DNSSEC_BOGUS)
+ bogus_answers += 1;
+ }
} else if (callback_type == GETDNS_CALLBACK_CANCEL)
fprintf(stderr,
@@ -403,7 +415,6 @@ void callback(getdns_context *context, getdns_callback_type_t callback_type,
getdns_get_errorstr_by_id(callback_type));
}
getdns_dict_destroy(response);
- response = NULL;
}
#define CONTINUE ((getdns_return_t)-2)
@@ -578,6 +589,9 @@ getdns_return_t parse_args(int argc, char **argv)
continue;
} else if (arg[0] == '+') {
+ if (strncmp(arg+1, "dnssec_", 7) == 0)
+ check_dnssec = 1;
+
if (arg[1] == 's' && arg[2] == 'i' && arg[3] == 't' &&
(arg[4] == '=' || arg[4] == '\0')) {
if ((r = set_cookie(extensions, arg+4))) {
@@ -1196,6 +1210,7 @@ getdns_return_t do_the_call(void)
fprintf( stdout, "%s\n", response_str);
if (verbosity) fprintf( stdout, "SYNC call completed.\n");
+
validate_chain(response);
free(response_str);
} else {
@@ -1208,8 +1223,18 @@ getdns_return_t do_the_call(void)
if (verbosity)
fprintf(stdout, "Response code was: GOOD. Status was: %s\n",
getdns_get_errorstr_by_id(status));
- if (response)
+ if (response) {
+ if (check_dnssec) {
+ uint32_t dnssec_status = GETDNS_DNSSEC_SECURE;
+
+ (void )getdns_dict_get_int(response,
+ "/replies_tree/0/dnssec_status",
+ &dnssec_status);
+ if (dnssec_status == GETDNS_DNSSEC_BOGUS)
+ bogus_answers += 1;
+ }
getdns_dict_destroy(response);
+ }
}
getdns_dict_destroy(address);
return r;
@@ -1790,5 +1815,7 @@ done_destroy_context:
if (!i_am_stubby && verbosity)
fprintf(stdout, "\nAll done.\n");
- return r;
+ return r ? r
+ : bogus_answers ? GETDNS_DNSSEC_BOGUS
+ : GETDNS_RETURN_GOOD;
}
From bceb6c8c87487561bf0632ca78db29c87fe3007f Mon Sep 17 00:00:00 2001
From: Willem Toorop
Date: Sat, 15 Jul 2017 11:14:35 +0200
Subject: [PATCH 03/10] Resubmit netreqs when roadblocks need to be avoided
---
src/dnssec.c | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/src/dnssec.c b/src/dnssec.c
index fbbc966d..0a1133dc 100644
--- a/src/dnssec.c
+++ b/src/dnssec.c
@@ -3043,6 +3043,37 @@ static void check_chain_complete(chain_head *chain)
netreq->owner = dnsreq;
r = _getdns_submit_netreq(netreq, &now_ms);
}
+ if (!dnsreq->dnssec_return_validation_chain)
+ return;
+
+ for ( head = chain; head ; head = next ) {
+ next = head->next;
+ for ( node_count = head->node_count
+ , node = head->parent
+ ; node_count
+ ; node_count--, node = node->parent ) {
+
+ if (node->dnskey_req) {
+ _getdns_netreq_change_state(
+ node->dnskey_req,
+ NET_REQ_NOT_SENT);
+ node->dnskey_req->owner->
+ avoid_dnssec_roadblocks = 1;
+ r = _getdns_submit_netreq(
+ node->dnskey_req, &now_ms);
+ }
+ if (node->ds_req) {
+ _getdns_netreq_change_state(
+ node->ds_req, NET_REQ_NOT_SENT);
+ node->ds_req->owner->
+ avoid_dnssec_roadblocks = 1;
+ r = _getdns_submit_netreq(
+ node->ds_req, &now_ms);
+ }
+ }
+ }
+ DEBUG_SEC("Outstanding requests: %d\n",
+ (int)count_outstanding_requests(chain));
return;
}
#endif
From 84430e02cdae5bae3b128351ec9ca470ad7cdb9f Mon Sep 17 00:00:00 2001
From: Willem Toorop
Date: Sat, 15 Jul 2017 17:48:24 +0200
Subject: [PATCH 04/10] Actually working roadblocks and getting validation
chains
---
src/dnssec.c | 42 +++++++++++++++++++++++++++++++++++-------
src/general.c | 7 +++++++
2 files changed, 42 insertions(+), 7 deletions(-)
diff --git a/src/dnssec.c b/src/dnssec.c
index 0a1133dc..d694775a 100644
--- a/src/dnssec.c
+++ b/src/dnssec.c
@@ -550,11 +550,26 @@ static chain_head *add_rrset2val_chain(struct mem_funcs *mf,
/* Also, try to prevent adding double rrsets */
if ( rrset->rr_class == head->rrset.rr_class
&& rrset->rr_type == head->rrset.rr_type
- && rrset->pkt == head->rrset.pkt
- && rrset->pkt_len == head->rrset.pkt_len
- && _dname_equal(rrset->name, head->rrset.name))
- return NULL;
+ && _dname_equal(rrset->name, head->rrset.name)) {
+ if (rrset->pkt == head->rrset.pkt &&
+ rrset->pkt_len == head->rrset.pkt_len)
+ return NULL;
+ else {
+ /* Anticipate resubmissions due to
+ * roadblock avoidance */
+ head->rrset.pkt = rrset->pkt;
+ head->rrset.pkt_len = rrset->pkt_len;
+ return head;
+ }
+ }
+
+ if ( rrset->rr_class == head->rrset.rr_class
+ && rrset->rr_type == head->rrset.rr_type
+ && rrset->pkt != head->rrset.pkt
+ && _dname_equal(rrset->name, head->rrset.name)) {
+ return NULL;
+ }
for (label = labels; label < last_label; label++) {
if (! _dname_is_parent(*label, head->rrset.name))
break;
@@ -2416,6 +2431,7 @@ static int key_proves_nonexistance(
* ========================+
* First find the closest encloser.
*/
+ if (*rrset->name)
for ( nc_name = rrset->name, ce_name = rrset->name + *rrset->name + 1
; *ce_name ; nc_name = ce_name, ce_name += *ce_name + 1) {
@@ -3034,14 +3050,18 @@ static void check_chain_complete(chain_head *chain)
uint64_t now_ms = 0;
dnsreq->avoid_dnssec_roadblocks = 1;
+ dnsreq->chain->lock += 1;
for ( netreq_p = dnsreq->netreqs
- ; !r && (netreq = *netreq_p)
+ ; (netreq = *netreq_p)
; netreq_p++) {
_getdns_netreq_change_state(netreq, NET_REQ_NOT_SENT);
+ netreq->dnssec_status =
+ GETDNS_DNSSEC_INDETERMINATE;
netreq->owner = dnsreq;
r = _getdns_submit_netreq(netreq, &now_ms);
+ DEBUG_SEC("Resubmitting main netreq returned: %d\n", r);
}
if (!dnsreq->dnssec_return_validation_chain)
return;
@@ -3216,11 +3236,16 @@ void _getdns_get_validation_chain(getdns_dns_req *dnsreq)
getdns_network_req *netreq, **netreq_p;
chain_head *chain = NULL, *chain_p;
- if (dnsreq->validating)
+ if (dnsreq->avoid_dnssec_roadblocks) {
+ chain = dnsreq->chain;
+
+ } else if (dnsreq->validating)
return;
dnsreq->validating = 1;
- for (netreq_p = dnsreq->netreqs; (netreq = *netreq_p) ; netreq_p++) {
+ if (dnsreq->avoid_dnssec_roadblocks && chain->lock == 0)
+ ; /* pass */
+ else for (netreq_p = dnsreq->netreqs; (netreq = *netreq_p) ; netreq_p++) {
if (! netreq->response
|| netreq->response_len < GLDNS_HEADER_SIZE
|| ( GLDNS_RCODE_WIRE(netreq->response)
@@ -3248,6 +3273,9 @@ void _getdns_get_validation_chain(getdns_dns_req *dnsreq)
if (chain_p->lock) chain_p->lock--;
}
dnsreq->chain = chain;
+ if (dnsreq->avoid_dnssec_roadblocks && chain->lock)
+ chain->lock -= 1;
+
check_chain_complete(chain);
} else {
dnsreq->validating = 0;
diff --git a/src/general.c b/src/general.c
index 280df08d..2420a47c 100644
--- a/src/general.c
+++ b/src/general.c
@@ -59,6 +59,9 @@ void _getdns_call_user_callback(getdns_dns_req *dnsreq, getdns_dict *response)
{
_getdns_context_clear_outbound_request(dnsreq);
+#if defined(REQ_DEBUG) && REQ_DEBUG
+ debug_req(__FUNC__, *dnsreq->netreqs);
+#endif
if (dnsreq->user_callback) {
dnsreq->context->processing = 1;
dnsreq->user_callback(dnsreq->context,
@@ -211,6 +214,7 @@ _getdns_check_dns_req_complete(getdns_dns_req *dns_req)
#ifdef STUB_NATIVE_DNSSEC
|| (dns_req->context->resolution_type == GETDNS_RESOLUTION_STUB
+ && !dns_req->avoid_dnssec_roadblocks
&& (dns_req->dnssec_return_status ||
dns_req->dnssec_return_only_secure ||
dns_req->dnssec_return_all_statuses
@@ -228,6 +232,9 @@ _getdns_check_dns_req_complete(getdns_dns_req *dns_req)
NULL, NULL, (getdns_eventloop_callback)
_getdns_validation_chain_timeout));
+#if defined(REQ_DEBUG) && REQ_DEBUG
+ debug_req("getting validation chain for ", *dns_req->netreqs);
+#endif
_getdns_get_validation_chain(dns_req);
} else
_getdns_call_user_callback(
From e11dc92df1d76f840a2928fbe06b253bae3abdc4 Mon Sep 17 00:00:00 2001
From: Willem Toorop
Date: Sat, 15 Jul 2017 18:38:31 +0200
Subject: [PATCH 05/10] Hopefully the last warning
---
src/dnssec.c | 10 +++-------
1 file changed, 3 insertions(+), 7 deletions(-)
diff --git a/src/dnssec.c b/src/dnssec.c
index d694775a..e9962852 100644
--- a/src/dnssec.c
+++ b/src/dnssec.c
@@ -3045,7 +3045,6 @@ static void check_chain_complete(chain_head *chain)
&& !dnsreq->avoid_dnssec_roadblocks
&& dnsreq->netreqs[0]->dnssec_status == GETDNS_DNSSEC_BOGUS) {
- int r = GETDNS_RETURN_GOOD;
getdns_network_req **netreq_p, *netreq;
uint64_t now_ms = 0;
@@ -3060,8 +3059,7 @@ static void check_chain_complete(chain_head *chain)
netreq->dnssec_status =
GETDNS_DNSSEC_INDETERMINATE;
netreq->owner = dnsreq;
- r = _getdns_submit_netreq(netreq, &now_ms);
- DEBUG_SEC("Resubmitting main netreq returned: %d\n", r);
+ (void) _getdns_submit_netreq(netreq, &now_ms);
}
if (!dnsreq->dnssec_return_validation_chain)
return;
@@ -3079,7 +3077,7 @@ static void check_chain_complete(chain_head *chain)
NET_REQ_NOT_SENT);
node->dnskey_req->owner->
avoid_dnssec_roadblocks = 1;
- r = _getdns_submit_netreq(
+ (void) _getdns_submit_netreq(
node->dnskey_req, &now_ms);
}
if (node->ds_req) {
@@ -3087,13 +3085,11 @@ static void check_chain_complete(chain_head *chain)
node->ds_req, NET_REQ_NOT_SENT);
node->ds_req->owner->
avoid_dnssec_roadblocks = 1;
- r = _getdns_submit_netreq(
+ (void) _getdns_submit_netreq(
node->ds_req, &now_ms);
}
}
}
- DEBUG_SEC("Outstanding requests: %d\n",
- (int)count_outstanding_requests(chain));
return;
}
#endif
From 2d7d6581b4ac7d3cfaff415c1abf1dbc4027b479 Mon Sep 17 00:00:00 2001
From: Neil Cook
Date: Mon, 31 Jul 2017 22:48:09 +0100
Subject: [PATCH 06/10] Ensure netreq->fd is set to -1 after
close()/closesocket()
If netreq->fd is not set to -1, then multiple functions close the
same socket. This causes major issues in multithread code where the
socket must not be closed multiple times as it may be owned by a
different thread.
---
src/stub.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/stub.c b/src/stub.c
index c6f25c79..2bff7ae9 100644
--- a/src/stub.c
+++ b/src/stub.c
@@ -588,6 +588,7 @@ _getdns_cancel_stub_request(getdns_network_req *netreq)
#else
close(netreq->fd);
#endif
+ netreq->fd = -1;
}
}
@@ -606,6 +607,7 @@ stub_timeout_cb(void *userarg)
#else
close(netreq->fd);
#endif
+ netreq->fd = -1;
netreq->upstream->udp_timeouts++;
if (netreq->upstream->udp_timeouts % 100 == 0)
_getdns_upstream_log(netreq->upstream, GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_DEBUG,
@@ -1413,6 +1415,7 @@ stub_udp_read_cb(void *userarg)
#else
close(netreq->fd);
#endif
+ netreq->fd = -1;
stub_next_upstream(netreq);
}
netreq->debug_end_time = _getdns_get_time_as_uintt64();
@@ -1435,8 +1438,8 @@ stub_udp_read_cb(void *userarg)
closesocket(netreq->fd);
#else
close(netreq->fd);
- netreq->fd = -1;
#endif
+ netreq->fd = -1;
while (GLDNS_TC_WIRE(netreq->response)) {
DEBUG_STUB("%s %-35s: MSG: %p TC bit set in response \n", STUB_DEBUG_READ,
__FUNC__, (void*)netreq);
@@ -1533,6 +1536,7 @@ stub_udp_write_cb(void *userarg)
#else
close(netreq->fd);
#endif
+ netreq->fd = -1;
stub_next_upstream(netreq);
}
netreq->debug_end_time = _getdns_get_time_as_uintt64();
From 1555c432f5e637ef16b95e1ab7dd5e7adfaff13b Mon Sep 17 00:00:00 2001
From: Neil Cook
Date: Mon, 31 Jul 2017 22:51:24 +0100
Subject: [PATCH 07/10] Fix array bounds bug in upstream_select
---
src/stub.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/stub.c b/src/stub.c
index 2bff7ae9..b8ac6710 100644
--- a/src/stub.c
+++ b/src/stub.c
@@ -1962,7 +1962,7 @@ upstream_select(getdns_network_req *netreq)
return &upstreams->upstreams[i];
}
i+=GETDNS_UPSTREAM_TRANSPORTS;
- if (i > upstreams->count)
+ if (i >= upstreams->count)
i = 0;
} while (i != upstreams->current_udp);
From 05016e3a3abd8af9e73a0447f1739a1e1fa59371 Mon Sep 17 00:00:00 2001
From: Sara Dickinson
Date: Fri, 4 Aug 2017 14:17:50 +0100
Subject: [PATCH 08/10] Remove mention of using the second bit in set_from_os
to init OpenSSL as this no longer applies
---
src/getdns/getdns.h.in | 3 ---
1 file changed, 3 deletions(-)
diff --git a/src/getdns/getdns.h.in b/src/getdns/getdns.h.in
index b483c7ea..feba62a1 100644
--- a/src/getdns/getdns.h.in
+++ b/src/getdns/getdns.h.in
@@ -1101,7 +1101,6 @@ getdns_service(getdns_context *context,
* (e.g. CRYPTO_THREADID_set_call) depending on the library version used.
* @param context context that can be used immediately with other API calls
* @param set_from_os set to 1 to initialize the context with os defaults
- * the second bit set (2) prevents OpenSSL library initialization.
* @return GETDNS_RETURN_GOOD on success
*/
getdns_return_t
@@ -1114,7 +1113,6 @@ getdns_context_create(getdns_context ** context, int set_from_os);
* (e.g. CRYPTO_THREADID_set_call) depending on the library version used.
* @param context context that can be used immediately with other API calls
* @param set_from_os set to 1 to initialize the context with os defaults
- * the second bit set (2) prevents OpenSSL library initialization.
* @param malloc custom malloc function
* @param realloc custom realloc function
* @param free custom free function
@@ -1136,7 +1134,6 @@ getdns_context_create_with_memory_functions(
* (e.g. CRYPTO_THREADID_set_call) depending on the library version used.
* @param context context that can be used immediately with other API calls
* @param set_from_os set to 1 to initialize the context with os defaults
- * the second bit set (2) prevents OpenSSL library initialization.
* @param userarg parameter passed to the custom malloc, realloc and free functions
* @param malloc custom malloc function
* @param realloc custom realloc function
From 8311dc904984f37bd4d30af0152c9c0f65c9f038 Mon Sep 17 00:00:00 2001
From: Sara Dickinson
Date: Fri, 4 Aug 2017 14:31:05 +0100
Subject: [PATCH 09/10] =?UTF-8?q?Minor=20updates=20on=20return=5Fcall=5Fre?=
=?UTF-8?q?porting.=20Fix=20the=20name=20in=20the=20API=20spec=20and=20add?=
=?UTF-8?q?=20a=20know=20issue=20that=20it=20isn=E2=80=99t=20fully=20suppo?=
=?UTF-8?q?rted=20in=20recursive=20mode.=20Also=20remove=20known=20issue?=
=?UTF-8?q?=20that=20stub=20doesn=E2=80=99t=20limit=20in=20outstanding=20q?=
=?UTF-8?q?ueries=20as=20this=20is=20now=20supported.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 2 +-
spec/index.html | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index e866d47d..f8ad726c 100644
--- a/README.md
+++ b/README.md
@@ -187,10 +187,10 @@ The following minor implementation omissions are noted:
Recursive mode does not support:
* TLS as a transport
* Non-zero connection idle timeouts or query pipelining
+* Anything other than query_type and resolution_type in the return_call_reporting extension
Stub mode does not support:
* Non zero idle timeouts for synchronous calls
-* Limit on number of outstanding queries
# Known Issues
diff --git a/spec/index.html b/spec/index.html
index a0432b4c..6888489f 100644
--- a/spec/index.html
+++ b/spec/index.html
@@ -875,7 +875,7 @@ names:
query_name
(a bindata) is the name that was sent
query_type
(an int) is the type that was queried for
query_to
(a bindata) is the address to which the query was sent
-run_time
(a bindata) is the difference between the time the successful
+run_time/ms
(a bindata) is the difference between the time the successful
query started and ended in milliseconds, represented
as a uint32_t (this does not include time taken for connection set up
or transport fallback)
From 9d86928900199bebecd0ef2f5c9e9f12a48cc46c Mon Sep 17 00:00:00 2001
From: Sara Dickinson
Date: Fri, 4 Aug 2017 14:50:04 +0100
Subject: [PATCH 10/10] Fix all the outdated links to wiki pages in the
README.md
---
README.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index e866d47d..be70623a 100644
--- a/README.md
+++ b/README.md
@@ -99,7 +99,7 @@ Note: If you only want to build stubby, then use the `--enable-stub-only` and `-
## Extensions and Event loop dependencies
-The implementation works with a variety of event loops, each built as a separate shared library. See [the wiki](https://github.com/getdnsapi/getdns/wiki/Asynchronous-Support#wiki-included-event-loop-integrations) for more details.
+The implementation works with a variety of event loops, each built as a separate shared library. See [this Doxygen page](https://getdnsapi.net/doxygen/group__eventloops.html) and [this man page](https://getdnsapi.net/documentation/manpages/#ASYNCHRONOUS USE) for more details.
* [libevent](http://libevent.org). Note: the examples *require* this and should work with either libevent 1.x or 2.x. 2.x is preferred.
* [libuv](https://github.com/joyent/libuv)
@@ -170,8 +170,8 @@ Non-goals (things we will not be doing at least initially) include:
## Language Bindings
In parallel, the team is actively developing bindings for various languages.
-For more information, visit the
-[wiki](https://github.com/getdnsapi/getdns/wiki/Language-Bindings).
+For more information, visit this
+[webpage](https://getdnsapi.net/bindings/).
## Unsupported getDNS Features
@@ -264,7 +264,7 @@ build the packages; this is simply the one we chose to use.
If you're using [Homebrew](http://brew.sh/), you may run `brew install getdns`. By default, this will only build the core library without any 3rd party event loop support.
-To install the [event loop integration libraries](https://github.com/getdnsapi/getdns/wiki/Asynchronous-Support) that enable support for libevent, libuv, and libev, run: `brew install getdns --with-libevent --with-libuv --with-libev`. All switches are optional.
+To install the [event loop integration libraries](https://getdnsapi.net/doxygen/group__eventloops.html) that enable support for libevent, libuv, and libev, run: `brew install getdns --with-libevent --with-libuv --with-libev`. All switches are optional.
Note that in order to compile the examples, the `--with-libevent` switch is required.