diff --git a/daemon/worker.c b/daemon/worker.c index fd2419caffff1e07a0c5565f1c7c70ee6f915d01..c25f35aaddb019c4f92ca00cf7dafd12539bfdd6 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -361,6 +361,34 @@ static int request_start(struct request_ctx *ctx, knot_pkt_t *query) /* Remember query source EDNS data */ if (query->opt_rr) { req->qsource.opt = knot_rrset_copy(query->opt_rr, &req->pool); + /* Client may explicitly ask to keep + * connection alive after getting answer. + * Although rfc7828 doesn't require us to send + * this edns option back to the client + * (https://tools.ietf.org/html/rfc7828#section-3.3.2 + * states "MAY include") we are going to do it. + */ + if (knot_edns_get_option(query->opt_rr, + KNOT_EDNS_OPTION_TCP_KEEPALIVE)) { + knot_mm_t *pool = &req->answer->mm; + knot_rrset_t *opt_rr = mm_alloc(pool, sizeof(knot_rrset_t)); + if (!opt_rr) { + return kr_error(ENOMEM); + } + struct engine *engine = ctx->worker->engine; + struct network *net = &engine->net; + uint64_t timeout = net->tcp.in_idle_timeout / 100; + if (timeout > UINT16_MAX) { + timeout = UINT16_MAX; + } + uint16_t ka_size = knot_edns_keepalive_size(timeout); + uint8_t ka_buf[ka_size]; + knot_edns_keepalive_write(ka_buf, ka_size, timeout); + knot_edns_init(opt_rr, KR_EDNS_PAYLOAD, 0, KR_EDNS_VERSION, pool); + knot_edns_add_option(opt_rr, KNOT_EDNS_OPTION_TCP_KEEPALIVE, + ka_size, ka_buf, pool); + req->answer->opt_rr = opt_rr; + } } /* Start resolution */ struct worker_ctx *worker = ctx->worker; diff --git a/lib/resolve.c b/lib/resolve.c index fd067520e91399a003f70555a33da1187205a282..bc557c610a7c54949aa8fd5d9856630452c405d0 100644 --- a/lib/resolve.c +++ b/lib/resolve.c @@ -424,9 +424,16 @@ static int edns_erase_and_reserve(knot_pkt_t *pkt) return knot_pkt_reserve(pkt, len); } -static int edns_create(knot_pkt_t *pkt, knot_pkt_t *template, struct kr_request *req) +static int edns_create(knot_pkt_t *pkt, knot_rrset_t *additional, struct kr_request *req) { pkt->opt_rr = knot_rrset_copy(req->ctx->opt_rr, &pkt->mm); + if (additional) { + int ret = kr_edns_append(pkt->opt_rr, additional->rrs.rdata, &pkt->mm); + if (ret != 0) { + return ret; + } + } + size_t wire_size = knot_edns_wire_size(pkt->opt_rr); #if defined(ENABLE_COOKIES) if (req->ctx->cookie_ctx.clnt.enabled || @@ -448,12 +455,16 @@ static int edns_create(knot_pkt_t *pkt, knot_pkt_t *template, struct kr_request static int answer_prepare(knot_pkt_t *answer, knot_pkt_t *query, struct kr_request *req) { + /* There may be some edns options (like tcp-keeelalive) prepared by daemon. + * knot_pkt_init_response() is going to clear opt_rr field, + * so we must to save it. */ + knot_rrset_t *daemon_edns_opts = answer->opt_rr; if (knot_pkt_init_response(answer, query) != 0) { return kr_error(ENOMEM); /* Failed to initialize answer */ } /* Handle EDNS in the query */ if (knot_pkt_has_edns(query)) { - int ret = edns_create(answer, query, req); + int ret = edns_create(answer, daemon_edns_opts, req); if (ret != 0){ return ret; } @@ -461,6 +472,9 @@ static int answer_prepare(knot_pkt_t *answer, knot_pkt_t *query, struct kr_reque if (knot_pkt_has_dnssec(query)) { knot_edns_set_do(answer->opt_rr); } + /* edns_create() created new rrset and appended daemon_edns_opts to it. + * Deallocate daemon_edns_opts since we don't need it anymore. */ + knot_rrset_free(daemon_edns_opts, &answer->mm); } return kr_ok(); } @@ -687,7 +701,7 @@ static int query_finalize(struct kr_request *request, struct kr_query *qry, knot /* Remove any EDNS records from any previous iteration. */ ret = edns_erase_and_reserve(pkt); if (ret == 0) { - ret = edns_create(pkt, request->answer, request); + ret = edns_create(pkt, NULL, request); } if (ret == 0) { /* Stub resolution (ask for +rd and +do) */ diff --git a/lib/utils.c b/lib/utils.c index 678cec22b253c69d1c119f7349210f1c5dd110a5..852e754e7fdff86f6331689824ff9752afca078f 100644 --- a/lib/utils.c +++ b/lib/utils.c @@ -1023,6 +1023,36 @@ int knot_dname_lf2wire(knot_dname_t * const dst, uint8_t len, const uint8_t *lf) return d - dst; } +int kr_edns_append(knot_rrset_t *opt, knot_rdata_t *data, knot_mm_t *mm) +{ + assert(opt->rrs.count == 1); + + uint8_t *old_data = opt->rrs.rdata->data; + uint16_t old_data_len = opt->rrs.rdata->len; + + /* construct new RDATA */ + uint32_t new_data_len = old_data_len + data->len; + if (new_data_len > UINT16_MAX) { + return kr_error(ENOSPC); + } + + knot_rdata_t *new_data = mm_alloc(mm, offsetof(knot_rdata_t, data) + new_data_len); + if (!new_data) { + return kr_error(ENOMEM); + } + + /* concatenate two RDATA's */ + memcpy(new_data->data, old_data, old_data_len); + memcpy(new_data->data + old_data_len, data->data, data->len); + new_data->len = new_data_len; + + /* free old RDATA */ + mm_free(mm, opt->rrs.rdata); + + opt->rrs.rdata = new_data; + return kr_ok(); +} + void kr_rrset_init(knot_rrset_t *rrset, knot_dname_t *owner, uint16_t type, uint16_t rclass, uint32_t ttl) { diff --git a/lib/utils.h b/lib/utils.h index 1e6cb16ff719651e0c3481ad4253f2de856673ec..4761ed66ce2a8b2af1723db2ece3fbb74bfed859 100644 --- a/lib/utils.h +++ b/lib/utils.h @@ -440,6 +440,19 @@ static inline int kr_dname_lf(uint8_t *dst, const knot_dname_t *src, bool add_wi return KNOT_EOK; }; +/*! + * \brief Append buffer with new EDNS options to existing opt_rr. + * + * \param opt OPT RR structure to add the data to. + * \param data Buffer with edns options to be added. + * \param mm Memory context. + * + * \return 0 - success + * kr_error(ENOMEM) - out-of-memory + * kr_error(ENOSPC) - resulting buffer would be too long + */ +KR_EXPORT int kr_edns_append(knot_rrset_t *opt, knot_rdata_t *data, knot_mm_t *mm); + /* Trivial non-inline wrappers, to be used in lua. */ KR_EXPORT void kr_rrset_init(knot_rrset_t *rrset, knot_dname_t *owner, uint16_t type, uint16_t rclass, uint32_t ttl); diff --git a/tests/test_utils.c b/tests/test_utils.c index 07856560eba064c57678ba8ecde226c3a25d0776..223dc580adaa887cb44738a1a02827e6e6a96bc9 100644 --- a/tests/test_utils.c +++ b/tests/test_utils.c @@ -79,11 +79,69 @@ static void test_straddr(void **state) assert_int_not_equal(test_bitcmp(ip6_sub, ip6_out, 4), 0); } +static void test_edns_append(void **state) +{ + uint8_t *source_option[KNOT_EDNS_MAX_OPTION_CODE]; + int source_option_len[KNOT_EDNS_MAX_OPTION_CODE]; + knot_pkt_t *pkt = knot_pkt_new(NULL, KNOT_WIRE_MAX_PKTSIZE, NULL); + knot_rrset_t *opt_rr = mm_alloc(NULL, sizeof(knot_rrset_t)); + knot_edns_init(opt_rr, KR_EDNS_PAYLOAD, 0, KR_EDNS_VERSION, NULL); + pkt->opt_rr = opt_rr; + + for (uint16_t i = 0; i < KNOT_EDNS_MAX_OPTION_CODE; ++i) { + if (i == KNOT_EDNS_OPTION_TCP_KEEPALIVE) { + source_option[i] = NULL; + source_option_len[i] = 0; + continue; + } + int opt_len = kr_rand_uint(16); + if (opt_len == 0) { + opt_len = 16; + } + uint8_t *opt_buf = malloc(opt_len); + assert_non_null(opt_buf); + knot_rrset_t *opt_rr_new = mm_alloc(NULL, sizeof(knot_rrset_t)); + assert_non_null(opt_rr_new); + + for (int j = 0; j < opt_len; ++j) { + opt_buf[j] = kr_rand_uint(255); + } + + source_option[i] = opt_buf; + source_option_len[i] = opt_len; + + knot_edns_init(opt_rr_new, KR_EDNS_PAYLOAD, 0, KR_EDNS_VERSION, NULL); + knot_edns_add_option(opt_rr_new, i, opt_len, opt_buf, NULL); + kr_edns_append(opt_rr, opt_rr_new->rrs.rdata, NULL); + knot_rrset_free(opt_rr_new, NULL); + } + + knot_edns_options_t *options; + knot_edns_get_options(opt_rr, &options, NULL); + + for (uint16_t i = 0; i < KNOT_EDNS_MAX_OPTION_CODE; ++i) { + uint8_t *opt = knot_edns_get_option(opt_rr, i); + if (source_option[i] == NULL) { + assert_null(opt); + continue; + } + int opt_len = knot_edns_opt_get_length(opt); + assert_int_equal(opt_len, source_option_len[i]); + assert_int_equal(memcmp(&opt[4], source_option[i], opt_len), 0); + free(source_option[i]); + } + + mm_free(NULL, options); + knot_rrset_free(opt_rr, NULL); + knot_pkt_free(pkt); +} + int main(void) { const UnitTest tests[] = { unit_test(test_strcatdup), unit_test(test_straddr), + unit_test(test_edns_append) }; return run_tests(tests);