Hi,

In many offices, split horizon DNS is used. This means that if you are
in the office you are supposed to use a specific resolver that will
hand out different results than when asking for the same name on the
rest of the internet.

Until now unwind could not really handle that, e.g. in recursing mode,
it would produce the view as from outside of the office. 

With this diff, it becomes possible to force using a specific resolver
when resolving names in specific domains.

For example, with this unwind.conf:

# Office forwarder
forwarder 1.2.3.4 
force forwarder {
        myoffice.com
        dmz.colocation.com
}

This will make unwind always use the mentioned forwarder for anything
under office.com or dmz.colocation.com. If the forwarder is dead,
regular resolving is done for these names and www.office.com will
likely return the external address.

Often split-horizon DNS breaks DNSSEC for these specific domains. If
that is the case, you can use

force acceptbogus forwarder { 
        ... 
}

please test this,

        -Otto

OAIndex: frontend.c
===================================================================
RCS file: /cvs/src/sbin/unwind/frontend.c,v
retrieving revision 1.40
diff -u -p -r1.40 frontend.c
--- frontend.c  27 Nov 2019 17:09:12 -0000      1.40
+++ frontend.c  28 Nov 2019 14:24:17 -0000
@@ -336,6 +336,7 @@ frontend_dispatch_main(int fd, short eve
                case IMSG_RECONF_BLOCKLIST_FILE:
                case IMSG_RECONF_FORWARDER:
                case IMSG_RECONF_DOT_FORWARDER:
+               case IMSG_RECONF_FORCE:
                        imsg_receive_config(&imsg, &nconf);
                        break;
                case IMSG_RECONF_END:
Index: parse.y
===================================================================
RCS file: /cvs/src/sbin/unwind/parse.y,v
retrieving revision 1.20
diff -u -p -r1.20 parse.y
--- parse.y     28 Nov 2019 10:02:44 -0000      1.20
+++ parse.y     28 Nov 2019 14:24:17 -0000
@@ -90,8 +90,9 @@ struct sockaddr_storage       *host_ip(const c
 
 typedef struct {
        union {
-               int64_t          number;
-               char            *string;
+               int64_t                          number;
+               char                            *string;
+               struct force_tree                force;
        } v;
        int lineno;
 } YYSTYPE;
@@ -101,12 +102,13 @@ typedef struct {
 %token INCLUDE ERROR
 %token FORWARDER DOT PORT 
 %token AUTHENTICATION NAME PREFERENCE RECURSOR DHCP STUB
-%token BLOCK LIST LOG
+%token BLOCK LIST LOG FORCE ACCEPTBOGUS
 
 %token <v.string>      STRING
 %token <v.number>      NUMBER
-%type  <v.number>      yesno port dot prefopt log
+%type  <v.number>      yesno port dot prefopt log acceptbogus
 %type  <v.string>      string authname
+%type  <v.force>       force_list
 
 %%
 
@@ -117,6 +119,7 @@ grammar             : /* empty */
                | grammar uw_pref '\n'
                | grammar uw_forwarder '\n'
                | grammar block_list '\n'
+               | grammar force '\n'
                | grammar error '\n'            { file->errors++; }
                ;
 
@@ -311,6 +314,63 @@ dot        :       DOT                             { $$ = 
DOT; }
 log    :       LOG                             { $$ = 1; }
        |       /* empty */                     { $$ = 0; }
        ;
+
+force  :       FORCE acceptbogus prefopt '{' force_list optnl '}' {
+                       struct force_tree_entry *n, *nxt;
+                       int error = 0;
+
+                       for (n = RB_MIN(force_tree, &$5); n != NULL;
+                           n = nxt) {
+                               nxt = RB_NEXT(force_tree, &conf->force, n);
+                               n->acceptbogus = $2;
+                               n->type = $3;
+                               RB_REMOVE(force_tree, &$5, n);
+                               if (RB_INSERT(force_tree, &conf->force,
+                                   n)) {
+                                       yyerror("%s already in an force "
+                                           "list", n->domain);
+                                       error = 1;
+                               }
+                       }
+                       if (error)
+                               YYERROR;
+               }
+       ;
+
+acceptbogus:   ACCEPTBOGUS { $$ = 1; }
+       |       /* empty */ { $$ = 0; }
+       ;
+
+force_list:    force_list optnl STRING {
+                       struct force_tree_entry *e;
+                       size_t                           len;
+
+                       len = strlen($3);
+                       e = malloc(sizeof(*e));
+                       if (e == NULL)
+                               err(1, NULL);
+                       if (strlcpy(e->domain, $3, sizeof(e->domain)) >=
+                           sizeof(e->domain)) {
+                               yyerror("force %s too long", $3);
+                               free($3);
+                               YYERROR;
+                       }
+                       free($3);
+                       if (len == 0 || e->domain[len-1] != '.') {
+                               if (strlcat(e->domain, ".",
+                                   sizeof((e->domain))) >=
+                                   sizeof((e->domain))) {
+                                       yyerror("force %s too long", $3);
+                                       YYERROR;
+                               }
+                       }
+                       RB_INSERT(force_tree, &$$, e);
+               }
+       |       /* empty */ {
+                       RB_INIT(&$$);
+               }
+       ;
+
 %%
 
 struct keywords {
@@ -346,10 +406,12 @@ lookup(char *s)
        /* This has to be sorted always. */
        static const struct keywords keywords[] = {
                {"DoT",                 DOT},
+               {"acceptbogus",         ACCEPTBOGUS},
                {"authentication",      AUTHENTICATION},
                {"block",               BLOCK},
                {"dhcp",                DHCP},
                {"dot",                 DOT},
+               {"force",               FORCE},
                {"forwarder",           FORWARDER},
                {"include",             INCLUDE},
                {"list",                LIST},
Index: printconf.c
===================================================================
RCS file: /cvs/src/sbin/unwind/printconf.c,v
retrieving revision 1.15
diff -u -p -r1.15 printconf.c
--- printconf.c 28 Nov 2019 10:02:44 -0000      1.15
+++ printconf.c 28 Nov 2019 14:24:17 -0000
@@ -31,6 +31,7 @@ print_config(struct uw_conf *conf)
 {
        struct uw_forwarder     *uw_forwarder;
        int                      i;
+       enum uw_resolver_type    j;
 
        if (conf->res_pref.len > 0) {
                printf("preference {");
@@ -68,4 +69,25 @@ print_config(struct uw_conf *conf)
        if (conf->blocklist_file != NULL)
                printf("block list \"%s\"%s\n", conf->blocklist_file,
                    conf->blocklist_log ? " log" : "");
+       for (j = 0; j < UW_RES_NONE; j++) {
+               struct force_tree_entry *e;
+               int                      empty = 1;
+
+               RB_FOREACH(e, force_tree, &conf->force) {
+                       if (e->type != j)
+                               continue;
+                       empty = 0;
+                       break;
+               }
+               if (empty)
+                       continue;
+
+               printf("force %s {", uw_resolver_type_str[j]);
+               RB_FOREACH(e, force_tree, &conf->force) {
+                       if (e->type != j)
+                               continue;
+                       printf("\n\t%s", e->domain);
+               }
+               printf("\n}\n");
+       }
 }
Index: resolver.c
===================================================================
RCS file: /cvs/src/sbin/unwind/resolver.c,v
retrieving revision 1.82
diff -u -p -r1.82 resolver.c
--- resolver.c  28 Nov 2019 10:40:29 -0000      1.82
+++ resolver.c  28 Nov 2019 14:24:17 -0000
@@ -175,6 +175,10 @@ void                        
trust_anchor_resolve_done(struct
                             int, void *, int, int, char *);
 void                    replace_autoconf_forwarders(struct
                             imsg_rdns_proposal *);
+int                     force_tree_cmp(struct force_tree_entry *,
+                            struct force_tree_entry *);
+int                     find_force(struct force_tree *, char *,
+                            struct uw_resolver **);
 int64_t                         histogram_median(int64_t *);
 
 struct uw_conf                 *resolver_conf;
@@ -190,6 +194,8 @@ static struct trust_anchor_head      trust_a
 
 struct event_base              *ev_base;
 
+RB_GENERATE(force_tree, force_tree_entry, entry, force_tree_cmp)
+
 static const char * const       as112_zones[] = {
        /* RFC1918 */
        "10.in-addr.arpa. transparent",
@@ -600,6 +606,7 @@ resolver_dispatch_main(int fd, short eve
                case IMSG_RECONF_BLOCKLIST_FILE:
                case IMSG_RECONF_FORWARDER:
                case IMSG_RECONF_DOT_FORWARDER:
+               case IMSG_RECONF_FORCE:
                        imsg_receive_config(&imsg, &nconf);
                        break;
                case IMSG_RECONF_END:
@@ -656,6 +663,7 @@ void
 setup_query(struct query_imsg *query_imsg)
 {
        struct running_query    *rq;
+       struct uw_resolver      *res;
        int                      i;
 
        if (find_running_query(query_imsg->id) != NULL) {
@@ -673,7 +681,12 @@ setup_query(struct query_imsg *query_ims
        rq->query_imsg = query_imsg;
        rq->next_resolver = 0;
 
-       if (sort_resolver_types(&rq->res_pref) == -1) {
+       find_force(&resolver_conf->force, query_imsg->qname, &res);
+
+       if (res != NULL && res->state != DEAD) {
+               rq->res_pref.len = 1;
+               rq->res_pref.types[0] = res->type;
+       } else if (sort_resolver_types(&rq->res_pref) == -1) {
                log_warn("mergesort");
                free(rq->query_imsg);
                free(rq);
@@ -942,9 +955,10 @@ resolve_done(struct uw_resolver *res, vo
 
        query_imsg->err = 0;
 
-       if (res->state == VALIDATING)
-               query_imsg->bogus = sec == BOGUS;
-       else
+       if (res->state == VALIDATING && sec == BOGUS) {
+               query_imsg->bogus = find_force(&resolver_conf->force,
+                                   query_imsg->qname, NULL) == 0;
+       } else
                query_imsg->bogus = 0;
 
        if (rq) {
@@ -1002,6 +1016,7 @@ new_recursor(void)
                return;
 
        resolvers[UW_RES_RECURSOR] = create_resolver(UW_RES_RECURSOR, 0);
+
        check_resolver(resolvers[UW_RES_RECURSOR]);
 }
 
@@ -1321,7 +1336,7 @@ check_resolver(struct uw_resolver *resol
 
        log_debug("%s: create_resolver", __func__);
        if ((res = create_resolver(resolver_to_check->type, 0)) == NULL)
-               fatal("%s", __func__);
+               fatalx("%s", __func__);
 
        resolver_ref(resolver_to_check);
 
@@ -1346,7 +1361,7 @@ check_resolver(struct uw_resolver *resol
 
        log_debug("%s: create_resolver for oppdot", __func__);
        if ((res = create_resolver(resolver_to_check->type, 1)) == NULL)
-               fatal("%s", __func__);
+               fatalx("%s", __func__);
 
        resolver_ref(resolver_to_check);
 
@@ -1943,6 +1958,45 @@ replace_autoconf_forwarders(struct imsg_
                        free(tmp);
                }
        }
+}
+
+int
+force_tree_cmp(struct force_tree_entry *a, struct force_tree_entry *b)
+{
+       return strcmp(a->domain, b->domain);
+}
+
+int
+find_force(struct force_tree *tree, char *qname, struct uw_resolver **res)
+{
+       struct force_tree_entry *n, e;
+       char                            *p;
+
+       if (res)
+               *res = NULL;
+       if (RB_EMPTY(tree))
+               return 0;
+
+       p = qname;
+       do {
+               if (strlcpy(e.domain, p, sizeof(e.domain)) >= sizeof(e.domain))
+                       fatal("qname too large");
+               n = RB_FIND(force_tree, tree, &e);
+               log_debug("%s: %s -> %p[%s]", __func__, p, n,
+                   n != NULL ?  uw_resolver_type_str[n->type] : "-");
+               if (n != NULL) {
+                       if (res)
+                               *res = resolvers[n->type];
+                       return n->acceptbogus;
+               }
+               if (*p == '.')
+                       p++;
+               p = strchr(p, '.');
+               if (p != NULL && p[1] != '\0')
+                       p++;
+       } while (p != NULL);
+       return 0;
+
 }
 
 int64_t
Index: unwind.c
===================================================================
RCS file: /cvs/src/sbin/unwind/unwind.c,v
retrieving revision 1.41
diff -u -p -r1.41 unwind.c
--- unwind.c    27 Nov 2019 17:11:00 -0000      1.41
+++ unwind.c    28 Nov 2019 14:24:17 -0000
@@ -577,7 +577,8 @@ main_reload(void)
 int
 main_imsg_send_config(struct uw_conf *xconf)
 {
-       struct uw_forwarder     *uw_forwarder;
+       struct uw_forwarder             *uw_forwarder;
+       struct force_tree_entry *force_entry;
 
        /* Send fixed part of config to children. */
        if (main_sendall(IMSG_RECONF_CONF, xconf, sizeof(*xconf)) == -1)
@@ -604,6 +605,11 @@ main_imsg_send_config(struct uw_conf *xc
                    sizeof(*uw_forwarder)) == -1)
                        return (-1);
        }
+       RB_FOREACH(force_entry, force_tree, &xconf->force) {
+               if (main_sendall(IMSG_RECONF_FORCE, force_entry,
+                   sizeof(*force_entry)) == -1)
+                       return (-1);
+       }
 
        /* Tell children the revised config is now complete. */
        if (main_sendall(IMSG_RECONF_END, NULL, 0) == -1)
@@ -625,7 +631,8 @@ main_sendall(enum imsg_type type, void *
 void
 merge_config(struct uw_conf *conf, struct uw_conf *xconf)
 {
-       struct uw_forwarder     *uw_forwarder;
+       struct uw_forwarder             *uw_forwarder;
+       struct force_tree_entry *n, *nxt;
 
        /* Remove & discard existing forwarders. */
        while ((uw_forwarder = TAILQ_FIRST(&conf->uw_forwarder_list)) !=
@@ -639,6 +646,13 @@ merge_config(struct uw_conf *conf, struc
                free(uw_forwarder);
        }
 
+       /* Remove & discard existing force tree. */
+       for (n = RB_MIN(force_tree, &conf->force); n != NULL; n = nxt) {
+               nxt = RB_NEXT(force_tree, &conf->force, n);
+               RB_REMOVE(force_tree, &conf->force, n);
+               free(n);
+       }
+
        memcpy(&conf->res_pref, &xconf->res_pref,
            sizeof(conf->res_pref));
 
@@ -647,12 +661,14 @@ merge_config(struct uw_conf *conf, struc
        conf->blocklist_log = xconf->blocklist_log;
 
        /* Add new forwarders. */
+       /* XXX TAILQ_CONCAT ? */
        while ((uw_forwarder = TAILQ_FIRST(&xconf->uw_forwarder_list)) !=
            NULL) {
                TAILQ_REMOVE(&xconf->uw_forwarder_list, uw_forwarder, entry);
                TAILQ_INSERT_TAIL(&conf->uw_forwarder_list,
                    uw_forwarder, entry);
        }
+       /* XXX TAILQ_CONCAT ? */
        while ((uw_forwarder = TAILQ_FIRST(&xconf->uw_dot_forwarder_list)) !=
            NULL) {
                TAILQ_REMOVE(&xconf->uw_dot_forwarder_list, uw_forwarder,
@@ -660,6 +676,12 @@ merge_config(struct uw_conf *conf, struc
                TAILQ_INSERT_TAIL(&conf->uw_dot_forwarder_list,
                    uw_forwarder, entry);
        }
+       
+       for (n = RB_MIN(force_tree, &xconf->force); n != NULL; n = nxt) {
+               nxt = RB_NEXT(force_tree, &xconf->force, n);    
+               RB_REMOVE(force_tree, &xconf->force, n);
+               RB_INSERT(force_tree, &conf->force, n);
+       }
 
        free(xconf);
 }
@@ -686,6 +708,8 @@ config_new_empty(void)
        TAILQ_INIT(&xconf->uw_forwarder_list);
        TAILQ_INIT(&xconf->uw_dot_forwarder_list);
 
+       RB_INIT(&xconf->force);
+
        return (xconf);
 }
 
@@ -788,8 +812,9 @@ send_blocklist_fd(void)
 void
 imsg_receive_config(struct imsg *imsg, struct uw_conf **xconf)
 {
-       struct uw_conf          *nconf;
-       struct uw_forwarder     *uw_forwarder;
+       struct uw_conf                  *nconf;
+       struct uw_forwarder             *uw_forwarder;
+       struct force_tree_entry *force_entry;
 
        nconf = *xconf;
 
@@ -807,6 +832,7 @@ imsg_receive_config(struct imsg *imsg, s
                memcpy(nconf, imsg->data, sizeof(struct uw_conf));
                TAILQ_INIT(&nconf->uw_forwarder_list);
                TAILQ_INIT(&nconf->uw_dot_forwarder_list);
+               RB_INIT(&nconf->force);
                break;
        case IMSG_RECONF_BLOCKLIST_FILE:
                /* make sure this is a string */
@@ -839,6 +865,18 @@ imsg_receive_config(struct imsg *imsg, s
                    uw_forwarder));
                TAILQ_INSERT_TAIL(&nconf->uw_dot_forwarder_list,
                    uw_forwarder, entry);
+               break;
+       case IMSG_RECONF_FORCE:
+               if (IMSG_DATA_SIZE(*imsg) != sizeof(struct force_tree_entry))
+                       fatalx("%s: IMSG_RECONF_FORCE wrong "
+                           "length: %lu", __func__,
+                           IMSG_DATA_SIZE(*imsg));
+               if ((force_entry = malloc(sizeof(struct
+                   force_tree_entry))) == NULL)
+                       fatal(NULL);
+               memcpy(force_entry, imsg->data, sizeof(struct
+                   force_tree_entry));
+               RB_INSERT(force_tree, &nconf->force, force_entry);
                break;
        default:
                log_debug("%s: error handling imsg %d", __func__,
Index: unwind.conf.5
===================================================================
RCS file: /cvs/src/sbin/unwind/unwind.conf.5,v
retrieving revision 1.19
diff -u -p -r1.19 unwind.conf.5
--- unwind.conf.5       28 Nov 2019 14:05:17 -0000      1.19
+++ unwind.conf.5       28 Nov 2019 14:24:17 -0000
@@ -113,6 +113,14 @@ itself recursively resolves names.
 .Pp
 The default preference is
 .Ic DoT forwarder recursor dhcp stub .
+.It Ic force Oo Cm acceptbogus Oc Ar type Brq Ar name ...
+Force resolving of
+.Ar name
+and its subdomains by the given resolver
+.Ar type .
+If
+.Cm acceptbogus
+is specified validation is not enforced.
 .El
 .Sh FILES
 .Bl -tag -width "/etc/unwind.conf" -compact
Index: unwind.h
===================================================================
RCS file: /cvs/src/sbin/unwind/unwind.h,v
retrieving revision 1.39
diff -u -p -r1.39 unwind.h
--- unwind.h    28 Nov 2019 10:02:44 -0000      1.39
+++ unwind.h    28 Nov 2019 14:24:18 -0000
@@ -19,6 +19,7 @@
  */
 
 #include <sys/types.h>
+#include <sys/tree.h>
 #include <netinet/in.h>        /* INET6_ADDRSTRLEN */
 #include <event.h>
 #include <imsg.h>
@@ -87,6 +88,7 @@ enum imsg_type {
        IMSG_RECONF_BLOCKLIST_FILE,
        IMSG_RECONF_FORWARDER,
        IMSG_RECONF_DOT_FORWARDER,
+       IMSG_RECONF_FORCE,
        IMSG_RECONF_END,
        IMSG_UDP4SOCK,
        IMSG_UDP6SOCK,
@@ -123,6 +125,15 @@ struct uw_forwarder {
        int                                      src;
 };
 
+struct force_tree_entry {
+       RB_ENTRY(force_tree_entry)       entry;
+       char                             domain[NI_MAXHOST];
+       enum uw_resolver_type            type;
+       int                              acceptbogus;
+};
+
+RB_HEAD(force_tree, force_tree_entry);
+
 struct resolver_preference {
        enum uw_resolver_type                    types[UW_RES_NONE];
        int                                      len;
@@ -132,6 +143,7 @@ TAILQ_HEAD(uw_forwarder_head, uw_forward
 struct uw_conf {
        struct uw_forwarder_head         uw_forwarder_list;
        struct uw_forwarder_head         uw_dot_forwarder_list;
+       struct force_tree                force;
        struct resolver_preference       res_pref;
        char                            *blocklist_file;
        int                              blocklist_log;
@@ -168,3 +180,5 @@ void        print_config(struct uw_conf *);
 /* parse.y */
 struct uw_conf *parse_config(char *);
 int             cmdline_symset(char *);
+
+RB_PROTOTYPE(force_tree, force_tree_entry, entry, force_tree_cmp);

Reply via email to