diff options
Diffstat (limited to 'src/knot/modules/dnsproxy')
-rw-r--r-- | src/knot/modules/dnsproxy/Makefile.inc | 13 | ||||
-rw-r--r-- | src/knot/modules/dnsproxy/dnsproxy.c | 191 | ||||
-rw-r--r-- | src/knot/modules/dnsproxy/dnsproxy.rst | 125 |
3 files changed, 329 insertions, 0 deletions
diff --git a/src/knot/modules/dnsproxy/Makefile.inc b/src/knot/modules/dnsproxy/Makefile.inc new file mode 100644 index 0000000..86f1577 --- /dev/null +++ b/src/knot/modules/dnsproxy/Makefile.inc @@ -0,0 +1,13 @@ +knot_modules_dnsproxy_la_SOURCES = knot/modules/dnsproxy/dnsproxy.c +EXTRA_DIST += knot/modules/dnsproxy/dnsproxy.rst + +if STATIC_MODULE_dnsproxy +libknotd_la_SOURCES += $(knot_modules_dnsproxy_la_SOURCES) +endif + +if SHARED_MODULE_dnsproxy +knot_modules_dnsproxy_la_LDFLAGS = $(KNOTD_MOD_LDFLAGS) +knot_modules_dnsproxy_la_CPPFLAGS = $(KNOTD_MOD_CPPFLAGS) +knot_modules_dnsproxy_la_LIBADD = $(libcontrib_LIBS) +pkglib_LTLIBRARIES += knot/modules/dnsproxy.la +endif diff --git a/src/knot/modules/dnsproxy/dnsproxy.c b/src/knot/modules/dnsproxy/dnsproxy.c new file mode 100644 index 0000000..b44b136 --- /dev/null +++ b/src/knot/modules/dnsproxy/dnsproxy.c @@ -0,0 +1,191 @@ +/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "contrib/net.h" +#include "knot/include/module.h" +#include "knot/conf/schema.h" +#include "knot/query/capture.h" // Forces static module! +#include "knot/query/requestor.h" // Forces static module! + +#define MOD_REMOTE "\x06""remote" +#define MOD_ADDRESS "\x07""address" +#define MOD_TCP_FASTOPEN "\x0C""tcp-fastopen" +#define MOD_TIMEOUT "\x07""timeout" +#define MOD_FALLBACK "\x08""fallback" +#define MOD_CATCH_NXDOMAIN "\x0E""catch-nxdomain" + +const yp_item_t dnsproxy_conf[] = { + { MOD_REMOTE, YP_TREF, YP_VREF = { C_RMT }, YP_FNONE, + { knotd_conf_check_ref } }, + { MOD_TIMEOUT, YP_TINT, YP_VINT = { 0, INT32_MAX, 500 } }, + { MOD_ADDRESS, YP_TNET, YP_VNONE, YP_FMULTI }, + { MOD_FALLBACK, YP_TBOOL, YP_VBOOL = { true } }, + { MOD_TCP_FASTOPEN, YP_TBOOL, YP_VNONE }, + { MOD_CATCH_NXDOMAIN, YP_TBOOL, YP_VNONE }, + { NULL } +}; + +int dnsproxy_conf_check(knotd_conf_check_args_t *args) +{ + knotd_conf_t rmt = knotd_conf_check_item(args, MOD_REMOTE); + if (rmt.count == 0) { + args->err_str = "no remote server specified"; + return KNOT_EINVAL; + } + + return KNOT_EOK; +} + +typedef struct { + struct sockaddr_storage remote; + struct sockaddr_storage via; + knotd_conf_t addr; + bool fallback; + bool tfo; + bool catch_nxdomain; + int timeout; +} dnsproxy_t; + +static knotd_state_t dnsproxy_fwd(knotd_state_t state, knot_pkt_t *pkt, + knotd_qdata_t *qdata, knotd_mod_t *mod) +{ + assert(pkt && qdata && mod); + + dnsproxy_t *proxy = knotd_mod_ctx(mod); + + /* Forward only queries ending with REFUSED (no zone) or NXDOMAIN (if configured) */ + if (proxy->fallback && !(qdata->rcode == KNOT_RCODE_REFUSED || + (qdata->rcode == KNOT_RCODE_NXDOMAIN && proxy->catch_nxdomain))) { + return state; + } + + /* Forward from specified addresses only if configured. */ + if (proxy->addr.count > 0) { + const struct sockaddr_storage *addr = knotd_qdata_remote_addr(qdata); + if (!knotd_conf_addr_range_match(&proxy->addr, addr)) { + return state; + } + } + + /* Forward also original TSIG. */ + if (qdata->query->tsig_rr != NULL && !proxy->fallback) { + knot_tsig_append(qdata->query->wire, &qdata->query->size, + qdata->query->max_size, qdata->query->tsig_rr); + } + + /* Capture layer context. */ + const knot_layer_api_t *capture = query_capture_api(); + struct capture_param capture_param = { + .sink = pkt + }; + + /* Create a forwarding request. */ + knot_requestor_t re; + int ret = knot_requestor_init(&re, capture, &capture_param, qdata->mm); + if (ret != KNOT_EOK) { + return state; /* Ignore, not enough memory. */ + } + + knot_request_flag_t flags = KNOT_REQUEST_NONE; + if (!net_is_stream(qdata->params->socket)) { + flags = KNOT_REQUEST_UDP; + } else if (proxy->tfo) { + flags = KNOT_REQUEST_TFO; + } + const struct sockaddr_storage *dst = &proxy->remote; + const struct sockaddr_storage *src = &proxy->via; + knot_request_t *req = knot_request_make(re.mm, dst, src, qdata->query, NULL, + flags); + if (req == NULL) { + knot_requestor_clear(&re); + return state; /* Ignore, not enough memory. */ + } + + /* Forward request. */ + ret = knot_requestor_exec(&re, req, proxy->timeout); + + knot_request_free(req, re.mm); + knot_requestor_clear(&re); + + /* Check result. */ + if (ret != KNOT_EOK) { + qdata->rcode = KNOT_RCODE_SERVFAIL; + return KNOTD_STATE_FAIL; /* Forwarding failed, SERVFAIL. */ + } else { + qdata->rcode = knot_pkt_ext_rcode(pkt); + } + + /* Respond also with TSIG. */ + if (pkt->tsig_rr != NULL && !proxy->fallback) { + knot_tsig_append(pkt->wire, &pkt->size, pkt->max_size, pkt->tsig_rr); + } + + return (proxy->fallback ? KNOTD_STATE_DONE : KNOTD_STATE_FINAL); +} + +int dnsproxy_load(knotd_mod_t *mod) +{ + dnsproxy_t *proxy = calloc(1, sizeof(*proxy)); + if (proxy == NULL) { + return KNOT_ENOMEM; + } + + knotd_conf_t remote = knotd_conf_mod(mod, MOD_REMOTE); + knotd_conf_t conf = knotd_conf(mod, C_RMT, C_ADDR, &remote); + if (conf.count > 0) { + proxy->remote = conf.multi[0].addr; + knotd_conf_free(&conf); + } + conf = knotd_conf(mod, C_RMT, C_VIA, &remote); + if (conf.count > 0) { + proxy->via = conf.multi[0].addr; + knotd_conf_free(&conf); + } + + proxy->addr = knotd_conf_mod(mod, MOD_ADDRESS); + + conf = knotd_conf_mod(mod, MOD_TIMEOUT); + proxy->timeout = conf.single.integer; + + conf = knotd_conf_mod(mod, MOD_FALLBACK); + proxy->fallback = conf.single.boolean; + + conf = knotd_conf_mod(mod, MOD_TCP_FASTOPEN); + proxy->tfo = conf.single.boolean; + + conf = knotd_conf_mod(mod, MOD_CATCH_NXDOMAIN); + proxy->catch_nxdomain = conf.single.boolean; + + knotd_mod_ctx_set(mod, proxy); + + if (proxy->fallback) { + return knotd_mod_hook(mod, KNOTD_STAGE_END, dnsproxy_fwd); + } else { + return knotd_mod_hook(mod, KNOTD_STAGE_BEGIN, dnsproxy_fwd); + } +} + +void dnsproxy_unload(knotd_mod_t *mod) +{ + dnsproxy_t *ctx = knotd_mod_ctx(mod); + if (ctx != NULL) { + knotd_conf_free(&ctx->addr); + } + free(ctx); +} + +KNOTD_MOD_API(dnsproxy, KNOTD_MOD_FLAG_SCOPE_ANY, + dnsproxy_load, dnsproxy_unload, dnsproxy_conf, dnsproxy_conf_check); diff --git a/src/knot/modules/dnsproxy/dnsproxy.rst b/src/knot/modules/dnsproxy/dnsproxy.rst new file mode 100644 index 0000000..9493738 --- /dev/null +++ b/src/knot/modules/dnsproxy/dnsproxy.rst @@ -0,0 +1,125 @@ +.. _mod-dnsproxy: + +``dnsproxy`` – Tiny DNS proxy +============================= + +The module forwards all queries, or all specific zone queries if configured +per zone, to the indicated server for resolution. If configured in the fallback +mode, only locally unsatisfied queries are forwarded. I.e. a tiny DNS proxy. +There are several uses of this feature: + +* A substitute public-facing server in front of the real one +* Local zones (poor man's "views"), rest is forwarded to the public-facing server +* Using the fallback to forward queries to a resolver +* etc. + +.. NOTE:: + The module does not alter the query/response as the resolver would, + and the original transport protocol is kept as well. + +Example +------- + +The configuration is straightforward and just a single remote server is +required:: + + remote: + - id: hidden + address: 10.0.1.1 + + mod-dnsproxy: + - id: default + remote: hidden + fallback: on + + template: + - id: default + global-module: mod-dnsproxy/default + + zone: + - domain: local.zone + +When clients query for anything in the ``local.zone``, they will be +responded to locally. The rest of the requests will be forwarded to the +specified server (``10.0.1.1`` in this case). + +Module reference +---------------- + +:: + + mod-dnsproxy: + - id: STR + remote: remote_id + timeout: INT + address: ADDR[/INT] | ADDR-ADDR ... + fallback: BOOL + tcp-fastopen: BOOL + catch-nxdomain: BOOL + +.. _mod-dnsproxy_id: + +id +.. + +A module identifier. + +.. _mod-dnsproxy_remote: + +remote +...... + +A :ref:`reference<remote_id>` to a remote server where the queries are +forwarded to. + +*Required* + +.. _mod-dnsproxy_timeout: + +timeout +....... + +A remote response timeout in milliseconds. + +*Default:* ``500`` (milliseconds) + +.. _mod-dnsproxy_address: + +address +....... + +An optional list of allowed ranges and/or subnets for query's source address. +If the query's address does not fall into any of the configured ranges, the +query isn't forwarded. + +*Default:* not set + +.. _mod-dnsproxy_fallback: + +fallback +........ + +If enabled, locally unsatisfied queries leading to REFUSED (no zone) are forwarded. +If disabled, all queries are directly forwarded without any local attempts +to resolve them. + +*Default:* ``on`` + +.. _mod-dnsproxy_tcp-fastopen: + +tcp-fastopen +............ + +If enabled, TCP Fast Open is used when forwarding TCP queries. + +*Default:* ``off`` + +.. _mod-dnsproxy_catch-nxdomain: + +catch-nxdomain +.............. + +If enabled, locally unsatisfied queries leading to NXDOMAIN are forwarded. +This option is only relevant in the fallback mode. + +*Default:* ``off`` |