Hi,
The patch below add opportunistic DoT to unwind. Some background
info:
The purpose of unwind is to provide secure DNS services even when
the available nameservers are broken or filtered like in many hotels.
To do that, it prefers DNSSEC whenever possible and changes to do
resolving by itself if needed.
DNSSEC only offers integrity and authenticity. To protect
eavesdropping on the requests in transit, encryption is needed, as
offered by e.g. DNS over TLS (DoT) and DNS over HTTP (DoT). unwind
already supports DoT for forwarders but only when explicitly
configured to do so.
This diff makes unwind try DoT for forwarders and nameservers learned
via DHCP and fall back to plaintext if it does not work.
Which DoT servers to use for testing? Most quad-whatever open DNS
resolvers like 9.9.9.9 support DoT so you can specify them as a
forwarder. But please note the privacy policies and realize they see
all your DNS queries. You might consider reading [1] which offers DoT
on 136.144.215.158 as well and has a good privacy policy and is in
Dutch jurisdiction. If you do want to run DoT yourself there's
dnsdist in ports. Some ISPs offer DoT as well.
How to test?
============
Apply the diff in /usr/src and rebuild sbin/unwind and usr.sbin/unwindctl.
- read the unwind(8) man page about changing dhclient.conf if you use
DHCP (this is not related to this diff but important anyway).
- make sure that if you have an existing working setup it does not
break with this diff.
- check if DoT is used when appriopiate by using the unwindctl command.
Even if you do not want to run DoT at least make sure existing stuff
does not break. I might need to add a config option to tune the
always on behaviour of opportunistic DoT, but only want to do that if
I have evidence it is needed.
Happy testing!
-Otto
[1] https://doh.powerdns.org/doh/privacy.html
Index: sbin/unwind/parse.y
===================================================================
RCS file: /cvs/src/sbin/unwind/parse.y,v
retrieving revision 1.11
diff -u -p -r1.11 parse.y
--- sbin/unwind/parse.y 21 Oct 2019 07:16:09 -0000 1.11
+++ sbin/unwind/parse.y 23 Oct 2019 12:28:42 -0000
@@ -84,7 +84,6 @@ int check_pref_uniq(enum uw_resolver_ty
static struct uw_conf *conf;
static int errors;
-static struct uw_forwarder *uw_forwarder;
void clear_config(struct uw_conf *xconf);
struct sockaddr_storage *host_ip(const char *);
@@ -286,7 +285,9 @@ forwarderopts_l : forwarderopts_l forwa
forwarderoptsl : STRING port authname dot {
int ret, port;
+ struct uw_forwarder *uw_fwd;
struct sockaddr_storage *ss;
+
if ((ss = host_ip($1)) == NULL) {
yyerror("%s is not an ip-address", $1);
free($1);
@@ -304,37 +305,53 @@ forwarderoptsl : STRING port authname d
else
port = $2;
- if ((uw_forwarder = calloc(1,
- sizeof(*uw_forwarder))) == NULL)
+ if ($3 != NULL && $4 == 0) {
+ yyerror("authentication name can only "
+ "be used with DoT");
+ free($1);
+ YYERROR;
+ }
+
+
+ if ((uw_fwd = calloc(1,
+ sizeof(*uw_fwd))) == NULL)
err(1, NULL);
- if ($3 == NULL)
- ret = snprintf(uw_forwarder->name,
- sizeof(uw_forwarder->name),
- "%s@%d", $1, port);
- else
- ret = snprintf(uw_forwarder->name,
- sizeof(uw_forwarder->name),
- "%s@%d#%s", $1, port, $3);
+ if ($4 == DOT) {
+ if ($3 == NULL)
+ ret = snprintf(uw_fwd->name,
+ sizeof(uw_fwd->name),
+ "%s@%d", $1, port);
+ else
+ ret = snprintf(uw_fwd->name,
+ sizeof(uw_fwd->name),
+ "%s@%d#%s", $1, port, $3);
+ } else {
+ uw_fwd->port = $2;
+ /* complete string wil be done later */
+ ret = snprintf(uw_fwd->name,
+ sizeof(uw_fwd->name), "%s", $1);
+ }
if (ret < 0 || (size_t)ret >=
- sizeof(uw_forwarder->name)) {
- free(uw_forwarder);
+ sizeof(uw_fwd->name)) {
+ free(uw_fwd);
yyerror("forwarder %s too long", $1);
free($1);
YYERROR;
}
- free($1);
if ($4 == DOT)
SIMPLEQ_INSERT_TAIL(
&conf->uw_dot_forwarder_list,
- uw_forwarder, entry);
- else
+ uw_fwd, entry);
+ else {
SIMPLEQ_INSERT_TAIL(
&conf->uw_forwarder_list,
- uw_forwarder, entry);
+ uw_fwd, entry);
+ }
+ free($1);
}
- ;
+ ;
port : PORT NUMBER { $$ = $2; }
| /* empty */ { $$ = 0; }
@@ -872,12 +889,14 @@ symget(const char *nam)
void
clear_config(struct uw_conf *xconf)
{
- while((uw_forwarder = SIMPLEQ_FIRST(&xconf->uw_forwarder_list)) !=
+ struct uw_forwarder *uw_forwarder;
+
+ while ((uw_forwarder = SIMPLEQ_FIRST(&xconf->uw_forwarder_list)) !=
NULL) {
SIMPLEQ_REMOVE_HEAD(&xconf->uw_forwarder_list, entry);
free(uw_forwarder);
}
- while((uw_forwarder = SIMPLEQ_FIRST(&xconf->uw_dot_forwarder_list)) !=
+ while ((uw_forwarder = SIMPLEQ_FIRST(&xconf->uw_dot_forwarder_list)) !=
NULL) {
SIMPLEQ_REMOVE_HEAD(&xconf->uw_dot_forwarder_list, entry);
free(uw_forwarder);
Index: sbin/unwind/resolver.c
===================================================================
RCS file: /cvs/src/sbin/unwind/resolver.c,v
retrieving revision 1.46
diff -u -p -r1.46 resolver.c
--- sbin/unwind/resolver.c 19 Oct 2019 17:42:21 -0000 1.46
+++ sbin/unwind/resolver.c 23 Oct 2019 12:28:42 -0000
@@ -107,6 +107,8 @@ struct uw_resolver *create_resolver(enum
void free_resolver(struct uw_resolver *);
void set_forwarders(struct uw_resolver *,
struct uw_forwarder_head *);
+void set_forwarders_oppdot(struct uw_resolver *,
+ struct uw_forwarder_head *, int);
void resolver_check_timo(int, short, void *);
void resolver_free_timo(int, short, void *);
void check_resolver(struct uw_resolver *);
@@ -156,6 +158,7 @@ struct event_base *ev_base;
enum uw_resolver_state global_state = DEAD;
enum captive_portal_state captive_portal_state = PORTAL_UNCHECKED;
+int dhcp_oppdot, forw_oppdot;
void
resolver_sig_handler(int sig, short event, void *arg)
@@ -605,6 +608,7 @@ resolver_dispatch_main(int fd, short eve
nconf = NULL;
if (forwarders_changed) {
log_debug("static forwarders changed");
+ forw_oppdot = 1;
new_static_forwarders();
}
if (dot_forwarders_changed) {
@@ -726,7 +730,7 @@ parse_dhcp_forwarders(char *forwarders)
if (forwarders != NULL) {
while((ns = strsep(&forwarders, ",")) != NULL) {
log_debug("%s: %s", __func__, ns);
- if ((uw_forwarder = malloc(sizeof(struct
+ if ((uw_forwarder = calloc(1, sizeof(struct
uw_forwarder))) == NULL)
fatal(NULL);
if (strlcpy(uw_forwarder->name, ns,
@@ -740,6 +744,7 @@ parse_dhcp_forwarders(char *forwarders)
if (check_forwarders_changed(&new_forwarder_list,
&dhcp_forwarder_list)) {
+ dhcp_oppdot = 1;
replace_forwarders(&new_forwarder_list, &dhcp_forwarder_list);
new_forwarders();
if (resolver_conf->captive_portal_auto)
@@ -886,10 +891,24 @@ create_resolver(enum uw_resolver_type ty
case UW_RES_RECURSOR:
break;
case UW_RES_DHCP:
- set_forwarders(res, &dhcp_forwarder_list);
+ if (dhcp_oppdot) {
+ set_forwarders_oppdot(res, &dhcp_forwarder_list, 853);
+ ub_ctx_set_option(res->ctx, "tls-cert-bundle:",
+ tls_default_ca_cert_file());
+ ub_ctx_set_tls(res->ctx, 1);
+ } else
+ set_forwarders_oppdot(res, &dhcp_forwarder_list, 53);
break;
case UW_RES_FORWARDER:
- set_forwarders(res, &resolver_conf->uw_forwarder_list);
+ if (forw_oppdot) {
+ set_forwarders_oppdot(res,
+ &resolver_conf->uw_forwarder_list, 853);
+ ub_ctx_set_option(res->ctx, "tls-cert-bundle:",
+ tls_default_ca_cert_file());
+ ub_ctx_set_tls(res->ctx, 1);
+ } else
+ set_forwarders_oppdot(res,
+ &resolver_conf->uw_forwarder_list, 53);
break;
case UW_RES_DOT:
set_forwarders(res, &resolver_conf->uw_dot_forwarder_list);
@@ -934,6 +953,22 @@ set_forwarders(struct uw_resolver *res,
}
void
+set_forwarders_oppdot(struct uw_resolver *res, struct uw_forwarder_head
+ *uw_forwarder_list, int def_port)
+{
+ struct uw_forwarder *uw_forwarder;
+
+ SIMPLEQ_FOREACH(uw_forwarder, uw_forwarder_list, entry) {
+ char name[1024];
+ int port = uw_forwarder->port;
+ if (port == 0)
+ port = def_port;
+ snprintf(name, sizeof(name), "%s@%d", uw_forwarder->name, port);
+ ub_ctx_set_fwd(res->ctx, name);
+ }
+}
+
+void
resolver_check_timo(int fd, short events, void *arg)
{
check_resolver((struct uw_resolver *)arg);
@@ -1005,6 +1040,21 @@ check_resolver_done(void *arg, int rcode
if (rcode == LDNS_RCODE_SERVFAIL) {
data->res->state = DEAD;
+ if (data->res->type == UW_RES_DHCP) {
+ if (dhcp_oppdot) {
+ dhcp_oppdot = 0;
+ log_debug("dhcp_oppdot -> 0");
+ new_forwarders();
+ log_debug("dhcp_oppdot -> 0 done");
+ }
+ } else {
+ if (forw_oppdot) {
+ forw_oppdot = 0;
+ log_debug("forw_oppdot -> 0");
+ new_static_forwarders();
+ log_debug("forw_oppdot -> 0 done");
+ }
+ }
goto out;
}
@@ -1176,8 +1226,8 @@ best_resolver(void)
uw_resolver_state_str[resolvers[UW_RES_FORWARDER]->state] : "NA",
uw_resolver_type_str[UW_RES_DOT],
resolvers[UW_RES_DOT] != NULL ?
- uw_resolver_state_str[resolvers[UW_RES_DOT]->state] :
- "NA", captive_portal_state_str[captive_portal_state]);
+ uw_resolver_state_str[resolvers[UW_RES_DOT]->state] : "NA",
+ captive_portal_state_str[captive_portal_state]);
if (captive_portal_state == PORTAL_UNKNOWN || captive_portal_state ==
BEHIND) {
@@ -1273,6 +1323,12 @@ send_resolver_info(struct uw_resolver *r
cri.state = res->state;
cri.type = res->type;
cri.selected = selected;
+ if (cri.type == UW_RES_DHCP && dhcp_oppdot)
+ cri.oppdot = 1;
+ else if (cri.type == UW_RES_FORWARDER && forw_oppdot)
+ cri.oppdot = 1;
+ else
+ cri.oppdot = 0;
resolver_imsg_compose_frontend(IMSG_CTL_RESOLVER_INFO, pid, &cri,
sizeof(cri));
}
Index: sbin/unwind/resolver.h
===================================================================
RCS file: /cvs/src/sbin/unwind/resolver.h,v
retrieving revision 1.6
diff -u -p -r1.6 resolver.h
--- sbin/unwind/resolver.h 2 Apr 2019 07:47:23 -0000 1.6
+++ sbin/unwind/resolver.h 23 Oct 2019 12:28:42 -0000
@@ -51,6 +51,7 @@ struct ctl_resolver_info {
enum uw_resolver_state state;
enum uw_resolver_type type;
int selected;
+ int oppdot;
};
void resolver(int, int);
Index: sbin/unwind/unwind.h
===================================================================
RCS file: /cvs/src/sbin/unwind/unwind.h,v
retrieving revision 1.18
diff -u -p -r1.18 unwind.h
--- sbin/unwind/unwind.h 21 Oct 2019 07:16:09 -0000 1.18
+++ sbin/unwind/unwind.h 23 Oct 2019 12:28:42 -0000
@@ -127,6 +127,7 @@ enum imsg_type {
struct uw_forwarder {
SIMPLEQ_ENTRY(uw_forwarder) entry;
char name[1024]; /* XXX */
+ uint16_t port;
};
SIMPLEQ_HEAD(uw_forwarder_head, uw_forwarder);
Index: usr.sbin/unwindctl/parser.c
===================================================================
RCS file: /cvs/src/usr.sbin/unwindctl/parser.c,v
retrieving revision 1.3
diff -u -p -r1.3 parser.c
--- usr.sbin/unwindctl/parser.c 3 Feb 2019 12:02:30 -0000 1.3
+++ usr.sbin/unwindctl/parser.c 23 Oct 2019 12:28:42 -0000
@@ -72,7 +72,7 @@ static const struct token t_status[] = {
{NOTOKEN, "", NONE, NULL},
{KEYWORD, "recursor", STATUS_RECURSOR, NULL},
{KEYWORD, "dhcp", STATUS_DHCP, NULL},
- {KEYWORD, "static", STATUS_STATIC, NULL},
+ {KEYWORD, "forwarder", STATUS_STATIC, NULL},
{KEYWORD, "DoT", STATUS_DOT, NULL},
{ENDTOKEN, "", STATUS, NULL}
};
Index: usr.sbin/unwindctl/unwindctl.8
===================================================================
RCS file: /cvs/src/usr.sbin/unwindctl/unwindctl.8,v
retrieving revision 1.4
diff -u -p -r1.4 unwindctl.8
--- usr.sbin/unwindctl/unwindctl.8 5 Feb 2019 20:41:12 -0000 1.4
+++ usr.sbin/unwindctl/unwindctl.8 23 Oct 2019 12:28:42 -0000
@@ -55,14 +55,14 @@ Enable very noisy debug logging.
Reload the configuration file.
.It Cm recheck portal
Run the captive portal detection.
-.It Cm status Op Cm recursor | dhcp | DoT | static
+.It Cm status Op Cm recursor | dhcp | DoT | forwarder
Show a status summary.
If one of
.Cm recursor ,
.Cm Dhcp ,
.Cm Dot ,
Or
-.Cm Static
+.Cm forwarder
is specified, more detailed information about that type of resolver is given
including reasons why DNSSEC validation might be failing and a query time
histogram.
Index: usr.sbin/unwindctl/unwindctl.c
===================================================================
RCS file: /cvs/src/usr.sbin/unwindctl/unwindctl.c,v
retrieving revision 1.6
diff -u -p -r1.6 unwindctl.c
--- usr.sbin/unwindctl/unwindctl.c 2 Apr 2019 07:47:23 -0000 1.6
+++ usr.sbin/unwindctl/unwindctl.c 23 Oct 2019 12:28:42 -0000
@@ -241,9 +241,10 @@ show_status_msg(struct imsg *imsg)
break;
case IMSG_CTL_RESOLVER_INFO:
cri = imsg->data;
- printf("%8s %16s %s\n", cri->selected ? "*" : " ",
+ printf("%8s %16s %s%s\n", cri->selected ? "*" : " ",
uw_resolver_type_str[cri->type],
- uw_resolver_state_str[cri->state]);
+ uw_resolver_state_str[cri->state],
+ cri->oppdot ? " (OppDoT)" : "");
break;
case IMSG_CTL_RESOLVER_WHY_BOGUS:
/* make sure this is a string */