1 /*
2 * NPF connection tests.
3 *
4 * Public Domain.
5 */
6
7 #ifdef _KERNEL
8 #include <sys/types.h>
9 #include <sys/kmem.h>
10 #endif
11
12 #include "npf.h"
13 #include "npf_impl.h"
14 #include "npf_conn.h"
15 #include "npf_test.h"
16
17 static bool lverbose = false;
18
19 static unsigned
count_conns(npf_conndb_t * cd)20 count_conns(npf_conndb_t *cd)
21 {
22 npf_conn_t *head = npf_conndb_getlist(cd), *conn = head;
23 unsigned n = 0;
24
25 while (conn) {
26 n++;
27 conn = npf_conndb_getnext(cd, conn);
28 if (conn == head) {
29 break;
30 }
31 }
32 return n;
33 }
34
35 static struct mbuf *
get_packet(unsigned i)36 get_packet(unsigned i)
37 {
38 struct mbuf *m;
39 struct ip *ip;
40
41 m = mbuf_get_pkt(AF_INET, IPPROTO_UDP,
42 "10.0.0.1", "172.16.0.1", 9000, 9000);
43 (void)mbuf_return_hdrs(m, false, &ip);
44 ip->ip_src.s_addr += i;
45 return m;
46 }
47
48 static bool
enqueue_connection(unsigned i,bool expire)49 enqueue_connection(unsigned i, bool expire)
50 {
51 struct mbuf *m = get_packet(i);
52 npf_cache_t *npc = get_cached_pkt(m, NULL);
53 npf_conn_t *con;
54
55 con = npf_conn_establish(npc, PFIL_IN, true);
56 CHECK_TRUE(con != NULL);
57 if (expire) {
58 npf_conn_expire(con);
59 }
60 npf_conn_release(con);
61 put_cached_pkt(npc);
62 return true;
63 }
64
65 static bool
run_conn_gc(unsigned active,unsigned expired,unsigned expected)66 run_conn_gc(unsigned active, unsigned expired, unsigned expected)
67 {
68 npf_t *npf = npf_getkernctx();
69 npf_conndb_t *cd = npf_conndb_create();
70 unsigned total, n = 0;
71
72 npf->conn_db = cd;
73
74 /*
75 * Insert the given number of active and expired connections..
76 */
77 total = active + expired;
78
79 while (active || expired) {
80 if (active) {
81 enqueue_connection(n++, false);
82 active--;
83 }
84 if (expired) {
85 enqueue_connection(n++, true);
86 expired--;
87 }
88 }
89
90 /* Verify the count. */
91 n = count_conns(cd);
92 CHECK_TRUE(n == total);
93
94 /*
95 * Run G/C. Check the remaining.
96 */
97 npf_conndb_gc(npf, cd, false, false);
98 n = count_conns(cd);
99 if (lverbose) {
100 printf("in conndb -- %u (expected %u)\n", n, expected);
101 }
102 CHECK_TRUE(n == expected);
103
104 /* Flush and destroy. */
105 npf_conndb_gc(npf, cd, true, false);
106 npf_conndb_destroy(cd);
107 npf->conn_db = NULL;
108 return true;
109 }
110
111 static bool
run_gc_tests(void)112 run_gc_tests(void)
113 {
114 bool ok;
115 int val;
116
117 /* Check the default value. */
118 npfk_param_get(npf_getkernctx(), "gc.step", &val);
119 CHECK_TRUE(val == 256);
120
121 /* Empty => GC => 0 in conndb. */
122 ok = run_conn_gc(0, 0, 0);
123 CHECK_TRUE(ok);
124
125 /* 1 active => GC => 1 in conndb. */
126 ok = run_conn_gc(1, 0, 1);
127 CHECK_TRUE(ok);
128
129 /* 1 expired => GC => 0 in conndb. */
130 ok = run_conn_gc(0, 1, 0);
131 CHECK_TRUE(ok);
132
133 /* 1 active and 1 expired => GC => 1 in conndb. */
134 ok = run_conn_gc(1, 1, 1);
135 CHECK_TRUE(ok);
136
137 /* 2 expired => GC => 0 in conndb. */
138 ok = run_conn_gc(0, 2, 0);
139 CHECK_TRUE(ok);
140
141 /* 128 expired => GC => 0 in conndb. */
142 ok = run_conn_gc(0, 128, 0);
143 CHECK_TRUE(ok);
144
145 /* 512 expired => GC => 256 in conndb. */
146 ok = run_conn_gc(0, 512, 256);
147 CHECK_TRUE(ok);
148
149 /* 512 expired => GC => 127 in conndb. */
150 npfk_param_set(npf_getkernctx(), "gc.step", 128);
151 ok = run_conn_gc(0, 512, 384);
152 CHECK_TRUE(ok);
153
154 return true;
155 }
156
157 static bool
run_conndb_tests(npf_t * npf)158 run_conndb_tests(npf_t *npf)
159 {
160 npf_conndb_t *orig_cd = npf->conn_db;
161 bool ok;
162
163 npf_config_enter(npf);
164 npf_conn_tracking(npf, true);
165 npf_config_exit(npf);
166
167 ok = run_gc_tests();
168
169 /* We *MUST* restore the valid conndb. */
170 npf->conn_db = orig_cd;
171 return ok;
172 }
173
174
175 static void
worker_test_task(npf_t * npf)176 worker_test_task(npf_t *npf)
177 {
178 bool *done = atomic_load_acquire(&npf->arg);
179 atomic_store_release(done, true);
180 }
181
182 static bool
run_worker_tests(npf_t * npf)183 run_worker_tests(npf_t *npf)
184 {
185 unsigned n = 100;
186 int error;
187
188 /* Spawn a worker thread. */
189 error = npf_worker_sysinit(1);
190 assert(error == 0);
191
192 /*
193 * Enlist/discharge an instance, trying to trigger a race.
194 */
195 while (n--) {
196 bool task_done = false;
197 unsigned retry = 100;
198 npf_t *test_npf;
199
200 /*
201 * Initialize a dummy NPF instance and add a test task.
202 * We will (ab)use npf_t::arg here.
203 *
204 * XXX/TODO: We should use:
205 *
206 * npfk_create(NPF_NO_GC, &npftest_mbufops,
207 * &npftest_ifops, &task_done);
208 *
209 * However, it resets the interface state and breaks
210 * other tests; to be refactor.
211 */
212 test_npf = kmem_zalloc(sizeof(npf_t), KM_SLEEP);
213 atomic_store_release(&test_npf->arg, &task_done);
214 test_npf->ebr = npf_ebr_create();
215
216 error = npf_worker_addfunc(test_npf, worker_test_task);
217 assert(error == 0);
218
219 /* Enlist the NPF instance. */
220 npf_worker_enlist(test_npf);
221
222 /* Wait for the task to be done. */
223 while (!atomic_load_acquire(&task_done) && retry--) {
224 npf_worker_signal(test_npf);
225 kpause("gctest", false, MAX(1, mstohz(1)), NULL);
226 }
227
228 CHECK_TRUE(atomic_load_acquire(&task_done));
229 npf_worker_discharge(test_npf);
230
231 /* Clear the parameter and signal again. */
232 atomic_store_release(&test_npf->arg, NULL);
233 npf_worker_signal(test_npf);
234
235 npf_ebr_destroy(test_npf->ebr);
236 kmem_free(test_npf, sizeof(npf_t)); // npfk_destroy()
237 }
238
239 /*
240 * Destroy the worker.
241 *
242 * Attempts to enlist, discharge or signal should have no effect.
243 */
244
245 npf_worker_sysfini();
246 npf_worker_enlist(npf);
247 npf_worker_signal(npf);
248 npf_worker_discharge(npf);
249 return true;
250 }
251
252 bool
npf_gc_test(bool verbose)253 npf_gc_test(bool verbose)
254 {
255 npf_t *npf = npf_getkernctx();
256 bool ok;
257
258 lverbose = verbose;
259
260 ok = run_conndb_tests(npf);
261 CHECK_TRUE(ok);
262
263 ok = run_worker_tests(npf);
264 CHECK_TRUE(ok);
265
266 return ok;
267 }
268