rrl.c 5.11 KB
Newer Older
1
/*  Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2 3 4 5 6 7 8 9 10 11 12 13

    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
14
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
15 16
 */

17 18
#include "knot/include/module.h"
#include "knot/nameserver/process_query.h" // Dependency on qdata->extra!
19 20 21 22 23 24 25
#include "knot/modules/rrl/functions.h"

#define MOD_RATE_LIMIT		"\x0A""rate-limit"
#define MOD_SLIP		"\x04""slip"
#define MOD_TBL_SIZE		"\x0A""table-size"
#define MOD_WHITELIST		"\x09""whitelist"

26 27 28 29
const yp_item_t rrl_conf[] = {
	{ MOD_RATE_LIMIT, YP_TINT, YP_VINT = { 1, INT32_MAX } },
	{ MOD_SLIP,       YP_TINT, YP_VINT = { 0, RRL_SLIP_MAX, 1 } },
	{ MOD_TBL_SIZE,   YP_TINT, YP_VINT = { 1, INT32_MAX, 393241 } },
30
	{ MOD_WHITELIST,  YP_TNET, YP_VNONE, YP_FMULTI },
31 32 33
	{ NULL }
};

34
int rrl_conf_check(knotd_conf_check_args_t *args)
35
{
36 37
	knotd_conf_t limit = knotd_conf_check_item(args, MOD_RATE_LIMIT);
	if (limit.count == 0) {
38 39 40 41 42 43 44 45 46 47
		args->err_str = "no rate limit specified";
		return KNOT_EINVAL;
	}

	return KNOT_EOK;
}

typedef struct {
	rrl_table_t *rrl;
	int slip;
48
	knotd_conf_t whitelist;
49 50
} rrl_ctx_t;

51 52 53 54 55 56 57 58 59 60
static const knot_dname_t *name_from_rrsig(const knot_rrset_t *rr)
{
	if (rr == NULL) {
		return NULL;
	}
	if (rr->type != KNOT_RRTYPE_RRSIG) {
		return NULL;
	}

	// This is a signature.
61
	return knot_rrsig_signer_name(rr->rrs.rdata);
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
}

static const knot_dname_t *name_from_authrr(const knot_rrset_t *rr)
{
	if (rr == NULL) {
		return NULL;
	}
	if (rr->type != KNOT_RRTYPE_NS && rr->type != KNOT_RRTYPE_SOA) {
		return NULL;
	}

	// This is a valid authority RR.
	return rr->owner;
}

77 78 79 80
static knotd_state_t ratelimit_apply(knotd_state_t state, knot_pkt_t *pkt,
                                     knotd_qdata_t *qdata, knotd_mod_t *mod)
{
	assert(pkt && qdata && mod);
81

82
	rrl_ctx_t *ctx = knotd_mod_ctx(mod);
83 84

	// Rate limit is not applied to TCP connections.
85
	if (!(qdata->params->flags & KNOTD_QUERY_FLAG_LIMIT_SIZE)) {
86 87 88
		return state;
	}

89 90 91 92 93
	// Rate limit is not applied to responses with a valid cookie.
	if (qdata->params->flags & KNOTD_QUERY_FLAG_COOKIE) {
		return state;
	}

94
	// Exempt clients.
95
	if (knotd_conf_addr_range_match(&ctx->whitelist, qdata->params->remote)) {
96 97 98 99
		return state;
	}

	rrl_req_t req = {
100
		.wire = pkt->wire,
101 102 103
		.query = qdata->query
	};

104
	if (!EMPTY_LIST(qdata->extra->wildcards)) {
Daniel Salzman's avatar
Daniel Salzman committed
105
		req.flags = RRL_REQ_WILDCARD;
106 107
	}

108
	// Take the zone name if known.
109
	const knot_dname_t *zone_name = knotd_qdata_zone_name(qdata);
110

111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
	// Take the signer name as zone name if there is an RRSIG.
	if (zone_name == NULL) {
		const knot_pktsection_t *ans = knot_pkt_section(pkt, KNOT_ANSWER);
		for (int i = 0; i < ans->count; i++) {
			zone_name = name_from_rrsig(knot_pkt_rr(ans, i));
			if (zone_name != NULL) {
				break;
			}
		}
	}

	// Take the NS or SOA owner name if there is no RRSIG.
	if (zone_name == NULL) {
		const knot_pktsection_t *auth = knot_pkt_section(pkt, KNOT_AUTHORITY);
		for (int i = 0; i < auth->count; i++) {
			zone_name = name_from_authrr(knot_pkt_rr(auth, i));
			if (zone_name != NULL) {
				break;
			}
		}
	}

133
	if (rrl_query(ctx->rrl, qdata->params->remote, &req, zone_name, mod) == KNOT_EOK) {
134 135 136 137
		// Rate limiting not applied.
		return state;
	}

138
	if (ctx->slip > 0 && rrl_slip_roll(ctx->slip)) {
139
		// Slip the answer.
140
		knotd_mod_stats_incr(mod, 0, 0, 1);
141
		qdata->err_truncated = true;
142
		return KNOTD_STATE_FAIL;
143 144
	} else {
		// Drop the answer.
145
		knotd_mod_stats_incr(mod, 1, 0, 1);
146
		return KNOTD_STATE_NOOP;
147 148 149
	}
}

150
static void ctx_free(rrl_ctx_t *ctx)
151 152 153 154
{
	assert(ctx);

	rrl_destroy(ctx->rrl);
155
	free(ctx);
156 157
}

158
int rrl_load(knotd_mod_t *mod)
159 160
{
	// Create RRL context.
161
	rrl_ctx_t *ctx = calloc(1, sizeof(rrl_ctx_t));
162 163 164 165 166
	if (ctx == NULL) {
		return KNOT_ENOMEM;
	}

	// Create table.
167 168 169
	uint32_t rate = knotd_conf_mod(mod, MOD_RATE_LIMIT).single.integer;
	size_t size = knotd_conf_mod(mod, MOD_TBL_SIZE).single.integer;
	ctx->rrl = rrl_create(size, rate);
170
	if (ctx->rrl == NULL) {
171
		ctx_free(ctx);
172 173 174 175
		return KNOT_ENOMEM;
	}

	// Get slip.
176
	ctx->slip = knotd_conf_mod(mod, MOD_SLIP).single.integer;
177 178 179

	// Get whitelist.
	ctx->whitelist = knotd_conf_mod(mod, MOD_WHITELIST);
180 181

	// Set up statistics counters.
182
	int ret = knotd_mod_stats_add(mod, "slipped", 1, NULL);
183
	if (ret != KNOT_EOK) {
184
		ctx_free(ctx);
185 186 187
		return ret;
	}

188
	ret = knotd_mod_stats_add(mod, "dropped", 1, NULL);
189
	if (ret != KNOT_EOK) {
190
		ctx_free(ctx);
191 192 193
		return ret;
	}

194
	knotd_mod_ctx_set(mod, ctx);
195

196
	return knotd_mod_hook(mod, KNOTD_STAGE_END, ratelimit_apply);
197 198
}

199
void rrl_unload(knotd_mod_t *mod)
200
{
201
	rrl_ctx_t *ctx = knotd_mod_ctx(mod);
202

203 204
	knotd_conf_free(&ctx->whitelist);
	ctx_free(ctx);
205
}
206 207 208

KNOTD_MOD_API(rrl, KNOTD_MOD_FLAG_SCOPE_ANY,
              rrl_load, rrl_unload, rrl_conf, rrl_conf_check);