1 /*  Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2 
3     This program is free software: you can redistribute it and/or modify
4     it under the terms of the GNU General Public License as published by
5     the Free Software Foundation, either version 3 of the License, or
6     (at your option) any later version.
7 
8     This program is distributed in the hope that it will be useful,
9     but WITHOUT ANY WARRANTY; without even the implied warranty of
10     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11     GNU General Public License for more details.
12 
13     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 #include <tap/basic.h>
18 
19 #include "libdnssec/crypto.h"
20 #include "libdnssec/random.h"
21 #include "libknot/libknot.h"
22 #include "contrib/sockaddr.h"
23 #include "knot/modules/rrl/functions.c"
24 #include "stdio.h"
25 
26 /* Enable time-dependent tests. */
27 //#define ENABLE_TIMED_TESTS
28 #define RRL_SIZE 196613
29 #define RRL_THREADS 8
30 #define RRL_INSERTS (RRL_SIZE/(5*RRL_THREADS)) /* lf = 1/5 */
31 
32 /* Disabled as default as it depends on random input.
33  * Table may be consistent even if some collision occur (and they may occur).
34  * Note: Disabled due to reported problems when running on VMs due to time
35  * flow inconsistencies. Should work alright on a host machine.
36  */
37 #ifdef ENABLE_TIMED_TESTS
38 struct bucketmap {
39 	unsigned i;
40 	uint64_t x;
41 };
42 
43 /*! \brief Unit runnable. */
44 struct runnable_data {
45 	int passed;
46 	rrl_table_t *rrl;
47 	struct sockaddr_storage *addr;
48 	rrl_req_t *rq;
49 	knot_dname_t *zone;
50 };
51 
rrl_runnable(void * arg)52 static void* rrl_runnable(void *arg)
53 {
54 	struct runnable_data *d = (struct runnable_data *)arg;
55 	struct sockaddr_storage addr;
56 	memcpy(&addr, d->addr, sizeof(struct sockaddr_storage));
57 	int lock = -1;
58 	uint32_t now = time(NULL);
59 	struct bucketmap *m = malloc(RRL_INSERTS * sizeof(struct bucketmap));
60 	for (unsigned i = 0; i < RRL_INSERTS; ++i) {
61 		m[i].i = dnssec_random_uint32_t();
62 		((struct sockaddr_in *) &addr)->sin_addr.s_addr = m[i].i;
63 		rrl_item_t *b = rrl_hash(d->rrl, &addr, d->rq, d->zone, now, &lock);
64 		rrl_unlock(d->rrl, lock);
65 		m[i].x = b->netblk;
66 	}
67 	for (unsigned i = 0; i < RRL_INSERTS; ++i) {
68 		((struct sockaddr_in *) &addr)->sin_addr.s_addr = m[i].i;
69 		rrl_item_t *b = rrl_hash(d->rrl, &addr, d->rq, d->zone, now, &lock);
70 		rrl_unlock(d->rrl, lock);
71 		if (b->netblk != m[i].x) {
72 			d->passed = 0;
73 		}
74 	}
75 	free(m);
76 	return NULL;
77 }
78 
rrl_hopscotch(struct runnable_data * rd)79 static void rrl_hopscotch(struct runnable_data* rd)
80 {
81 	rd->passed = 1;
82 	pthread_t thr[RRL_THREADS];
83 	for (unsigned i = 0; i < RRL_THREADS; ++i) {
84 		pthread_create(thr + i, NULL, &rrl_runnable, rd);
85 	}
86 	for (unsigned i = 0; i < RRL_THREADS; ++i) {
87 		pthread_join(thr[i], NULL);
88 	}
89 }
90 #endif
91 
main(int argc,char * argv[])92 int main(int argc, char *argv[])
93 {
94 	plan_lazy();
95 
96 	dnssec_crypto_init();
97 
98 	/* Prepare query. */
99 	knot_pkt_t *query = knot_pkt_new(NULL, 512, NULL);
100 	if (query == NULL) {
101 		return KNOT_ERROR; /* Fatal */
102 	}
103 
104 	knot_dname_t *qname = knot_dname_from_str_alloc("beef.");
105 	int ret = knot_pkt_put_question(query, qname, KNOT_CLASS_IN, KNOT_RRTYPE_A);
106 	knot_dname_free(qname, NULL);
107 	if (ret != KNOT_EOK) {
108 		knot_pkt_free(query);
109 		return KNOT_ERROR; /* Fatal */
110 	}
111 
112 	/* Prepare response */
113 	uint8_t rbuf[65535];
114 	size_t rlen = sizeof(rbuf);
115 	memcpy(rbuf, query->wire, query->size);
116 	knot_wire_flags_set_qr(rbuf);
117 
118 	rrl_req_t rq;
119 	rq.wire = rbuf;
120 	rq.len = rlen;
121 	rq.query = query;
122 	rq.flags = 0;
123 
124 	/* 1. create rrl table */
125 	const uint32_t rate = 10;
126 	rrl_table_t *rrl = rrl_create(RRL_SIZE, rate);
127 	ok(rrl != NULL, "rrl: create");
128 
129 	/* 2. N unlimited requests. */
130 	knot_dname_t *zone = knot_dname_from_str_alloc("rrl.");
131 
132 	struct sockaddr_storage addr;
133 	struct sockaddr_storage addr6;
134 	sockaddr_set(&addr, AF_INET, "1.2.3.4", 0);
135 	sockaddr_set(&addr6, AF_INET6, "1122:3344:5566:7788::aabb", 0);
136 	ret = 0;
137 	for (unsigned i = 0; i < rate * RRL_CAPACITY; ++i) {
138 		if (rrl_query(rrl, &addr, &rq, zone, NULL) != KNOT_EOK ||
139 		    rrl_query(rrl, &addr6, &rq, zone, NULL) != KNOT_EOK) {
140 			ret = KNOT_ELIMIT;
141 			break;
142 		}
143 	}
144 	is_int(0, ret, "rrl: unlimited IPv4/v6 requests");
145 
146 	/* 3. Endian-independent hash input buffer. */
147 	uint8_t buf[RRL_CLSBLK_MAXLEN];
148 	// CLS_LARGE + remote + dname wire.
149 	uint8_t expectedv4[] = "\x10\x01\x02\x03\x00\x00\x00\x00\x00\x04""beef";
150 	rrl_classify(buf, sizeof(buf), &addr, &rq, qname);
151 	is_int(0, memcmp(buf, expectedv4, sizeof(expectedv4)), "rrl: IPv4 hash input buffer");
152 	uint8_t expectedv6[] = "\x10\x11\x22\x33\x44\x55\x66\x77\x00\x04""beef";
153 	rrl_classify(buf, sizeof(buf), &addr6, &rq, qname);
154 	is_int(0, memcmp(buf, expectedv6, sizeof(expectedv6)), "rrl: IPv6 hash input buffer");
155 
156 #ifdef ENABLE_TIMED_TESTS
157 	/* 5. limited request */
158 	ret = rrl_query(rrl, &addr, &rq, zone, NULL);
159 	is_int(KNOT_ELIMIT, ret, "rrl: throttled IPv4 request");
160 
161 	/* 6. limited IPv6 request */
162 	ret = rrl_query(rrl, &addr6, &rq, zone, NULL);
163 	is_int(KNOT_ELIMIT, ret, "rrl: throttled IPv6 request");
164 
165 	/* 8. hopscotch test */
166 	struct runnable_data rd = {
167 		1, rrl, &addr, &rq, zone
168 	};
169 	rrl_hopscotch(&rd);
170 	ok(rd.passed, "rrl: hashtable is ~ consistent");
171 #endif
172 
173 	knot_dname_free(zone, NULL);
174 	knot_pkt_free(query);
175 	rrl_destroy(rrl);
176 	dnssec_crypto_cleanup();
177 	return 0;
178 }
179