/*
* probe.c - dnssec-trigger DNSSEC probes implementation
*
* Copyright (c) 2011, NLnet Labs. All rights reserved.
*
* This software is open source.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the NLNET LABS nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* \file
*
* This file contains the probe implementation.
*/
#include "config.h"
#include "probe.h"
#include "svr.h"
#include "cfg.h"
#include "log.h"
#include "netevent.h"
#include "net_help.h"
#include "ubhook.h"
#include "reshook.h"
#include "http.h"
#include "update.h"
#include <ldns/ldns.h>
/* create probes for the ip addresses in the string */
static void probe_spawn(const char* ip, int recurse, int dnstcp,
struct ssllist* ssldns, int port);
/* set timeout on outq and create UDP query and send it */
static int outq_settimeout_and_send(struct outq* outq);
/* send outq over tcp */
static int outq_send_tcp(struct outq* outq);
/* a query is done, check probe to see if failed, succeed or wait */
static void probe_partial_done(struct probe_ip* p, const char* in,
const char* reason);
/* a probe is done (fail or success) see global progress */
static void probe_done(struct probe_ip* p);
void probe_start(char* ips)
{
char* next;
struct svr* svr = global_svr;
if(svr->http) {
http_general_delete(svr->http);
svr->http = NULL;
}
if(svr->probes) {
/* clear existing probe list */
probe_list_delete(svr->probes);
svr->probes = NULL;
if(svr->num_probes_done < svr->num_probes) {
verbose(VERB_QUERY, "probes cancelled due to fast "
"net change");
}
svr->num_probes_done = 0;
svr->num_probes = 0;
}
/* spawn a probe for every IP address in the list */
svr->saw_first_working = 0;
svr->saw_direct_work = 0;
svr->saw_dnstcp_work = 0;
svr->probe_direct = 0;
svr->probe_dnstcp = 0;
while(*ips == ' ')
ips++;
while(ips && *ips) {
if((next = strchr(ips, ' ')) != NULL) {
*next++ = 0;
while(*next == ' ')
next++;
}
probe_spawn(ips, 1, 0, 0, DNS_PORT);
ips = next;
}
svr->num_probes_to_cache = svr->num_probes;
/* (if no resulting probes), check result now */
if(!svr->probes)
probe_cache_done();
else if(svr->forced_insecure) {
verbose(VERB_OPS, "probe started: but still forced insecure");
/* call it right away, so the user does not have to wait */
probe_setup_hotspot_signon(svr);
} else {
/* there are cache DNS, and not forced insecure: check HTTP */
if(!svr->http && svr->cfg->num_http_urls != 0) {
svr->http = http_general_start(svr);
if(!svr->http) log_err("out of memory");
svr->http->saw_http_work = 0;
}
}
}
void probe_delete(struct probe_ip* p)
{
if(!p) return;
free(p->name);
free(p->reason);
free(p->http_desc);
SSL_CTX_free(p->sslctx);
outq_delete(p->ds_c);
outq_delete(p->dnskey_c);
outq_delete(p->nsec3_c);
outq_delete(p->host_c);
http_get_delete(p->http);
free(p);
}
void probe_list_delete(struct probe_ip* list)
{
struct probe_ip* p=list, *np;
while(p) {
np = p->next;
probe_delete(p);
p = np;
}
}
/** get random signed TLD */
static const char*
get_random_dest(void)
{
const char* choices[] = { "se.", "uk.", "nl.", "de." };
return choices[ ldns_get_random() % 4 ];
}
/** get random NSEC3 signed TLD */
static const char*
get_random_nsec3_dest(void)
{
const char* choices[] = { "_probe.us.com.", "_probe.uk.com.", "_probe.uk.uk.", "_probe.uk.net." };
return choices[ ldns_get_random() % 4 ];
}
/** the NSEC3 qtype to elicit it (a nodata answer) */
#define PROBE_NSEC3_QTYPE LDNS_RR_TYPE_NULL
/** get random authority server */
static const char*
get_random_auth_ip4(void)
{
/* list of root servers */
const char* choices[] = {
"198.41.0.4", /* a */
"192.228.79.201", /* b */
"192.33.4.12", /* c */
"199.7.91.13", /* d */
"192.203.230.10", /* e */
"192.5.5.241", /* f */
"192.112.36.4", /* g */
"198.97.190.53", /* h */
"192.36.148.17", /* i */
"192.58.128.30", /* j */
"193.0.14.129", /* k */
"199.7.83.42", /* l */
"202.12.27.33" /* m */
};
return choices[ ldns_get_random() % 13 ];
}
/** get random authority server */
static const char*
get_random_auth_ip6(void)
{
/* list of root servers */
const char* choices[] = {
"2001:503:ba3e::2:30", /* a */
"2001:500:200::b", /* b */
"2001:500:2::c", /* c */
"2001:500:2d::d", /* d */
"2001:500:a8::e", /* e */
"2001:500:2f::f", /* f */
"2001:500:12::d0d", /* g */
"2001:500:1::53", /* h */
"2001:7fe::53", /* i */
"2001:503:c27::2:30", /* j */
"2001:7fd::1", /* k */
"2001:500:9f::42", /* l */
"2001:dc3::35" /* m */
};
return choices[ ldns_get_random() % 13 ];
}
static const char* get_random_tcp80_ip4(struct cfg* cfg)
{
if(cfg->num_tcp80_ip4 == 0)
return NULL;
return strlist_get_num(cfg->tcp80_ip4,
((unsigned)ldns_get_random())%((unsigned)cfg->num_tcp80_ip4));
}
static const char* get_random_tcp80_ip6(struct cfg* cfg)
{
if(cfg->num_tcp80_ip6 == 0)
return NULL;
return strlist_get_num(cfg->tcp80_ip6,
((unsigned)ldns_get_random())%((unsigned)cfg->num_tcp80_ip6));
}
static const char* get_random_tcp443_ip4(struct cfg* cfg)
{
if(cfg->num_tcp443_ip4 == 0)
return NULL;
return strlist_get_num(cfg->tcp443_ip4,
((unsigned)ldns_get_random())%((unsigned)cfg->num_tcp443_ip4));
}
static const char* get_random_tcp443_ip6(struct cfg* cfg)
{
if(cfg->num_tcp443_ip6 == 0)
return NULL;
return strlist_get_num(cfg->tcp443_ip6,
((unsigned)ldns_get_random())%((unsigned)cfg->num_tcp443_ip6));
}
static struct ssllist* get_random_ssl443_ip4(struct cfg* cfg)
{
if(cfg->num_ssl443_ip4 == 0)
return NULL;
return ssllist_get_num(cfg->ssl443_ip4,
((unsigned)ldns_get_random())%((unsigned)cfg->num_ssl443_ip4));
}
static struct ssllist* get_random_ssl443_ip6(struct cfg* cfg)
{
if(cfg->num_ssl443_ip6 == 0)
return NULL;
return ssllist_get_num(cfg->ssl443_ip6,
((unsigned)ldns_get_random())%((unsigned)cfg->num_ssl443_ip6));
}
int probe_is_cache(struct probe_ip* p)
{
return !p->to_auth && !p->ssldns && !p->dnstcp && !p->to_http;
}
/** check if hash of x is the given hash, error or NULL on success. */
static char*
match_hash(struct hashlist* he, X509* x)
{
unsigned char hash[EVP_MAX_MD_SIZE];
unsigned int len = (unsigned int)sizeof(hash);
/* always sha256 now */
if(!X509_digest(x, EVP_sha256(), hash, &len)) {
log_err("out of memory");
return "out of memory in X509_digest";
}
if(verbosity >= 3) {
char buf[1024];
char* at = buf;
size_t blen = sizeof(buf);
unsigned int i;
for(i=0; i<len; i++) {
snprintf(at, blen, "%2.2X%s",
(unsigned int)hash[i], (i==len-1)?"":":");
blen -= strlen(at);
at += strlen(at);
}
log_info("the ssl fingerprint of the server is %s", buf);
}
if(len != he->hashlen) {
/* should not happen, since we test sha256 length of
* the cfg hash we read in */
return "SSL certificate hash length is wrong";
}
if(memcmp(hash, he->hash, len) != 0) {
return "SSL certificate on internet does not match stored hash (ssl intercepted?)";
}
return 0;
}
/** check list of hashes, error string or NULL on success */
static char*
match_hashes(struct hashlist* list, X509* x)
{
struct hashlist* h;
char* reason = "Internal error: no hashlist but comparison";
for(h=list; h; h=h->next)
if( (reason=match_hash(h, x)) == NULL)
return NULL;
return reason;
}
/** check SSL certificate on probe (that is otherwise DNSSEC OK) */
static const char*
check_ssl(struct outq* outq)
{
/* verify the ssl connection */
/* there is also the SSL_get_verify_result, but that does PKIX
* verification, we simply check the entire self-signed cert with
* a stored hash, you can compute this hash with the command
* openssl x509 -sha256 -fingerprint -in server.pem */
X509* x = SSL_get_peer_certificate(outq->c->ssl);
if(!outq->probe->ssldns->hashes) {
/* no stored hash */
X509_free(x);
} else if(x) {
char* reason = match_hashes(outq->probe->ssldns->hashes, x);
X509_free(x);
if(reason)
return reason;
} else {
return "No SSL certificate on internet but have stored hash (ssl intercepted?)";
}
/* all OK */
return NULL;
}
/** outq is done, NULL reason for success */
static void
outq_done(struct outq* outq, const char* reason)
{
struct probe_ip* p = outq->probe;
const char* in = NULL;
if(!p) {
selfupdate_outq_done(global_svr->update, outq, NULL, reason);
return;
}
if(p->sslctx && !reason) {
reason = check_ssl(outq);
}
if(p->nsec3_c == outq) {
outq_delete(p->nsec3_c);
p->nsec3_c = NULL;
in = "NSEC3";
} else if(p->host_c == outq) {
http_host_outq_done(p, reason);
return;
} else if(p->ds_c == outq) {
outq_delete(p->ds_c);
p->ds_c = NULL;
in = "DS";
} else {
outq_delete(p->dnskey_c);
p->dnskey_c = NULL;
in = "DNSKEY";
}
/* This is a good place for test code.
if(!p->ssldns && !reason)
reason = "failed for test purposes";
*/
probe_partial_done(p, in, reason);
}
/** test if type is present in authority section of returned packet */
static int
check_type_in_authority(ldns_pkt* p, int t)
{
ldns_rr_list *l = ldns_pkt_rr_list_by_type(p, t,
LDNS_SECTION_AUTHORITY);
if(!l) {
return 0;
}
ldns_rr_list_deep_free(l);
return 1;
}
/** test if type is present in returned packet */
static int
check_type_in_answer(ldns_pkt* p, int t)
{
ldns_rr_list *l = ldns_pkt_rr_list_by_type(p, t, LDNS_SECTION_ANSWER);
if(!l) {
return 0;
}
ldns_rr_list_deep_free(l);
return 1;
}
/** test if the right denial (from parent) is in the returned packet */
static int
check_denial_in_answer(ldns_pkt* p, const char* dname)
{
size_t i;
ldns_rdf* d = ldns_dname_new_frm_str(dname);
ldns_rr_list *l = ldns_pkt_rr_list_by_type(p, LDNS_RR_TYPE_SOA,
LDNS_SECTION_AUTHORITY);
if(!d) {
ldns_rr_list_deep_free(l);
return 0; /* robustness, the name should parse */
}
if(!l) {
ldns_rdf_deep_free(d);
return 0;
}
for(i=0; i<ldns_rr_list_rr_count(l); i++) {
/* note that subdomain test is false if names are equal,
* the SOA must be from a parent server */
if(ldns_dname_is_subdomain(d, ldns_rr_owner(ldns_rr_list_rr(
l, i)))) {
ldns_rr_list_deep_free(l);
ldns_rdf_deep_free(d);
return 1;
}
}
ldns_rr_list_deep_free(l);
ldns_rdf_deep_free(d);
return 0;
}
static void
outq_check_packet(struct outq* outq, uint8_t* wire, size_t len)
{
char reason[512];
int rrsig_in_auth = 0;
ldns_pkt *p = NULL;
ldns_status s;
if(verbosity >= VERB_ALGO) {
if(!outq->probe) {
verbose(VERB_ALGO, "%s %s received", outq->qname,
outq->qtype==LDNS_RR_TYPE_TXT?"TXT":(
outq->qtype==LDNS_RR_TYPE_A?"A":(
outq->qtype==LDNS_RR_TYPE_AAAA?"AAAA":"other"
)));
} else if(outq->probe->to_http) {
verbose(VERB_ALGO, "from %s %s received",
outq->probe->name, outq->probe->http_ip6?
"HTTPip6":"HTTPip4");
} else
verbose(VERB_ALGO, "from %s %s %s received",
outq->probe->name,
outq->qtype==PROBE_NSEC3_QTYPE?"NSEC3":(
outq->qtype==LDNS_RR_TYPE_DNSKEY?"DNSKEY":"DS"),
outq->probe->ssldns?"SSLprobe":(
outq->probe->dnstcp?"TCPprobe":(
outq->probe->to_auth?"AUTHprobe":"cacheprobe"
)));
log_hex("packet", wire, len);
}
if(outq->probe)
outq->probe->got_packet = 1;
if(!LDNS_QR_WIRE(wire)) {
outq_done(outq, "reply without QR flag");
return;
}
if(LDNS_TC_WIRE(wire)) {
/* start TCP query and wait for it */
verbose(VERB_ALGO, "%s: TC flag, switching to TCP",
outq->probe?outq->probe->name:outq->qname);
if(!outq_send_tcp(outq)) {
outq_done(outq, "cannot send TCP query after TC flag");
}
return;
}
if( (s=ldns_wire2pkt(&p, wire, len)) != LDNS_STATUS_OK) {
snprintf(reason, sizeof(reason), "cannot disassemble reply: %s",
ldns_get_errorstr_by_id(s));
outq_done(outq, reason);
return;
}
if(!p) {
outq_done(outq, "out of memory");
return;
}
if(verbosity >= VERB_ALGO) {
char* desc = ldns_pkt2str(p);
if(desc) verbose(VERB_ALGO, "%s", desc);
free(desc);
}
/* does DNS work? */
if(ldns_pkt_get_rcode(p) != LDNS_RCODE_NOERROR) {
char* r = ldns_pkt_rcode2str(ldns_pkt_get_rcode(p));
snprintf(reason, sizeof(reason), "no answer, %s",
r?r:"(out of memory)");
outq_done(outq, reason);
LDNS_FREE(r);
ldns_pkt_free(p);
return;
}
if(!outq->probe) {
selfupdate_outq_done(global_svr->update, outq, p, NULL);
return;
}
/* if this all OK, and addr, then use http addr process */
if(outq == outq->probe->host_c) {
/* this routine frees pkt */
http_host_outq_result(outq->probe, p);
return;
}
/* test EDNS0 presence, of OPT record */
/* LDNS forgets during pkt parse, but we test the ARCOUNT;
* 0 additionals means no EDNS(on the wire), and after parsing the
* same additional RRs as before means no EDNS OPT */
if(LDNS_ARCOUNT(wire) == 0 ||
ldns_pkt_arcount(p) == LDNS_ARCOUNT(wire)) {
outq_done(outq, "no EDNS");
ldns_pkt_free(p);
return;
}
/* test if the type, RRSIG present */
if(outq->qtype == PROBE_NSEC3_QTYPE) {
if(!check_type_in_authority(p, LDNS_RR_TYPE_NSEC3)) {
outq_done(outq, "no NSEC3 in nodata reply");
ldns_pkt_free(p);
return;
}
rrsig_in_auth = 1;
} else if(!check_type_in_answer(p, (int)outq->qtype)) {
if(outq->qtype == LDNS_RR_TYPE_DS) {
/* if type DS, and it is not present, it is OK if
* we get a proper denial from the parent with NSEC */
if(!check_denial_in_answer(p, outq->qname)) {
outq_done(outq, "no DS and no proper "
"denial in reply");
ldns_pkt_free(p);
return;
}
if(!check_type_in_authority(p, LDNS_RR_TYPE_NSEC)) {
outq_done(outq, "no NSEC in denial reply");
ldns_pkt_free(p);
return;
}
rrsig_in_auth = 1;
} else {
/* failed to find type */
char* r = ldns_rr_type2str(outq->qtype);
snprintf(reason, sizeof(reason),
"no %s in reply", r?r:"DNSSEC-RRTYPE");
outq_done(outq, reason);
LDNS_FREE(r);
ldns_pkt_free(p);
return;
}
}
if(rrsig_in_auth) {
if(!check_type_in_authority(p, LDNS_RR_TYPE_RRSIG)) {
outq_done(outq, "no RRSIGs in reply");
ldns_pkt_free(p);
return;
}
} else {
if(!check_type_in_answer(p, LDNS_RR_TYPE_RRSIG)) {
outq_done(outq, "no RRSIGs in reply");
ldns_pkt_free(p);
return;
}
}
/* for authoritative probes we try to detect transparent proxies
* that mess with the answer, we have to avoid them, if they
* worked well, the DHCP DNS cache would work well. */
if(!outq->recurse) {
if(LDNS_RA_WIRE(wire)) {
outq_done(outq, "authority response has RA flag");
ldns_pkt_free(p);
return;
}
if(!LDNS_AA_WIRE(wire)) {
outq_done(outq, "authority response misses AA flag");
ldns_pkt_free(p);
return;
}
}
outq_done(outq, NULL);
ldns_pkt_free(p);
}
int outq_handle_udp(struct comm_point* c, void* my_arg, int error,
struct comm_reply *reply_info)
{
struct outq* outq = (struct outq*)my_arg;
uint8_t* wire = ldns_buffer_begin(c->buffer);
size_t len = ldns_buffer_limit(c->buffer);
if(error != NETEVENT_NOERROR) {
verbose(VERB_ALGO, "udp receive error");
return 0;
}
if(sockaddr_cmp(&outq->addr, outq->addrlen, &reply_info->addr,
reply_info->addrlen) != 0) {
/* from wrong source, keep listening for the real one */
log_addr(VERB_ALGO, "reply from wrong source",
&reply_info->addr, reply_info->addrlen);
return 0;
}
/* quick sanity check */
if(len < LDNS_HEADER_SIZE || LDNS_ID_WIRE(wire) != outq->qid
|| !LDNS_QR_WIRE(wire)) {
verbose(VERB_ALGO, "%4.4x wire, qid %4.4x, qr %s",
LDNS_ID_WIRE(wire), outq->qid,
LDNS_QR_WIRE(wire)?"yes":"no");
/* wait for the real reply */
verbose(VERB_ALGO, "ignored bad reply (tooshort, wrong qid or noQR)");
return 0;
}
comm_timer_disable(outq->timer);
outq_check_packet(outq, wire, len);
return 0;
}
static int
create_probe_query(struct outq* outq, ldns_buffer* buffer)
{
uint16_t flags = 0;
ldns_pkt* pkt = NULL;
ldns_status status;
if(outq->recurse)
flags |= LDNS_RD;
if(outq->cdflag)
flags |= LDNS_CD;
status = ldns_pkt_query_new_frm_str(&pkt, outq->qname, outq->qtype,
LDNS_RR_CLASS_IN, flags);
if(status != LDNS_STATUS_OK) {
log_err("could not pkt_query_new %s",
ldns_get_errorstr_by_id(status));
return 0;
}
if(outq->edns) {
ldns_pkt_set_edns_do(pkt, 1);
ldns_pkt_set_edns_udp_size(pkt, 4096);
} else {
ldns_pkt_set_edns_do(pkt, 0);
}
ldns_pkt_set_id(pkt, outq->qid);
ldns_buffer_clear(buffer);
status = ldns_pkt2buffer_wire(buffer, pkt);
if(status != LDNS_STATUS_OK) {
log_err("could not host2wire packet %s",
ldns_get_errorstr_by_id(status));
ldns_pkt_free(pkt);
return 0;
}
ldns_buffer_flip(buffer);
ldns_pkt_free(pkt);
return 1;
}
struct outq*
outq_create(const char* ip, int tp, const char* domain, int recurse,
struct probe_ip* p, int tcp, int onssl, int port, int edns, int cdflag)
{
int fd;
struct outq* outq = (struct outq*)calloc(1, sizeof(*outq));
/* open UDP socket */
if(!outq) {
log_err("out of memory");
return NULL;
}
outq->qname = domain;
outq->probe = p;
outq->qtype = (uint16_t)tp;
outq->qid = (uint16_t)ldns_get_random();
outq->recurse = recurse;
outq->on_ssl = onssl;
outq->port = port;
outq->edns = edns;
outq->cdflag = cdflag;
if(!ipstrtoaddr(ip, port, &outq->addr, &outq->addrlen)) {
log_err("could not parse ip %s", ip);
free(outq);
return NULL;
}
outq->timer = comm_timer_create(global_svr->base, &outq_timeout, outq);
if(!outq->timer) {
log_err("cannot create timer");
outq_delete(outq);
return NULL;
}
if(tcp || onssl){
/* also sets timeout, timer */
/* we test if it works, because if it were to call outq_done
* with an error reason the query counts go wrong and the
* probe does not work correctly. */
if(!outq_send_tcp(outq)) {
outq_delete(outq);
return NULL;
}
return outq;
}
fd = socket(strchr(ip, ':')?PF_INET6:PF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(fd == -1) {
#ifndef USE_WINSOCK
if(errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT) {
if(verbosity <= 2) {
free(outq);
return NULL;
}
}
#else
if(WSAGetLastError() == WSAEAFNOSUPPORT ||
WSAGetLastError() == WSAEPROTONOSUPPORT) {
if(verbosity <= 2) {
free(outq);
return NULL;
}
}
#endif
log_err("socket %s udp: %s", strchr(ip, ':')?"ip6":"ip4",
strerror(errno));
free(outq);
return NULL;
}
outq->c = comm_point_create_udp(global_svr->base, fd,
global_svr->udp_buffer, &outq_handle_udp, outq);
if(!outq->c) {
#ifndef USE_WINSOCK
close(fd);
#else
closesocket(fd);
#endif
free(outq);
return NULL;
}
/* set timeout on commpoint */
outq->timeout = QUERY_START_TIMEOUT; /* msec */
if(!outq_settimeout_and_send(outq)) {
outq_delete(outq);
return NULL;
}
return outq;
}
void outq_delete(struct outq* outq)
{
if(!outq) return;
comm_timer_delete(outq->timer);
comm_point_delete(outq->c);
free(outq);
}
static void outq_settimer(struct outq* outq)
{
struct timeval tv;
tv.tv_sec = outq->timeout/1000;
tv.tv_usec = (outq->timeout%1000)*1000;
comm_timer_set(outq->timer, &tv);
}
static int outq_settimeout_and_send(struct outq* outq)
{
ldns_buffer* udpbuf = global_svr->udp_buffer;
outq_settimer(outq);
/* create and send a message over the fd */
if(!create_probe_query(outq, udpbuf)) {
log_err("cannot create probe query");
return 0;
}
/* send it */
if(!comm_point_send_udp_msg(outq->c, udpbuf,
(struct sockaddr*)&outq->addr, outq->addrlen)) {
log_err("could not UDP send to ip %s", outq->probe->name);
return 0;
}
return 1;
}
void outq_timeout(void* arg)
{
struct outq* outq = (struct outq*)arg;
char *t = ldns_rr_type2str(outq->qtype);
verbose(VERB_ALGO, "%s %s: UDP timeout after %d msec",
outq->probe?outq->probe->name:outq->qname, t, outq->timeout);
free(t);
if(outq->timeout > QUERY_END_TIMEOUT) {
/* too many timeouts */
outq_done(outq, "timeout");
return;
}
/* resend */
outq->timeout *= 2;
if(!outq_settimeout_and_send(outq)) {
outq_done(outq, "could not resend after timeout");
return;
}
}
/** use next free buffer to service a tcp query */
static int
outq_tcp_take_into_use(struct outq* outq)
{
int s;
/* open socket */
#ifdef INET6
if(strchr(outq->probe->name, ':'))
s = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP);
else
#endif
s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if(s == -1) {
#ifndef USE_WINSOCK
if(errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT) {
if(verbosity <= 2) {
return 0;
}
}
log_err("outgoing tcp: socket: %s", strerror(errno));
#else
if(WSAGetLastError() == WSAEAFNOSUPPORT ||
WSAGetLastError() == WSAEPROTONOSUPPORT) {
if(verbosity <= 2) {
return 0;
}
}
log_err("outgoing tcp: socket: %s",
wsa_strerror(WSAGetLastError()));
#endif
log_addr(VERB_QUERY, "failed address",
&outq->addr, outq->addrlen);
return 0;
}
fd_set_nonblock(s);
if(connect(s, (struct sockaddr*)&outq->addr, outq->addrlen) == -1) {
#ifndef USE_WINSOCK
#ifdef EINPROGRESS
if(errno != EINPROGRESS) {
#else
if(1) {
#endif
log_err("outgoing tcp: connect: %s", strerror(errno));
close(s);
#else /* USE_WINSOCK */
if(WSAGetLastError() != WSAEINPROGRESS &&
WSAGetLastError() != WSAEWOULDBLOCK) {
closesocket(s);
#endif
log_addr(VERB_OPS, "failed address",
&outq->addr, outq->addrlen);
return 0;
}
}
if(outq->on_ssl) {
outq->c->ssl = outgoing_ssl_fd(outq->probe->sslctx, s);
if(!outq->c->ssl) {
outq->c->fd = s;
comm_point_close(outq->c);
return 0;
}
#ifdef USE_WINSOCK
comm_point_tcp_win_bio_cb(outq->c, outq->c->ssl);
#endif
outq->c->ssl_shake_state = comm_ssl_shake_write;
}
outq->c->repinfo.addrlen = outq->addrlen;
memcpy(&outq->c->repinfo.addr, &outq->addr, outq->addrlen);
outq->c->tcp_is_reading = 0;
outq->c->tcp_byte_count = 0;
comm_point_start_listening(outq->c, s, -1);
return 1;
}
static int outq_send_tcp(struct outq* outq)
{
/* send outq over tcp, stop UDP in progress (if any) */
if(outq->c) comm_point_delete(outq->c);
outq->timeout = QUERY_TCP_TIMEOUT;
outq->on_tcp = 1;
outq->qid = (uint16_t)ldns_get_random();
outq->c = comm_point_create_tcp_out(global_svr->base, 65553,
outq_handle_tcp, outq);
if(!outq->c) {
log_err("cannot create tcp comm point, out of memory");
return 0;
}
if(!create_probe_query(outq, outq->c->buffer))
return 0;
if(!outq_tcp_take_into_use(outq))
return 0;
outq_settimer(outq);
return 1;
}
int outq_handle_tcp(struct comm_point* c, void* my_arg, int error,
struct comm_reply* ATTR_UNUSED(reply_info))
{
struct outq* outq = (struct outq*)my_arg;
uint8_t* wire = ldns_buffer_begin(c->buffer);
size_t len = ldns_buffer_limit(c->buffer);
if(error != NETEVENT_NOERROR) {
if(error == NETEVENT_CLOSED)
outq_done(outq, "TCP connection failure");
else outq_done(outq, "TCP receive error");
return 0;
}
/* quick sanity check */
if(len < LDNS_HEADER_SIZE) {
outq_done(outq, "TCP reply with short header");
return 0;
}
if(LDNS_ID_WIRE(wire) != outq->qid) {
outq_done(outq, "TCP reply with wrong ID");
return 0;
}
comm_timer_disable(outq->timer);
outq_check_packet(outq, wire, len);
return 0;
}
static int addr_is_localhost(const char* ip)
{
struct sockaddr_storage addr;
socklen_t len;
struct sockaddr_storage lo;
socklen_t lolen;
/* unified print format for ipv4 addresses */
if(strcmp(ip, "127.0.0.1") == 0)
return 1;
/* only for IPv6 do we need more tests */
if(!strchr(ip, ':'))
return 0;
/* detect ::ffff:127.0.0.1 */
if(strstr(ip, "127.0.0.1"))
return 1;
/* detect ::1 but there are many ways to denote that IPv6 */
if(!ipstrtoaddr(ip, DNS_PORT, &addr, &len)) {
return 0; /* it is not localhost, but unparseable */
}
if(!ipstrtoaddr("::1", DNS_PORT, &lo, &lolen)) {
return 0; /* internal error or no IPv6 */
}
return (sockaddr_cmp_addr(&lo, lolen, &addr, len) == 0);
}
static void probe_spawn(const char* ip, int recurse, int dnstcp,
struct ssllist* ssldns, int port)
{
const char* dest;
struct probe_ip* p;
if(!ip || ip[0]==0) return;
/* create a probe for this IP */
p = (struct probe_ip*)calloc(1, sizeof(*p));
if(!p) {
log_err("out of memory");
return;
}
/* make sure the IP address is not 127.0.0.1 or ::1, that would
* create a forward-loop for the resolver */
if(addr_is_localhost(ip)) {
free(p);
verbose(VERB_ALGO, "skip localhost address %s", ip);
return;
}
/* create probe structure and register it */
p->to_auth = !recurse;
p->dnstcp = dnstcp;
p->ssldns = ssldns;
p->port = port;
p->got_packet = 0;
p->name = strdup(ip);
if(!p->name) {
free(p);
log_err("out of memory");
return;
}
if(p->ssldns) {
/* this could contain verification certificates */
p->sslctx = connect_sslctx_create(NULL, NULL, NULL);
if(!p->sslctx) {
log_err("could not create sslctx for %s", p->name);
probe_delete(p);
return;
}
}
/* send the queries */
dest = get_random_dest();
verbose(VERB_ALGO, "probe %s %s %s (tld %s)",
p->name, (recurse?"rec":"norec"), ssldns?"ssl":(dnstcp?"tcp":"udp"), dest);
/* send the probe queries and wait for reply */
p->dnskey_c = outq_create(p->name, LDNS_RR_TYPE_DNSKEY, ".",
recurse, p, dnstcp, ssldns!=0, port, 1, 1);
p->ds_c = outq_create(p->name, LDNS_RR_TYPE_DS, dest, recurse, p,
dnstcp, ssldns!=0, port, 1, 1);
if(!p->ds_c || !p->dnskey_c) {
log_err("could not send queries for probe");
probe_delete(p);
return;
}
if(recurse && !dnstcp) {
/* for cache test NSEC3 */
const char* nd = get_random_nsec3_dest();
verbose(VERB_ALGO, "nsec3 query %s", nd);
p->nsec3_c = outq_create(p->name, PROBE_NSEC3_QTYPE, nd,
recurse, p, dnstcp, ssldns!=0, port, 1, 1);
if(!p->nsec3_c) {
log_err("could not send nsec3 query for probe");
probe_delete(p);
return;
}
}
/* put it in the svr list */
p->next = global_svr->probes;
global_svr->probes = p;
global_svr->num_probes++;
}
/** start probes for direct DNS authority server connection */
static void probe_spawn_direct(void)
{
int nump = global_svr->num_probes;
/* try both IP4 and IP6, one that works is enough */
verbose(VERB_ALGO, "probe authority servers");
probe_spawn(get_random_auth_ip4(), 0, 0, 0, DNS_PORT);
probe_spawn(get_random_auth_ip6(), 0, 0, 0, DNS_PORT);
if(global_svr->num_probes == nump) {
/* failed to create the probes */
/* not a loop since svr->probe_direct is true */
probe_cache_done();
}
}
/** start probes for TCP to open resolvers on non53 port numbers */
static void probe_spawn_dnstcp(void)
{
/* try ip4 and ip6, on port 80 and 443 (if configured) */
verbose(VERB_ALGO, "probe dnstcp servers");
probe_spawn(get_random_tcp80_ip4(global_svr->cfg), 1, 1, 0, 80);
probe_spawn(get_random_tcp80_ip6(global_svr->cfg), 1, 1, 0, 80);
probe_spawn(get_random_tcp443_ip4(global_svr->cfg), 1, 1, 0, 443);
probe_spawn(get_random_tcp443_ip6(global_svr->cfg), 1, 1, 0, 443);
}
/** start probes for SSL DNS to open resolvers */
static void probe_spawn_ssldns(void)
{
struct ssllist* s4 = get_random_ssl443_ip4(global_svr->cfg);
struct ssllist* s6 = get_random_ssl443_ip6(global_svr->cfg);
verbose(VERB_ALGO, "probe ssl dns servers");
if(s4) probe_spawn(s4->str, 1, 1, s4, 443);
if(s6) probe_spawn(s6->str, 1, 1, s6, 443);
}
void probe_unsafe_test(void)
{
int nurl = global_svr->cfg->num_http_urls;
verbose(VERB_OPS, "test unsafe probe combination started");
/* pretend there is no http probing */
global_svr->cfg->num_http_urls = 0;
probe_start("127.0.0.3");
global_svr->cfg->num_http_urls = nurl;
/* pretend we got a reply from the cache (so not disconnected) */
if(global_svr->probes)
global_svr->probes->got_packet = 1;
global_svr->probe_direct = 1;
probe_spawn("127.0.0.4", 0, 0, 0, DNS_PORT);
global_svr->probe_dnstcp = 1;
probe_spawn("127.0.0.5", 1, 1, 0, 80);
probe_spawn("127.0.0.6", 1, 1, 0, 443);
if(global_svr->cfg->ssl443_ip4)
probe_spawn("127.0.0.7", 1, 1, global_svr->cfg->ssl443_ip4, 443);
}
void probe_tcp_test(void)
{
int nurl = global_svr->cfg->num_http_urls;
verbose(VERB_OPS, "test tcp probe combination started");
/* pretend there is no http probing */
global_svr->cfg->num_http_urls = 0;
probe_start("127.0.0.3");
global_svr->cfg->num_http_urls = nurl;
global_svr->probe_direct = 1;
probe_spawn("127.0.0.4", 0, 0, 0, DNS_PORT);
global_svr->tcp_timer_used = 1; /* avoid retry after 20 sec */
}
void probe_ssl_test(void)
{
int nurl = global_svr->cfg->num_http_urls;
verbose(VERB_OPS, "test ssl probe combination started");
/* pretend there is no http probing */
global_svr->cfg->num_http_urls = 0;
probe_start("127.0.0.3");
global_svr->cfg->num_http_urls = nurl;
global_svr->probe_direct = 1;
probe_spawn("127.0.0.4", 0, 0, 0, DNS_PORT);
global_svr->probe_dnstcp = 1;
probe_spawn_ssldns();
global_svr->tcp_timer_used = 1; /* avoid retry after 20 sec */
}
void probe_http_test(void)
{
struct cfg* cfg = global_svr->cfg;
struct strlist2* old_http_urls = cfg->http_urls;
struct strlist2* old_http_urls_last = cfg->http_urls_last;
int old_num_http_urls = cfg->num_http_urls;
struct strlist2 fake;
verbose(VERB_OPS, "test http probe url started");
fake.next = NULL;
/* lookup of existing URL but we give wrong contents */
fake.str1 = "http://open.nlnetlabs.nl/index.html";
fake.str2 = "NoSuchString12345";
cfg->http_urls = &fake;
cfg->http_urls_last = &fake;
cfg->num_http_urls = 1;
global_svr->skip_http = 0;
cmd_reprobe();
cfg->http_urls = old_http_urls;
cfg->http_urls_last = old_http_urls_last;
cfg->num_http_urls = old_num_http_urls;
}
/* stop unfininished probes and remove them */
static void stop_unfinished_probes(void)
{
struct probe_ip* p, *prev = NULL, *np;
for(p = global_svr->probes; p; p = np) {
np = p->next;
if(!p->finished) {
if(prev) prev->next = p->next;
else global_svr->probes = p->next;
verbose(VERB_ALGO, "stop %s: not needed", p->name);
probe_delete(p);
} else {
prev = p;
}
}
}
/* see if there are working dnstcp probes */
int probe_has_work_tcp(struct svr* svr, int port, int ip6, int ssl)
{
struct probe_ip* p;
for(p = svr->probes; p; p = p->next) {
if(p->works && p->dnstcp && p->port == port) {
if(ssl && !p->ssldns)
continue;
if(!ssl && p->ssldns)
continue;
if(ip6 && strchr(p->name, ':'))
return 1;
if(!ip6 && !strchr(p->name, ':'))
return 1;
}
}
return 0;
}
/** see if probe totally done or we have to wait more */
static void
probe_partial_done(struct probe_ip* p, const char* in, const char* reason)
{
if(!reason && (p->ds_c || p->dnskey_c || p->nsec3_c)) {
/* this one success but wait for the other one */
verbose(VERB_ALGO, "probe %s: %s completed successfully",
p->name, in);
return;
}
if(reason) {
verbose(VERB_ALGO, "probe %s: failed: %s in %s",
p->name, reason, in);
/* stop other probe (if any), it failed */
outq_delete(p->ds_c);
p->ds_c = NULL;
outq_delete(p->dnskey_c);
p->dnskey_c = NULL;
outq_delete(p->nsec3_c);
p->nsec3_c = NULL;
/* note failure */
p->reason = strdup(reason);
p->works = 0;
} else {
verbose(VERB_ALGO, "probe %s: %s completed successfully",
p->name, in);
p->works = 1;
}
p->finished = 1;
global_svr->num_probes_done++;
probe_done(p);
}
/* once a probe completes, do this:
* if this probe succeeds and it is the first to do so, set unbound_fwd.
* if all probes are done and have successes, set unbound_fwd.
* if all probes failed DNSSEC, spawn a probe for DNS-direct.
* if all probes failed and dns-direct failed, we fail.
* if all probes failed and dns-direct works, set unbound_fwd.
*
* p is the probe that is done now.
*/
static void
probe_done(struct probe_ip* p)
{
struct svr* svr = global_svr;
if(p->works) {
if(!svr->probe_dnstcp && !svr->probe_direct &&
!svr->saw_first_working) {
svr->saw_first_working = 1;
/* if works, not forced_insecure and http works (or
* did not get probed because not configured) then
* we can already use this cache-DNS now before all
* probes are done */
if(!svr->forced_insecure && (!svr->http ||
svr->skip_http || svr->http->saw_http_work)) {
probe_setup_cache(svr, p);
}
} else if(svr->probe_direct && !svr->probe_dnstcp &&
!svr->saw_direct_work) {
svr->saw_direct_work = 1;
/* no need for wait for more done */
stop_unfinished_probes();
probe_cache_done();
return;
} else if(svr->probe_dnstcp && !svr->saw_dnstcp_work) {
svr->saw_dnstcp_work = 1;
/* can already use this port */
if(!svr->forced_insecure)
probe_setup_dnstcp(svr);
}
}
if(svr->num_probes_done < svr->num_probes) {
/* continue to wait for the rest */
return;
}
probe_cache_done();
}
/** setup to use cache */
void probe_setup_cache(struct svr* svr, struct probe_ip* p)
{
svr->res_state = res_cache;
if(svr->insecure_state) hook_resolv_flush(svr->cfg);
svr->insecure_state = 0;
/* send the working servers to unbound */
if(p)
hook_unbound_cache(svr->cfg, p->name);
else hook_unbound_cache_list(svr->cfg, svr->probes);
/* set resolv.conf to 127.0.0.1 */
hook_resolv_localhost(svr->cfg);
svr_retry_timer_stop();
svr->tcp_timer_used = 0;
svr->http_insecure = 0;
}
/** setup for auth (direct to authorities) */
void probe_setup_auth(struct svr* svr)
{
svr->res_state = res_auth;
if(svr->insecure_state) hook_resolv_flush(svr->cfg);
svr->insecure_state = 0;
hook_unbound_auth(svr->cfg);
/* set resolv.conf to 127.0.0.1 */
hook_resolv_localhost(svr->cfg);
svr_retry_timer_stop();
svr->tcp_timer_used = 0;
svr->http_insecure = 0;
}
/** setup for dnstcp (uses tcp to open resolver) */
void probe_setup_dnstcp(struct svr* svr)
{
int tcp80_ip4, tcp443_ip4, tcp80_ip6, tcp443_ip6, ssl443_ip4,
ssl443_ip6;
if(svr->insecure_state) hook_resolv_flush(svr->cfg);
svr->insecure_state = 0;
/* see which ports work */
tcp80_ip4 = probe_has_work_tcp(svr, 80, 0, 0);
tcp80_ip6 = probe_has_work_tcp(svr, 80, 1, 0);
tcp443_ip4 = probe_has_work_tcp(svr, 443, 0, 0);
tcp443_ip6 = probe_has_work_tcp(svr, 443, 1, 0);
ssl443_ip4 = probe_has_work_tcp(svr, 443, 0, 1);
ssl443_ip6 = probe_has_work_tcp(svr, 443, 1, 1);
if(tcp80_ip4 || tcp443_ip4 || tcp80_ip6 || tcp443_ip6) {
svr->res_state = res_tcp;
hook_unbound_tcp_upstream(svr->cfg, tcp80_ip4, tcp80_ip6,
tcp443_ip4, tcp443_ip6);
} else {
svr->res_state = res_ssl;
hook_unbound_ssl_upstream(svr->cfg, ssl443_ip4, ssl443_ip6);
}
/* set resolv.conf to 127.0.0.1 */
hook_resolv_localhost(svr->cfg);
svr_retry_timer_stop();
svr_tcp_timer_enable();
svr->http_insecure = 0;
}
/** setup to be disconnected */
void probe_setup_disconnected(struct svr* svr)
{
svr->insecure_state = 0;
svr->res_state = res_disconn;
/* set unbound to go dark */
hook_unbound_dark(svr->cfg);
/* set resolver.conf to 127.0.0.1 (get rid of old
* settings that may be in there) */
hook_resolv_localhost(svr->cfg);
svr_retry_timer_next(0);
svr->tcp_timer_used = 0;
svr->http_insecure = 0;
}
/** setup for dark (no dnssec) */
void probe_setup_dark(struct svr* svr)
{
/* DNSSEC failure, and there is some unsafe IPs */
if(svr->res_state != res_dark)
svr->insecure_state = 0; /* ask again */
svr->res_state = res_dark;
/* set unbound to dark */
hook_unbound_dark(svr->cfg);
/* see what the user wants */
if(svr->insecure_state) {
/* set resolv.conf to DHCP IP list */
hook_resolv_iplist(svr->cfg, svr->probes);
} else { /* set resolv.conf to 127.0.0.1 now,
* the user may select insecure later */
hook_resolv_localhost(svr->cfg);
}
svr_retry_timer_next(0);
svr->tcp_timer_used = 0;
svr->http_insecure = 0;
}
/** setup forced insecure (for hotspot signon) */
void probe_setup_hotspot_signon(struct svr* svr)
{
svr->res_state = res_dark;
svr->forced_insecure = 1;
svr->insecure_state = 1;
/* effectuate it */
hook_unbound_dark(svr->cfg);
hook_resolv_iplist(svr->cfg, svr->probes);
svr_retry_timer_stop();
svr_tcp_timer_stop();
svr->http_insecure = 0;
}
/** setup http insecure (for hotspot signon) */
void probe_setup_http_insecure(struct svr* svr)
{
svr->res_state = res_dark;
svr->http_insecure = 1;
/* effectuate it */
hook_unbound_dark(svr->cfg);
/* see what the user wants */
if(svr->insecure_state) {
/* set resolv.conf to DHCP IP list */
hook_resolv_iplist(svr->cfg, svr->probes);
} else { /* set resolv.conf to 127.0.0.1 now,
* the user may select insecure later */
hook_resolv_localhost(svr->cfg);
}
/* set timer to catch user as things work again */
svr_retry_timer_next(1);
svr->tcp_timer_used = 0;
}
void
probe_cache_done(void)
{
struct svr* svr = global_svr;
if(!svr->probe_direct && svr->http && !svr->http->saw_http_work
&& !svr->skip_http) {
/* cache probe completed, but http fails to work, stop probes */
/* do not probe direct authority servers, because HTTP fails*/
/* unless we skip http probe, then go on to probe authority */
probe_all_done();
return;
}
if(!svr->probe_direct && !svr->saw_first_working) {
/* no working server, probe the direct DNS */
/* we wait until the other probes fail to not put
* traffic to the authority servers when a cache works */
svr->probe_direct = 1;
/* set flag first avoids loop in case spawn fails */
probe_spawn_direct();
return;
}
if(!svr->probe_dnstcp && !svr->saw_first_working
&& !svr->saw_direct_work && (cfg_have_dnstcp(svr->cfg) ||
cfg_have_ssldns(svr->cfg))) {
int nump = global_svr->num_probes;
int done = 0;
if(hook_unbound_supports_tcp_upstream(svr->cfg)) {
/* no working cache and authority-direct works.
* probe dns-over-tcp on port 80 and 443.
* Do not probe earlier to avoid traffic on
* those resolvers when not necessary */
svr->probe_dnstcp = 1;
probe_spawn_dnstcp();
done = 1;
}
if(hook_unbound_supports_ssl_upstream(svr->cfg)) {
/* probe for SSL wrapped service to avoid deepstuff */
svr->probe_dnstcp = 1;
probe_spawn_ssldns();
done = 1;
}
if(!done) {
verbose(VERB_OPS, "unbound does not support "
"tcp-upstream and ssl-upstream, but "
"these features are needed now. "
"Please upgrade unbound");
} else if(global_svr->num_probes == nump) {
/* failed to create the probes (outofmemory?) */
} else if(done) {
/* do the probes */
return;
}
}
probe_all_done();
}
/** true if no packets were received during the probe: network seems down */
static int
got_no_packets(struct svr* svr)
{
struct probe_ip* p;
for(p=svr->probes; p; p=p->next) {
if(p->got_packet)
return 0;
}
return 1;
}
void
probe_all_done(void)
{
struct svr* svr = global_svr;
if(verbosity >= VERB_DETAIL) {
struct probe_ip* p;
for(p=svr->probes; p; p=p->next) {
if(p->to_http)
verbose(VERB_DETAIL, "%s %s: %s %s",
p->host_c?"addrlookup":"http", p->name,
p->works?"OK":"error", p->reason?p->reason:"");
else if(p->dnstcp)
verbose(VERB_DETAIL, "%s%d %s: %s %s",
p->ssldns?"ssl":"tcp", p->port, p->name,
p->works?"OK":"error", p->reason?p->reason:"");
else
verbose(VERB_DETAIL, "%s %s: %s %s",
p->to_auth?"authority":"cache", p->name,
p->works?"OK":"error", p->reason?p->reason:"");
}
}
/* reset skip http once it works */
if(svr->skip_http && svr->http && svr->http->saw_http_work)
svr->skip_http = 0;
if(svr->forced_insecure) {
verbose(VERB_OPS, "probe done: but still forced insecure");
/* call it again, in case DHCP changes while hotspot-signon */
probe_setup_hotspot_signon(svr);
} else if(svr->probe_dnstcp && svr->saw_dnstcp_work) {
/* set unbound to process over tcp */
verbose(VERB_OPS, "probe done: DNSSEC to tcp or ssl resolver");
probe_setup_dnstcp(svr);
} else if(svr->probe_direct && svr->saw_direct_work) {
/* set unbound to process directly */
verbose(VERB_OPS, "probe done: DNSSEC to auth direct");
probe_setup_auth(svr);
} else if(svr->probe_direct && !svr->saw_direct_work &&
!svr->saw_dnstcp_work) {
/* if there are no cache IPs, then there is nothing else
* we can do, we are in offline mode, most likely. No DHCP,
* no network connectivity */
/* if none of the probes got a packet, then nothing pings,
* we have a stored network state without a network connect.
* The insecure option cannot ping the cache, so disconnect. */
if(svr->num_probes_to_cache == 0 || got_no_packets(svr)) {
verbose(VERB_OPS, "probe done: disconnected");
probe_setup_disconnected(svr);
} else {
verbose(VERB_OPS, "probe done: DNSSEC fails");
probe_setup_dark(svr);
}
} else if(svr->http && !svr->http->saw_http_work && !svr->skip_http) {
/* http probed (so have DHCPDNS), but fails */
verbose(VERB_OPS, "probe done: http fails");
probe_setup_http_insecure(svr);
} else {
verbose(VERB_OPS, "probe done: DNSSEC to cache");
probe_setup_cache(svr, NULL);
}
svr->probetime = time(0);
svr_send_results(svr);
svr_check_update(svr);
}