diff --git a/lib/cookies/cache.c b/lib/cookies/cache.c new file mode 100644 index 0000000000000000000000000000000000000000..8e03467cc14c62c655f8f368570b4f0bf59dc561 --- /dev/null +++ b/lib/cookies/cache.c @@ -0,0 +1,209 @@ +/* Copyright (C) 2014 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <libknot/db/db_lmdb.h> + +#include "lib/cookies/cache.h" +#include "lib/cookies/control.h" + +/* Key size */ +#define KEY_HSIZE (sizeof(uint8_t)) +#define KEY_SIZE (KEY_HSIZE + 16) +#define txn_api(txn) ((txn)->owner->api) +#define txn_is_valid(txn) ((txn) && (txn)->owner && txn_api(txn)) + +/** + * @internal Composed key as { u8 tag, u8[4,16] IP address } + */ +static size_t cache_key(uint8_t *buf, uint8_t tag, const void *sockaddr) +{ + assert(buf && sockaddr); + + const uint8_t *addr = NULL; + size_t addr_len = 0; + + if (kr_ok() != kr_address_bytes(sockaddr, &addr, &addr_len)) { + return 0; + } + assert(addr_len > 0); + + buf[0] = tag; + memcpy(buf + sizeof(uint8_t), addr, addr_len); + + return addr_len + KEY_HSIZE; +} + +static struct kr_cache_entry *lookup(struct kr_cache_txn *txn, uint8_t tag, + const void *sockaddr) +{ + if (!txn_is_valid(txn) || !sockaddr) { + return NULL; + } + + uint8_t keybuf[KEY_SIZE]; + size_t key_len = cache_key(keybuf, tag, sockaddr); + + /* Look up and return value */ + knot_db_val_t key = { keybuf, key_len }; + knot_db_val_t val = { NULL, 0 }; + int ret = txn_api(txn)->find(&txn->t, &key, &val, 0); + if (ret != KNOT_EOK) { + return NULL; + } + + return (struct kr_cache_entry *)val.data; +} + +static int check_lifetime(struct kr_cache_entry *found, uint32_t *timestamp) +{ + /* No time constraint */ + if (!timestamp) { + return kr_ok(); + } else if (*timestamp <= found->timestamp) { + /* John Connor record cached in the future. */ + *timestamp = 0; + return kr_ok(); + } else { + /* Check if the record is still valid. */ + uint32_t drift = *timestamp - found->timestamp; + if (drift <= found->ttl) { + *timestamp = drift; + return kr_ok(); + } + } + return kr_error(ESTALE); +} + +int kr_cookie_cache_peek(struct kr_cache_txn *txn, uint8_t tag, const void *sockaddr, + struct kr_cache_entry **entry, uint32_t *timestamp) +{ + if (!txn_is_valid(txn) || !sockaddr || !entry) { + return kr_error(EINVAL); + } + + struct kr_cache_entry *found = lookup(txn, tag, sockaddr); + if (!found) { + txn->owner->stats.miss += 1; + return kr_error(ENOENT); + } + + /* Check entry lifetime */ + *entry = found; + int ret = check_lifetime(found, timestamp); + if (ret == 0) { + txn->owner->stats.hit += 1; + } else { + txn->owner->stats.miss += 1; + } + return ret; +} + +static void entry_write(struct kr_cache_entry *dst, struct kr_cache_entry *header, knot_db_val_t data) +{ + assert(dst && header); + memcpy(dst, header, sizeof(*header)); + if (data.data) + memcpy(dst->data, data.data, data.len); +} + +int kr_cookie_cache_insert(struct kr_cache_txn *txn, + uint8_t tag, const void *sockaddr, + struct kr_cache_entry *header, knot_db_val_t data) +{ + if (!txn_is_valid(txn) || !sockaddr || !header) { + return kr_error(EINVAL); + } + + /* Insert key */ + uint8_t keybuf[KEY_SIZE]; + size_t key_len = cache_key(keybuf, tag, sockaddr); + if (key_len == 0) { + return kr_error(EILSEQ); + } + knot_db_val_t key = { keybuf, key_len }; + knot_db_val_t entry = { NULL, sizeof(*header) + data.len }; + const knot_db_api_t *db_api = txn_api(txn); + + /* LMDB can do late write and avoid copy */ + txn->owner->stats.insert += 1; + if (db_api == knot_db_lmdb_api()) { + int ret = db_api->insert(&txn->t, &key, &entry, 0); + if (ret != 0) { + return ret; + } + entry_write(entry.data, header, data); + } else { + /* Other backends must prepare contiguous data first */ + entry.data = malloc(entry.len); + if (!entry.data) { + return kr_error(ENOMEM); + } + entry_write(entry.data, header, data); + int ret = db_api->insert(&txn->t, &key, &entry, 0); + free(entry.data); + if (ret != 0) { + return ret; + } + } + + return kr_ok(); +} + +int kr_cookie_cache_peek_cookie(struct kr_cache_txn *txn, const void *sockaddr, + const uint8_t **cookie_opt, uint32_t *timestamp) +{ + if (!txn_is_valid(txn) || !sockaddr || !cookie_opt || !timestamp) { + return kr_error(EINVAL); + } + + /* Check if the RRSet is in the cache. */ + struct kr_cache_entry *entry = NULL; + int ret = kr_cookie_cache_peek(txn, KR_CACHE_COOKIE, sockaddr, &entry, timestamp); + if (ret != 0) { + return ret; + } + *cookie_opt = entry->data; + return kr_ok(); +} + +int kr_cookie_cache_insert_cookie(struct kr_cache_txn *txn, const void *sockaddr, + uint8_t *cookie_opt, uint32_t timestamp) +{ + if (!txn_is_valid(txn) || !sockaddr) { + return kr_error(EINVAL); + } + + /* Ignore empty cookie data. */ + if (!cookie_opt) { + return kr_ok(); + } + + /* Prepare header to write. */ + struct kr_cache_entry header = { + .timestamp = timestamp, + .ttl = 72000, + .rank = KR_RANK_BAD, + .flags = KR_CACHE_FLAG_NONE, + .count = 1 + }; + + size_t cookie_opt_size = knot_edns_opt_get_length(cookie_opt) + KNOT_EDNS_OPTION_HDRLEN; + + knot_db_val_t data = { cookie_opt, cookie_opt_size }; + return kr_cookie_cache_insert(txn, KR_CACHE_COOKIE, sockaddr, &header, + data); +} diff --git a/lib/cookies/cache.h b/lib/cookies/cache.h new file mode 100644 index 0000000000000000000000000000000000000000..59278b7b289092323a13a0962abc2f3dcdcae859 --- /dev/null +++ b/lib/cookies/cache.h @@ -0,0 +1,47 @@ +/* Copyright (C) 2016 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "lib/cache.h" + +#define KR_CACHE_COOKIE (KR_CACHE_USER + 'C') + +KR_EXPORT +int kr_cookie_cache_peek(struct kr_cache_txn *txn, uint8_t tag, const void *sockaddr, + struct kr_cache_entry **entry, uint32_t *timestamp); + +KR_EXPORT +int kr_cookie_cache_insert(struct kr_cache_txn *txn, + uint8_t tag, const void *sockaddr, + struct kr_cache_entry *header, knot_db_val_t data); + +KR_EXPORT +int kr_cookie_cache_peek_cookie(struct kr_cache_txn *txn, const void *sockaddr, + const uint8_t **cookie_opt, uint32_t *timestamp); + +/** + * Insert a DNS cookie (client and server) entry for the given server signature (IP address). + * @param txn transaction instance + * @param sockaddr server IP address + * @param cookie_opt whole EDNS cookie option (header, client and server) + * @param cookie_size size of the cookie + * @param timestamp current time + * @return 0 or an errcode + */ +KR_EXPORT +int kr_cookie_cache_insert_cookie(struct kr_cache_txn *txn, const void *sockaddr, + uint8_t *cookie_opt, uint32_t timestamp); diff --git a/lib/cookies/control.c b/lib/cookies/control.c index 40c2f031c41732392bb86998f9d4d29f51f7769c..20fdaa23affed2fb9d72b58b5058264d20bca7f9 100644 --- a/lib/cookies/control.c +++ b/lib/cookies/control.c @@ -19,9 +19,11 @@ #include <netinet/in.h> #include <assert.h> #include <stdint.h> +#include <string.h> #include <libknot/error.h> #include "contrib/fnv/fnv.h" +#include "lib/cookies/cache.h" #include "lib/cookies/control.h" #include "lib/layer.h" #include "lib/utils.h" @@ -30,14 +32,14 @@ static uint8_t cc[KNOT_OPT_COOKIE_CLNT] = { 1, 2, 3, 4, 5, 6, 7, 8}; -static struct secret_quantity client = { +static struct secret_quantity client_secret = { .size = KNOT_OPT_COOKIE_CLNT, .data = cc }; struct cookies_control kr_cookies_control = { .enabled = true, - .client = &client + .secret = &client_secret }; static int opt_rr_add_cookies(knot_rrset_t *opt_rr, @@ -45,14 +47,13 @@ static int opt_rr_add_cookies(knot_rrset_t *opt_rr, uint8_t *sc, uint16_t sc_len, knot_mm_t *mm) { - int ret; uint16_t cookies_size = 0; uint8_t *cookies_data = NULL; cookies_size = knot_edns_opt_cookie_data_len(sc_len); - ret = knot_edns_reserve_option(opt_rr, KNOT_EDNS_OPTION_COOKIE, - cookies_size, &cookies_data, mm); + int ret = knot_edns_reserve_option(opt_rr, KNOT_EDNS_OPTION_COOKIE, + cookies_size, &cookies_data, mm); if (ret != KNOT_EOK) { return ret; } @@ -69,8 +70,33 @@ static int opt_rr_add_cookies(knot_rrset_t *opt_rr, return KNOT_EOK; } -static void obtain_address(void *sockaddr, uint8_t **addr, size_t *len) +static int opt_rr_add_option(knot_rrset_t *opt_rr, uint8_t *option, + knot_mm_t *mm) { + assert(opt_rr && option); + + uint8_t *reserved_data = NULL; + uint16_t opt_code = knot_edns_opt_get_code(option); + uint16_t opt_len = knot_edns_opt_get_length(option); + uint8_t *opt_data = knot_edns_opt_get_data(option); + + int ret = knot_edns_reserve_option(opt_rr, opt_code, + opt_len, &reserved_data, mm); + if (ret != KNOT_EOK) { + return ret; + } + assert(reserved_data); + + memcpy(reserved_data, opt_data, opt_len); + return KNOT_EOK; +} + +int kr_address_bytes(const void *sockaddr, const uint8_t **addr, size_t *len) +{ + if (!sockaddr || !addr || !len) { + return kr_error(EINVAL); + } + assert(sockaddr && addr && len); int addr_family = ((struct sockaddr *) sockaddr)->sa_family; @@ -89,7 +115,7 @@ static void obtain_address(void *sockaddr, uint8_t **addr, size_t *len) *len = 0; addr_family = AF_UNSPEC; DEBUG_MSG(NULL, "%s\n", "could obtain IP address"); - return; + return kr_error(EINVAL); break; } @@ -98,6 +124,8 @@ static void obtain_address(void *sockaddr, uint8_t **addr, size_t *len) inet_ntop(addr_family, *addr, ns_str, sizeof(ns_str)); DEBUG_MSG(NULL, "obtaned IP address '%s'\n", ns_str); } + + return kr_ok(); } int kr_client_cokie_fnv64(uint8_t cc_buf[KNOT_OPT_COOKIE_CLNT], @@ -113,7 +141,7 @@ int kr_client_cokie_fnv64(uint8_t cc_buf[KNOT_OPT_COOKIE_CLNT], return kr_error(EINVAL); } - uint8_t *addr = NULL; + const uint8_t *addr = NULL; size_t size = 0; Fnv64_t hash_val = FNV1A_64_INIT; @@ -121,23 +149,23 @@ int kr_client_cokie_fnv64(uint8_t cc_buf[KNOT_OPT_COOKIE_CLNT], /* Client address currently always ignored. */ #if 0 if (clnt_sockaddr) { - obtain_address(clnt_sockaddr, &addr, &size); - if (addr && size) { + if (kr_ok() == kr_address_bytes(clnt_sockaddr, &addr, &size)) { + assert(addr && size); hash_val = fnv_64a_buf(addr, size, hash_val); } } #endif if (srvr_sockaddr) { - obtain_address(srvr_sockaddr, &addr, &size); - if (addr && size) { - hash_val = fnv_64a_buf(addr, size, hash_val); + if (kr_ok() == kr_address_bytes(srvr_sockaddr, &addr, &size)) { + assert(addr && size); + hash_val = fnv_64a_buf((void *) addr, size, hash_val); } } if (secret && secret->size && secret->data) { DEBUG_MSG(NULL, "%s\n", "adding client secret into cookie"); - hash_val = fnv_64a_buf(addr, size, hash_val); + hash_val = fnv_64a_buf((void *) addr, size, hash_val); } assert(KNOT_OPT_COOKIE_CLNT == sizeof(hash_val)); @@ -153,18 +181,23 @@ int kr_request_put_cookie(struct cookies_control *cntrl, void *clnt_sockaddr, assert(cntrl); assert(pkt); - uint8_t cc[KNOT_OPT_COOKIE_CLNT]; - if (!pkt->opt_rr) { return kr_ok(); } - if (!cntrl->client) { + if (!cntrl->secret) { return kr_error(EINVAL); } - int ret = kr_client_cokie_fnv64(cc, clnt_sockaddr, srvr_sockaddr, - cntrl->client); +// + struct kr_cache_txn txn; + const uint8_t *cached_cookie = NULL; + uint32_t timestamp = 0; + kr_cache_txn_begin(&kr_cookies_control.cache, &txn, KNOT_DB_RDONLY); + int ret = kr_cookie_cache_peek_cookie(&txn, srvr_sockaddr, + &cached_cookie, ×tamp); + bool cached = (ret == kr_ok()); +// /* This is a very nasty hack that prevents the packet to be corrupted * when using contemporary 'Cookie interface'. */ @@ -181,9 +214,21 @@ int kr_request_put_cookie(struct cookies_control *cntrl, void *clnt_sockaddr, } #endif - /* TODO -- generate client cookie from client address, server address - * and secret quantity. */ - ret = opt_rr_add_cookies(pkt->opt_rr, cc, NULL, 0, &pkt->mm); + if (cached) { + ret = opt_rr_add_option(pkt->opt_rr, (uint8_t *) cached_cookie, + &pkt->mm); + } else { + /* Generate new client cookie only. */ + uint8_t cc[KNOT_OPT_COOKIE_CLNT]; + ret = kr_client_cokie_fnv64(cc, clnt_sockaddr, srvr_sockaddr, + cntrl->secret); + + /* TODO -- generate client cookie from client address, server address + * and secret quantity. */ + ret = opt_rr_add_cookies(pkt->opt_rr, cc, NULL, 0, &pkt->mm); + } + + kr_cache_txn_abort(&txn); /* Write to packet. */ assert(pkt->current == KNOT_ADDITIONAL); diff --git a/lib/cookies/control.h b/lib/cookies/control.h index 98a16648ef52aff29eed97bec6fffb2a1afab6d0..9703bf535b8b1a26e5eae8b50a3d5cceeb1e869d 100644 --- a/lib/cookies/control.h +++ b/lib/cookies/control.h @@ -34,7 +34,7 @@ struct secret_quantity { /** DNSSEC cookies controlling structure. */ struct cookies_control { bool enabled; /*!< Enabled/disables DNS cookies functionality. */ - struct secret_quantity *client; /*!< Client secret quantity. */ + struct secret_quantity *secret; /*!< Client secret quantity. */ struct kr_cache cache; /*!< Server cookies cache. */ }; @@ -43,6 +43,14 @@ struct cookies_control { KR_EXPORT extern struct cookies_control kr_cookies_control; +/** + * Get pointers to IP address bytes. + * @param sockaddr socket address + * @param addr pointer to address + * @param len address length + */ +int kr_address_bytes(const void *sockaddr, const uint8_t **addr, size_t *len); + /** * Compute client cookie. * @not At least one of the arguments must be non-null. diff --git a/lib/lib.mk b/lib/lib.mk index 0408aa71ecf297325077dcca40da9babec1ba11b..4dafe5c8e37d4bafb14f30fbf389d1f020d87eee 100644 --- a/lib/lib.mk +++ b/lib/lib.mk @@ -5,6 +5,7 @@ libkres_SOURCES := \ lib/layer/validate.c \ lib/layer/rrcache.c \ lib/layer/pktcache.c \ + lib/cookies/cache.c \ lib/cookies/control.c \ lib/dnssec/nsec.c \ lib/dnssec/nsec3.c \ @@ -25,6 +26,7 @@ libkres_HEADERS := \ lib/generic/map.h \ lib/generic/set.h \ lib/layer.h \ + lib/cookies/cache.h \ lib/cookies/control.h \ lib/dnssec/nsec.h \ lib/dnssec/nsec3.h \ diff --git a/modules/cookies/cookies.c b/modules/cookies/cookies.c index b2ee1a10332b83ec54a85e08499aa052723503ad..8ebcd01ac11f15566b255e9c0e6b290987e06ea2 100644 --- a/modules/cookies/cookies.c +++ b/modules/cookies/cookies.c @@ -27,6 +27,7 @@ #include <time.h> #include "daemon/engine.h" +#include "lib/cookies/cache.h" #include "lib/cookies/control.h" #include "lib/module.h" #include "lib/layer.h" @@ -69,6 +70,39 @@ static int check_client_cookie(const uint8_t cc[KNOT_OPT_COOKIE_CLNT], return kr_error(EINVAL); } +/** + * Tries to guess the name server address from the reputation mechanism. + */ +static const struct sockaddr *guess_server_addr(const uint8_t cc[KNOT_OPT_COOKIE_CLNT], + struct kr_nsrep *nsrep, + struct secret_quantity *secret) +{ + assert(cc && nsrep && secret); + + const struct sockaddr *sockaddr = NULL; + + /* Abusing name server reputation mechanism to obtain IP addresses. */ + for (int i = 0; i < KR_NSREP_MAXADDR; ++i) { + if (nsrep->addr[i].ip.sa_family == AF_UNSPEC) { + break; + } + int ret = check_client_cookie(cc, NULL, &nsrep->addr[i], secret); + WITH_DEBUG { + char addr_str[INET6_ADDRSTRLEN]; + inet_ntop(nsrep->addr[i].ip.sa_family, + kr_nsrep_inaddr(nsrep->addr[i]), addr_str, + sizeof(addr_str)); + DEBUG_MSG(NULL, "nsrep address '%s' %d\n", addr_str, ret); + } + if (ret == kr_ok()) { + sockaddr = (struct sockaddr *) &nsrep->addr[i]; + break; + } + } + + return sockaddr; +} + /** Process response. */ static int check_response(knot_layer_t *ctx, knot_pkt_t *pkt) { @@ -104,31 +138,14 @@ static int check_response(knot_layer_t *ctx, knot_pkt_t *pkt) DEBUG_MSG(NULL, "%s\n", "checking response for received cookies"); - const void *srvr_sockaddr = NULL; + const struct sockaddr *srvr_sockaddr = NULL; struct kr_request *req = ctx->data; struct kr_query *qry = req->current_query; struct kr_nsrep *ns = &qry->ns; /* Abusing name server reputation mechanism to obtain IP addresses. */ - for (int i = 0; i < KR_NSREP_MAXADDR; ++i) { - if (ns->addr[i].ip.sa_family == AF_UNSPEC) { - break; - } - ret = check_client_cookie(cc, NULL, &ns->addr[i], kr_cookies_control.client); - WITH_DEBUG { - char addr_str[INET6_ADDRSTRLEN]; - inet_ntop(ns->addr[i].ip.sa_family, - kr_nsrep_inaddr(ns->addr[i]), addr_str, - sizeof(addr_str)); - DEBUG_MSG(NULL, "nsrep address '%s' %d\n", addr_str, ret); - } - if (ret == kr_ok()) { - srvr_sockaddr = &ns->addr[i]; - break; - } - } - + srvr_sockaddr = guess_server_addr(cc, ns, kr_cookies_control.secret); if (!srvr_sockaddr) { DEBUG_MSG(NULL, "%s\n", "could not ensure any server for received cookie"); @@ -136,8 +153,26 @@ static int check_response(knot_layer_t *ctx, knot_pkt_t *pkt) return ctx->state; } + struct kr_cache_txn txn; + if (kr_cache_txn_begin(&kr_cookies_control.cache, &txn, 0) != 0) { + /* Could not acquire cache. */ + return ctx->state; + } + DEBUG_MSG(NULL, "%s\n", "caching server cookie"); + /* TODO -- Cache only missing or change cookie. */ + + ret = kr_cookie_cache_insert_cookie(&txn, srvr_sockaddr, cookie_opt, + qry->timestamp.tv_sec); + if (ret != kr_ok()) { + kr_cache_txn_abort(&txn); + } else { + DEBUG_MSG(NULL, "%s\n", "cookie_cached"); + kr_cache_txn_commit(&txn); + } + + print_packet_dflt(pkt); return ctx->state;