From 651c5aadeb2c86a5d6ec5945062241095cf49a0a Mon Sep 17 00:00:00 2001 From: Grigorii Demidov Date: Wed, 26 Apr 2017 12:09:00 +0200 Subject: [PATCH] lib: full forwarding mode support --- daemon/lua/kres-gen.lua | 2 +- lib/layer/iterate.c | 26 ++++++- lib/layer/validate.c | 120 ++++++++++++++++++++++++++++++- lib/nsrep.c | 14 ++++ lib/nsrep.h | 8 +++ lib/resolve.c | 144 ++++++++++++++++++++++++++++++++++++-- lib/rplan.h | 1 + modules/policy/policy.lua | 28 +++++++- 8 files changed, 329 insertions(+), 14 deletions(-) diff --git a/daemon/lua/kres-gen.lua b/daemon/lua/kres-gen.lua index ff1fcfd9b..c0dc75e8a 100644 --- a/daemon/lua/kres-gen.lua +++ b/daemon/lua/kres-gen.lua @@ -162,7 +162,7 @@ struct kr_context { struct kr_zonecut root_hints; char _stub[]; }; -struct query_flag {static const int NO_MINIMIZE = 1; static const int NO_THROTTLE = 2; static const int NO_IPV6 = 4; static const int NO_IPV4 = 8; static const int TCP = 16; static const int RESOLVED = 32; static const int AWAIT_IPV4 = 64; static const int AWAIT_IPV6 = 128; static const int AWAIT_CUT = 256; static const int SAFEMODE = 512; static const int CACHED = 1024; static const int NO_CACHE = 2048; static const int EXPIRING = 4096; static const int ALLOW_LOCAL = 8192; static const int DNSSEC_WANT = 16384; static const int DNSSEC_BOGUS = 32768; static const int DNSSEC_INSECURE = 65536; static const int STUB = 131072; static const int ALWAYS_CUT = 262144; static const int DNSSEC_WEXPAND = 524288; static const int PERMISSIVE = 1048576; static const int STRICT = 2097152; static const int BADCOOKIE_AGAIN = 4194304; static const int CNAME = 8388608; static const int REORDER_RR = 16777216; static const int TRACE = 33554432; static const int NO_0X20 = 67108864; static const int DNSSEC_NODS = 134217728; static const int DNSSEC_OPTOUT = 268435456; static const int NONAUTH = 536870912;}; +struct query_flag {static const int NO_MINIMIZE = 1; static const int NO_THROTTLE = 2; static const int NO_IPV6 = 4; static const int NO_IPV4 = 8; static const int TCP = 16; static const int RESOLVED = 32; static const int AWAIT_IPV4 = 64; static const int AWAIT_IPV6 = 128; static const int AWAIT_CUT = 256; static const int SAFEMODE = 512; static const int CACHED = 1024; static const int NO_CACHE = 2048; static const int EXPIRING = 4096; static const int ALLOW_LOCAL = 8192; static const int DNSSEC_WANT = 16384; static const int DNSSEC_BOGUS = 32768; static const int DNSSEC_INSECURE = 65536; static const int STUB = 131072; static const int ALWAYS_CUT = 262144; static const int DNSSEC_WEXPAND = 524288; static const int PERMISSIVE = 1048576; static const int STRICT = 2097152; static const int BADCOOKIE_AGAIN = 4194304; static const int CNAME = 8388608; static const int REORDER_RR = 16777216; static const int TRACE = 33554432; static const int NO_0X20 = 67108864; static const int DNSSEC_NODS = 134217728; static const int DNSSEC_OPTOUT = 268435456; static const int NONAUTH = 536870912; static const int FORWARD = 1073741824;}; int knot_dname_size(const knot_dname_t *); knot_dname_t *knot_dname_from_str(uint8_t *, const char *, size_t); char *knot_dname_to_str(char *, const knot_dname_t *, size_t); diff --git a/lib/layer/iterate.c b/lib/layer/iterate.c index e955aa77d..d3c79ae66 100644 --- a/lib/layer/iterate.c +++ b/lib/layer/iterate.c @@ -362,6 +362,18 @@ static int process_authority(knot_pkt_t *pkt, struct kr_request *req) int result = KR_STATE_CONSUME; const knot_pktsection_t *ns = knot_pkt_section(pkt, KNOT_AUTHORITY); + if (qry->flags & QUERY_FORWARD) { + for (unsigned i = 0; i < ns->count; ++i) { + const knot_rrset_t *rr = knot_pkt_rr(ns, i); + if (rr->type == KNOT_RRTYPE_SOA || rr->type == KNOT_RRTYPE_NS) { + if (knot_dname_in(rr->owner, knot_pkt_qname(pkt))) { + qry->zone_cut.name = knot_dname_copy(rr->owner, &req->pool); + } + } + } + return KR_STATE_CONSUME; + } + #ifdef STRICT_MODE /* AA, terminate resolution chain. */ if (knot_wire_get_aa(pkt->wire)) { @@ -605,7 +617,8 @@ static int process_answer(knot_pkt_t *pkt, struct kr_request *req) /* This answer didn't improve resolution chain, therefore must be authoritative (relaxed to negative). */ if (!is_authoritative(pkt, query)) { - if (pkt_class & (PKT_NXDOMAIN|PKT_NODATA)) { + if (!(query->flags & QUERY_FORWARD) && + pkt_class & (PKT_NXDOMAIN|PKT_NODATA)) { VERBOSE_MSG("<= lame response: non-auth sent negative response\n"); return KR_STATE_FAIL; } @@ -647,8 +660,15 @@ static int process_answer(knot_pkt_t *pkt, struct kr_request *req) if (!next) { return KR_STATE_FAIL; } - next->flags |= QUERY_AWAIT_CUT; - + if (query->flags & QUERY_FORWARD) { + next->flags |= QUERY_FORWARD; + state = kr_nsrep_copy_set(&next->ns, &query->ns); + if (state != kr_ok()) { + return KR_STATE_FAIL; + } + } else { + next->flags |= QUERY_AWAIT_CUT; + } /* Want DNSSEC if and only if it's posible to secure * this name (i.e. iff it is covered by a TA) */ if (kr_ta_covers_qry(req->ctx, cname, query->stype)) { diff --git a/lib/layer/validate.c b/lib/layer/validate.c index ea293e113..52b7fa810 100644 --- a/lib/layer/validate.c +++ b/lib/layer/validate.c @@ -384,6 +384,9 @@ static int update_delegation(struct kr_request *req, struct kr_query *qry, knot_ qry->flags |= QUERY_DNSSEC_NODS; } return ret; + } else if (qry->flags & QUERY_FORWARD && qry->parent) { + struct kr_query *parent = qry->parent; + parent->zone_cut.name = knot_dname_copy(qry->sname, parent->zone_cut.pool); } /* Extend trust anchor */ @@ -454,6 +457,13 @@ static int rrsig_not_found(kr_layer_t *ctx, const knot_rrset_t *rr) } else { next->flags |= QUERY_AWAIT_CUT; } + if (qry->flags & QUERY_FORWARD) { + int state = kr_nsrep_copy_set(&next->ns, &qry->ns); + if (state != kr_ok()) { + return KR_STATE_FAIL; + } + next->flags &= ~QUERY_AWAIT_CUT; + } next->flags |= QUERY_DNSSEC_WANT; return KR_STATE_YIELD; } @@ -538,6 +548,104 @@ static bool check_empty_answer(kr_layer_t *ctx, knot_pkt_t *pkt) return ((an->count != 0) && (num_entries == 0)) ? false : true; } +static void unsigned_forward(kr_layer_t *ctx, knot_pkt_t *pkt) +{ + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + const uint16_t qtype = knot_pkt_qtype(pkt); + + if (qtype != KNOT_RRTYPE_DS) { + struct kr_rplan *rplan = &req->rplan; + struct kr_query *next = kr_rplan_push(rplan, qry, qry->sname, qry->sclass, KNOT_RRTYPE_DS); + int state = kr_nsrep_copy_set(&next->ns, &qry->ns); + if (state != kr_ok()) { + return; + } + kr_zonecut_set(&next->zone_cut, qry->zone_cut.name); + kr_zonecut_copy_trust(&next->zone_cut, &qry->zone_cut); + next->flags |= QUERY_DNSSEC_WANT; + return; + } + return; + + + /* We are in forwarding mode and have got unsigned answer. + * Check if answer is empty or not */ + if ((qtype == KNOT_RRTYPE_DS) || (qtype == KNOT_RRTYPE_RRSIG)) { + /* We are already trying to detect zone security status. + * Answer for DS \ RRSIG query can contains + * CNAME instead of target qtype. + * Ask for DS for parent zone in this case */ + if ((knot_pkt_section(pkt, KNOT_ANSWER)->count != 0) && + !(qry->flags & QUERY_CNAME)) { + /* Answer isn't empty and doesn't contain CNAME. + * Do nothing */ + return; + } + } else if ((knot_pkt_section(pkt, KNOT_ANSWER)->count != 0) && + !(qry->flags & QUERY_CNAME)) { + /* Answer isn't empty. */ + return; + } + + /* AUTHORITY can contain SOA */ + if (knot_pkt_section(pkt, KNOT_AUTHORITY)->count > 1) { + return; + } + + const knot_dname_t *qname = NULL; + bool has_soa = false; + if (knot_pkt_section(pkt, KNOT_AUTHORITY)->count == 1) { + const knot_pktsection_t *sec = knot_pkt_section(pkt, KNOT_AUTHORITY); + const knot_rrset_t *rr = knot_pkt_rr(sec, 0); + if (rr->type != KNOT_RRTYPE_SOA) { + return; + } + qname = rr->owner; + has_soa = true; + } + + /* check_signer() is going to return KR_STATE_YIELD. + * This will cause refetching DS for current zone name by check_trust_chain() + * (i.e. - we do not receive RRSIG at all and + * want to figure out with zone security status). + * Here we set current zone name. When qtype is : + * 1) RRSIG + SOA - use SOA owner + * 2) RRSIG - use qname + * 3) DS + SOA - next-label(SOA owner) + * 4) DS - use next-label(qname) */ + if (qtype != KNOT_RRTYPE_DS) { + /* We have got empty answer. + * It is necessary to figure out with zone security status. + * If there is SOA. use SOA owner since it is exact zone name. + * If no, try the owner of packet qname. */ + if (!has_soa) { + qname = knot_pkt_qname(pkt); + } + } else { + /* We already trying to figure out with security status. + * In previous steps DS query was spawned. + * Here we have got empty answer for DS query. + * If there is SOA. use next-label (SOA owner), + * otherwise use next-label (packet qname) */ + if (has_soa) { + if (knot_dname_is_equal(qname, knot_pkt_qname(pkt)) && qname[0] != '\0') { + qname = knot_wire_next_label(qname, NULL); + } + } else { + qname = knot_pkt_qname(pkt); + if (qname[0] != '\0') { + qname = knot_wire_next_label(qname, NULL); + } + } + } + if (qname[0] != '\0') { + qry->zone_cut.name = knot_dname_copy(qname, &req->pool); + } + + return; +} + static int check_signer(kr_layer_t *ctx, knot_pkt_t *pkt) { struct kr_request *req = ctx->req; @@ -565,6 +673,9 @@ static int check_signer(kr_layer_t *ctx, knot_pkt_t *pkt) * for both parent and child, * and child zone is not signed. */ qry->zone_cut.name = knot_dname_copy(qname, &req->pool); + } else if (qry->flags & QUERY_FORWARD) { + unsigned_forward(ctx, pkt); + return KR_STATE_YIELD; } } else if (knot_dname_is_sub(signer, qry->zone_cut.name)) { /* Key signer is below current cut, advance and refetch keys. */ @@ -580,8 +691,11 @@ static int check_signer(kr_layer_t *ctx, knot_pkt_t *pkt) } qry->zone_cut.name = knot_dname_copy(signer, &req->pool); } /* else zone cut matches, but DS/DNSKEY doesn't => refetch. */ - VERBOSE_MSG(qry, ">< cut changed, needs revalidation\n"); - return KR_STATE_YIELD; + if (qry->stype != KNOT_RRTYPE_DS) { + /* zone cut matches, but DS/DNSKEY doesn't => refetch. */ + VERBOSE_MSG(qry, ">< cut changed, needs revalidation\n"); + return KR_STATE_YIELD; + } } return KR_STATE_DONE; } @@ -803,7 +917,7 @@ static int validate(kr_layer_t *ctx, knot_pkt_t *pkt) qtype = KNOT_RRTYPE_DS; } /* Update parent query zone cut */ - if (qry->parent) { + if (qry->parent && pkt_rcode != KNOT_RCODE_NXDOMAIN) { if (update_parent_keys(qry, qtype) != 0) { return KR_STATE_FAIL; } diff --git a/lib/nsrep.c b/lib/nsrep.c index 8139a223c..564caf30c 100644 --- a/lib/nsrep.c +++ b/lib/nsrep.c @@ -321,3 +321,17 @@ int kr_nsrep_update_rep(struct kr_nsrep *ns, unsigned reputation, kr_nsrep_lru_t } return kr_ok(); } + +int kr_nsrep_copy_set(struct kr_nsrep *dst, const struct kr_nsrep *src) +{ + if (!dst || !src ) { + return kr_error(EINVAL); + } + + memcpy(dst, src, sizeof(struct kr_nsrep)); + dst->name = (const uint8_t *)""; + dst->score = KR_NS_UNKNOWN; + dst->reputation = 0; + + return kr_ok(); +} diff --git a/lib/nsrep.h b/lib/nsrep.h index a0d9aeb90..302d984c1 100644 --- a/lib/nsrep.h +++ b/lib/nsrep.h @@ -142,3 +142,11 @@ int kr_nsrep_update_rtt(struct kr_nsrep *ns, const struct sockaddr *addr, */ KR_EXPORT int kr_nsrep_update_rep(struct kr_nsrep *ns, unsigned reputation, kr_nsrep_lru_t *cache); +/** + * Copy NSSET reputation information and resets score. + * + * @param dst updated NS representation + * @param src source NS representation + * @return 0 on success, error code on failure + */ +int kr_nsrep_copy_set(struct kr_nsrep *dst, const struct kr_nsrep *src); diff --git a/lib/resolve.c b/lib/resolve.c index 1012fbb74..f951a08f4 100644 --- a/lib/resolve.c +++ b/lib/resolve.c @@ -659,6 +659,10 @@ static int query_finalize(struct kr_request *request, struct kr_query *qry, knot knot_wire_set_cd(pkt->wire); } /* Full resolution (ask for +cd and +do) */ + } else if (qry->flags & QUERY_FORWARD) { + knot_wire_set_rd(pkt->wire); + knot_edns_set_do(pkt->opt_rr); + knot_wire_set_cd(pkt->wire); } else if (qry->flags & QUERY_DNSSEC_WANT) { knot_edns_set_do(pkt->opt_rr); knot_wire_set_cd(pkt->wire); @@ -918,6 +922,120 @@ static struct kr_query *zone_cut_subreq(struct kr_rplan *rplan, struct kr_query return next; } +static int forward_trust_chain_check(struct kr_request *request, struct kr_query *qry, bool resume) +{ + struct kr_rplan *rplan = &request->rplan; + map_t *trust_anchors = &request->ctx->trust_anchors; + map_t *negative_anchors = &request->ctx->negative_anchors; + + assert(qry->flags & QUERY_FORWARD); + printf("QUERY_RESOLVED? %i\n", (qry->flags & QUERY_RESOLVED)); + printf("QUERY_NO_MINIMIZE? %i\n", (qry->flags & QUERY_NO_MINIMIZE)); + + if (qry->flags & QUERY_DNSSEC_INSECURE) { + return KR_STATE_PRODUCE; + } + + const knot_dname_t* wanted_name = qry->zone_cut.name; + bool nods = false; + if (qry->parent == NULL && !resume) { + wanted_name = qry->sname; + int cut_labels = knot_dname_labels(qry->zone_cut.name, NULL); + int wanted_name_labels = knot_dname_labels(wanted_name, NULL); + while(wanted_name[0] && wanted_name_labels > cut_labels + 1) { + wanted_name = knot_wire_next_label(wanted_name, NULL); + wanted_name_labels -= 1; + } + nods = (wanted_name == qry->sname); + } + + kr_dname_print(qry->zone_cut.name, "zone_cut : ", "\n"); + kr_dname_print(wanted_name, "wanted_name : ", "\n"); + for (int i = 0; i < request->rplan.resolved.len; ++i) { + struct kr_query *q = request->rplan.resolved.at[i]; + if (q->parent == qry && + q->sclass == qry->sclass && + q->stype == KNOT_RRTYPE_DS && + (q->flags & QUERY_DNSSEC_NODS) && + knot_dname_is_equal(q->sname, wanted_name)) { + nods = true; + VERBOSE_MSG(qry, "stop chasing trust chain\n"); + break; + } + } + + nods = nods || + ((qry->stype == KNOT_RRTYPE_DS) && + knot_dname_is_equal(wanted_name, qry->sname)); + + /* Disable DNSSEC if it enters NTA. */ + if (kr_ta_get(negative_anchors, wanted_name)){ + VERBOSE_MSG(qry, ">< negative TA, going insecure\n"); + qry->flags &= ~QUERY_DNSSEC_WANT; + } + + /* Enable DNSSEC if enters a new island of trust. */ + bool want_secured = (qry->flags & QUERY_DNSSEC_WANT) && + !knot_wire_get_cd(request->answer->wire); + if (!(qry->flags & QUERY_DNSSEC_WANT) && + !knot_wire_get_cd(request->answer->wire) && + kr_ta_get(trust_anchors, wanted_name)) { + qry->flags |= QUERY_DNSSEC_WANT; + want_secured = true; + WITH_VERBOSE { + char qname_str[KNOT_DNAME_MAXLEN]; + knot_dname_to_str(qname_str, wanted_name, sizeof(qname_str)); + VERBOSE_MSG(qry, ">< TA: '%s'\n", qname_str); + } + } + + if (want_secured && !qry->zone_cut.trust_anchor) { + knot_rrset_t *ta_rr = kr_ta_get(trust_anchors, wanted_name); + if (!ta_rr) { + char name[] = "\0"; + ta_rr = kr_ta_get(trust_anchors, (knot_dname_t*)name); + } + qry->zone_cut.trust_anchor = knot_rrset_copy(ta_rr, qry->zone_cut.pool); + } + + const bool has_ta = (qry->zone_cut.trust_anchor != NULL); + const knot_dname_t *ta_name = (has_ta ? qry->zone_cut.trust_anchor->owner : NULL); + const bool refetch_ta = (!has_ta || !knot_dname_is_equal(wanted_name, ta_name)); + if (!nods && want_secured && refetch_ta) { + struct kr_query *next = kr_rplan_push(rplan, qry, wanted_name, qry->sclass, KNOT_RRTYPE_DS); + if (!next) { + return KR_STATE_FAIL; + } + int state = kr_nsrep_copy_set(&next->ns, &qry->ns); + if (state != kr_ok()) { + return KR_STATE_FAIL; + } + kr_zonecut_set(&next->zone_cut, qry->zone_cut.name); + kr_zonecut_copy_trust(&next->zone_cut, &qry->zone_cut); + next->flags |= QUERY_DNSSEC_WANT; + return KR_STATE_DONE; + } + + /* Try to fetch missing DNSKEY. + * Do not fetch if this is a DNSKEY subrequest to avoid circular dependency. */ + const bool is_dnskey_subreq = kr_rplan_satisfies(qry, ta_name, KNOT_CLASS_IN, KNOT_RRTYPE_DNSKEY); + const bool refetch_key = has_ta && (!qry->zone_cut.key || !knot_dname_is_equal(ta_name, qry->zone_cut.key->owner)); + printf("has_ta %i\n", has_ta); + printf("want_secured %i refetch_key %i is_dnskey_subreq %i\n", want_secured, refetch_key, is_dnskey_subreq); + if (want_secured && refetch_key && !is_dnskey_subreq) { + struct kr_query *next = zone_cut_subreq(rplan, qry, ta_name, KNOT_RRTYPE_DNSKEY); + if (!next) { + return KR_STATE_FAIL; + } + int state = kr_nsrep_copy_set(&next->ns, &qry->ns); + if (state != kr_ok()) { + return KR_STATE_FAIL; + } + return KR_STATE_DONE; + } + return KR_STATE_PRODUCE; +} + /* @todo: Validator refactoring, keep this in driver for now. */ static int trust_chain_check(struct kr_request *request, struct kr_query *qry) { @@ -970,7 +1088,15 @@ static int trust_chain_check(struct kr_request *request, struct kr_query *qry) if (!next) { return KR_STATE_FAIL; } - next->flags |= QUERY_AWAIT_CUT|QUERY_DNSSEC_WANT; + if (qry->flags & QUERY_FORWARD) { + int state = kr_nsrep_copy_set(&next->ns, &qry->ns); + if (state != kr_ok()) { + return KR_STATE_FAIL; + } + } else { + next->flags |= QUERY_AWAIT_CUT; + } + next->flags |= QUERY_DNSSEC_WANT; return KR_STATE_DONE; } /* Try to fetch missing DNSKEY (either missing or above current cut). @@ -996,6 +1122,12 @@ static int zone_cut_check(struct kr_request *request, struct kr_query *qry, knot return KR_STATE_PRODUCE; } + /* Forwarding to upstream resolver mode. + * Since forwarding targets already are in qry->ns - + * cut fetching is not needed. */ + if (qry->flags & QUERY_FORWARD) { + return forward_trust_chain_check(request, qry, false); + } if (!(qry->flags & QUERY_AWAIT_CUT)) { /* The query was resolved from cache. * Spawn DS \ DNSKEY requests if needed and exit */ @@ -1064,7 +1196,10 @@ int kr_resolve_produce(struct kr_request *request, struct sockaddr **dst, int *t struct kr_query *qry = array_tail(rplan->pending); if (qry->deferred != NULL) { /* @todo: Refactoring validator, check trust chain before resuming. */ - switch(trust_chain_check(request, qry)) { + int state = (qry->flags & QUERY_FORWARD) ? + forward_trust_chain_check(request, qry, true) : + trust_chain_check(request, qry); + switch(state) { case KR_STATE_FAIL: return KR_STATE_FAIL; case KR_STATE_DONE: return KR_STATE_PRODUCE; default: break; @@ -1138,12 +1273,13 @@ ns_election: return KR_STATE_FAIL; } - const bool retry = (qry->flags & (QUERY_TCP|QUERY_STUB|QUERY_BADCOOKIE_AGAIN)); + const bool retry = (qry->flags & (QUERY_TCP|QUERY_STUB|QUERY_FORWARD|QUERY_BADCOOKIE_AGAIN)); if (qry->flags & (QUERY_AWAIT_IPV4|QUERY_AWAIT_IPV6)) { kr_nsrep_elect_addr(qry, request->ctx); } else if (!qry->ns.name || !retry) { /* Keep NS when requerying/stub/badcookie. */ /* Root DNSKEY must be fetched from the hints to avoid chicken and egg problem. */ - if (qry->sname[0] == '\0' && qry->stype == KNOT_RRTYPE_DNSKEY) { + if (qry->sname[0] == '\0' && qry->stype == KNOT_RRTYPE_DNSKEY + && !(qry->stype | QUERY_FORWARD)) { kr_zonecut_set_sbelt(request->ctx, &qry->zone_cut); qry->flags |= QUERY_NO_THROTTLE; /* Pick even bad SBELT servers */ } diff --git a/lib/rplan.h b/lib/rplan.h index 368833dac..ce5c2f6ca 100644 --- a/lib/rplan.h +++ b/lib/rplan.h @@ -57,6 +57,7 @@ X(DNSSEC_OPTOUT, 1 << 28) /**< Closest encloser proof has optout */ \ X(NONAUTH, 1 << 29) /**< Non-authoritative in-bailiwick records are enough. * TODO: utilize this also outside cache. */ \ + X(FORWARD, 1 << 30) /**< Forward all queries to upstream; validate answers */ \ /* 1 << 31 Used by ../modules/dns64/dns64.lua */ /** Query flags */ diff --git a/modules/policy/policy.lua b/modules/policy/policy.lua index d3ae783d2..8385585fb 100644 --- a/modules/policy/policy.lua +++ b/modules/policy/policy.lua @@ -65,12 +65,12 @@ local function mirror(target) end -- Forward request, and solve as stub query -local function forward(target) +local function stub(target) local list = {} if type(target) == 'table' then for _, v in pairs(target) do table.insert(list, addr2sock(v)) - assert(#list <= 4, 'at most 4 FORWARD targets are supported') + assert(#list <= 4, 'at most 4 STUB targets are supported') end else table.insert(list, addr2sock(target)) @@ -85,6 +85,28 @@ local function forward(target) end end +-- Forward request and all subrequests to upstream; validate answers +local function forward(target) + local list = {} + if type(target) == 'table' then + for _, v in pairs(target) do + table.insert(list, addr2sock(v)) + assert(#list <= 4, 'at most 4 FORWARD targets are supported') + end + else + table.insert(list, addr2sock(target)) + end + return function(state, req) + req = kres.request_t(req) + local qry = req:current() + req.options = bit.bor(bit.bor(req.options, kres.query.FORWARD), kres.query.NO_MINIMIZE) + qry.flags = bit.band(bit.bor(qry.flags, kres.query.FORWARD), bit.bnot(kres.query.ALWAYS_CUT)) + qry.flags = bit.bor(qry.flags, kres.query.NO_MINIMIZE) + qry:nslist(list) + return state + end +end + -- Rewrite records in packet local function reroute(tbl, names) -- Import renumbering rules @@ -110,7 +132,7 @@ end local policy = { -- Policies PASS = 1, DENY = 2, DROP = 3, TC = 4, QTRACE = 5, - FORWARD = forward, REROUTE = reroute, MIRROR = mirror, FLAGS = flags, + FORWARD = forward, STUB = stub, REROUTE = reroute, MIRROR = mirror, FLAGS = flags, -- Special values ANY = 0, } -- 2.47.2