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 */

Reply via email to