OpenVPN
test_mbuf.c
Go to the documentation of this file.
1/*
2 * OpenVPN -- An application to securely tunnel IP networks
3 * over a single UDP port, with support for SSL/TLS-based
4 * session authentication and key exchange,
5 * packet encryption, packet authentication, and
6 * packet compression.
7 *
8 * Copyright (C) 2025 OpenVPN Inc. <sales@openvpn.com>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2
12 * as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, see <https://www.gnu.org/licenses/>.
21 */
22
23#ifdef HAVE_CONFIG_H
24#include "config.h"
25#endif
26
27#include "syshead.h"
28
29#include <setjmp.h>
30#include <cmocka.h>
31
32#include "buffer.h"
33#include "multi.h"
34#include "mbuf.h"
35#include "test_common.h"
36
37static void
38test_mbuf_init(void **state)
39{
40 struct mbuf_set *ms = mbuf_init(256);
41 assert_int_equal(ms->capacity, 256);
43 assert_non_null(ms->array);
45
46 ms = mbuf_init(257);
47 assert_int_equal(ms->capacity, 512);
49
50#ifdef UNIT_TEST_ALLOW_BIG_ALLOC /* allocates up to 2GB of memory */
54
55/* NOTE: expect_assert_failure does not seem to work with MSVC */
56#ifndef _MSC_VER
58#endif
59#endif
60}
61
62static void
64{
65 struct mbuf_set *ms = mbuf_init(4);
66 assert_int_equal(ms->capacity, 4);
68 assert_non_null(ms->array);
69
70 /* instances */
71 struct multi_instance mi = { 0 };
72 struct multi_instance mi2 = { 0 };
73 /* buffers */
74 struct buffer buf = alloc_buf(16);
75 struct mbuf_buffer *mbuf_buf = mbuf_alloc_buf(&buf);
76 assert_int_equal(mbuf_buf->refcount, 1);
77 struct mbuf_buffer *mbuf_buf2 = mbuf_alloc_buf(&buf);
78 assert_int_equal(mbuf_buf2->refcount, 1);
79 free_buf(&buf);
80 /* items */
81 struct mbuf_item mb_item = { .buffer = mbuf_buf, .instance = &mi };
82 struct mbuf_item mb_item2 = { .buffer = mbuf_buf2, .instance = &mi2 };
83
84 mbuf_add_item(ms, &mb_item);
85 assert_int_equal(mbuf_buf->refcount, 2);
86 assert_int_equal(mbuf_buf2->refcount, 1);
87 assert_int_equal(mbuf_len(ms), 1);
88 assert_int_equal(mbuf_maximum_queued(ms), 1);
89 assert_int_equal(ms->head, 0);
90 assert_ptr_equal(mbuf_peek(ms), &mi);
91
92 mbuf_add_item(ms, &mb_item2);
93 assert_int_equal(mbuf_buf->refcount, 2);
94 assert_int_equal(mbuf_buf2->refcount, 2);
95 assert_int_equal(mbuf_len(ms), 2);
96 assert_int_equal(mbuf_maximum_queued(ms), 2);
97 assert_int_equal(ms->head, 0);
98 assert_ptr_equal(mbuf_peek(ms), &mi);
99
100 mbuf_add_item(ms, &mb_item2);
101 assert_int_equal(mbuf_buf->refcount, 2);
102 assert_int_equal(mbuf_buf2->refcount, 3);
103 assert_int_equal(mbuf_len(ms), 3);
104 assert_int_equal(mbuf_maximum_queued(ms), 3);
105 assert_int_equal(ms->head, 0);
106 assert_ptr_equal(mbuf_peek(ms), &mi);
107
108 mbuf_add_item(ms, &mb_item2);
109 mbuf_add_item(ms, &mb_item2); /* overflow, first item gets removed */
110 assert_int_equal(mbuf_buf->refcount, 1);
111 assert_int_equal(mbuf_buf2->refcount, 5);
112 assert_int_equal(mbuf_len(ms), 4);
113 assert_int_equal(mbuf_maximum_queued(ms), 4);
114 assert_int_equal(ms->head, 1);
115 assert_ptr_equal(mbuf_peek(ms), &mi2);
116
117 mbuf_add_item(ms, &mb_item);
118 assert_int_equal(mbuf_buf->refcount, 2);
119 assert_int_equal(mbuf_buf2->refcount, 4);
120 assert_int_equal(mbuf_len(ms), 4);
121 assert_int_equal(mbuf_maximum_queued(ms), 4);
122 assert_int_equal(ms->head, 2);
123 assert_ptr_equal(mbuf_peek(ms), &mi2);
124
125 struct mbuf_item out_item;
126 assert_true(mbuf_extract_item(ms, &out_item));
127 assert_ptr_equal(out_item.instance, mb_item2.instance);
128 assert_int_equal(mbuf_buf->refcount, 2);
129 assert_int_equal(mbuf_buf2->refcount, 4);
130 assert_int_equal(mbuf_len(ms), 3);
131 assert_int_equal(mbuf_maximum_queued(ms), 4);
132 assert_int_equal(ms->head, 3);
133 assert_ptr_equal(mbuf_peek(ms), &mi2);
134 mbuf_free_buf(out_item.buffer);
135
137 assert_int_equal(mbuf_buf->refcount, 2);
138 assert_int_equal(mbuf_buf2->refcount, 1);
139 assert_int_equal(mbuf_len(ms), 1);
140 assert_int_equal(mbuf_maximum_queued(ms), 4);
141 assert_int_equal(ms->head, 1);
142 assert_ptr_equal(mbuf_peek(ms), &mi);
143
144 mbuf_free(ms);
145 assert_int_equal(mbuf_buf->refcount, 1);
146 mbuf_free_buf(mbuf_buf);
147 assert_int_equal(mbuf_buf2->refcount, 1);
148 mbuf_free_buf(mbuf_buf2);
149}
150
151/* a queue holding nothing but dereferenced items is an empty queue */
152static void
154{
155 struct mbuf_set *ms = mbuf_init(4);
156 struct multi_instance mi = { 0 };
157 struct buffer buf = alloc_buf(16);
158 struct mbuf_buffer *mbuf_buf = mbuf_alloc_buf(&buf);
159 struct mbuf_item item = { .buffer = mbuf_buf, .instance = &mi };
160 free_buf(&buf);
161
162 for (int i = 0; i < 4; ++i)
163 {
164 mbuf_add_item(ms, &item);
165 }
166 assert_int_equal(mbuf_len(ms), 4);
167 assert_int_equal(mbuf_buf->refcount, 5);
168
170
171 assert_int_equal(mbuf_len(ms), 0);
172 assert_false(mbuf_defined(ms));
173 assert_null(mbuf_peek(ms));
174 assert_int_equal(mbuf_buf->refcount, 1);
175
176 /* the queue is empty, so this must be queued and not dropped */
177 mbuf_add_item(ms, &item);
178 assert_int_equal(mbuf_len(ms), 1);
179 assert_ptr_equal(mbuf_peek(ms), &mi);
180
181 mbuf_free(ms);
182 mbuf_free_buf(mbuf_buf);
183}
184
185/* extracting the last live item must not leave a trailing hole behind */
186static void
188{
189 struct mbuf_set *ms = mbuf_init(4);
190 struct multi_instance mi = { 0 };
191 struct multi_instance mi2 = { 0 };
192 struct buffer buf = alloc_buf(16);
193 struct mbuf_buffer *mbuf_buf = mbuf_alloc_buf(&buf);
194 struct mbuf_item item = { .buffer = mbuf_buf, .instance = &mi };
195 struct mbuf_item item2 = { .buffer = mbuf_buf, .instance = &mi2 };
196 free_buf(&buf);
197
198 mbuf_add_item(ms, &item);
199 mbuf_add_item(ms, &item2);
201 assert_int_equal(mbuf_len(ms), 2); /* head is still live, nothing to reclaim */
202
203 struct mbuf_item out;
204 assert_true(mbuf_extract_item(ms, &out));
205 assert_ptr_equal(out.instance, &mi);
207
208 assert_int_equal(mbuf_len(ms), 0);
209 assert_false(mbuf_defined(ms));
210 assert_null(mbuf_peek(ms));
211
212 mbuf_free(ms);
213 mbuf_free_buf(mbuf_buf);
214}
215
216/* reclaiming stops at the first live item, it does not walk the whole ring */
217static void
219{
220 struct mbuf_set *ms = mbuf_init(4);
221 struct multi_instance mi = { 0 };
222 struct multi_instance mi2 = { 0 };
223 struct buffer buf = alloc_buf(16);
224 struct mbuf_buffer *mbuf_buf = mbuf_alloc_buf(&buf);
225 struct mbuf_item item = { .buffer = mbuf_buf, .instance = &mi };
226 struct mbuf_item item2 = { .buffer = mbuf_buf, .instance = &mi2 };
227 free_buf(&buf);
228
229 /* [mi][mi2][mi] -> dereferencing mi2 leaves [mi][hole][mi] */
230 mbuf_add_item(ms, &item);
231 mbuf_add_item(ms, &item2);
232 mbuf_add_item(ms, &item);
234 assert_int_equal(mbuf_len(ms), 3); /* head is live, nothing to reclaim yet */
235
236 /* extracting the head leaves the hole in front: it must be reclaimed, and
237 * the walk must stop at the live item behind it */
238 struct mbuf_item out;
239 assert_true(mbuf_extract_item(ms, &out));
240 assert_ptr_equal(out.instance, &mi);
242
243 assert_int_equal(mbuf_len(ms), 1);
244 assert_true(mbuf_defined(ms));
245 assert_ptr_equal(mbuf_peek(ms), &mi);
246
247 mbuf_free(ms);
248 mbuf_free_buf(mbuf_buf);
249}
250
251/* Holes behind a live item are not reclaimed, so a full ring can still hold
252 * them. mbuf_add_item() must cope: it evicts the live head, which drags the
253 * holes with it, rather than finding nothing to drop. */
254static void
256{
257 struct mbuf_set *ms = mbuf_init(4);
258 struct multi_instance mi = { 0 };
259 struct multi_instance mi2 = { 0 };
260 struct multi_instance mi3 = { 0 };
261 struct buffer buf = alloc_buf(16);
262 struct mbuf_buffer *mbuf_buf = mbuf_alloc_buf(&buf);
263 struct mbuf_item item = { .buffer = mbuf_buf, .instance = &mi };
264 struct mbuf_item item2 = { .buffer = mbuf_buf, .instance = &mi2 };
265 struct mbuf_item item3 = { .buffer = mbuf_buf, .instance = &mi3 };
266 free_buf(&buf);
267
268 /* [mi][mi2][mi2][mi2] -> dereferencing mi2 leaves [mi][hole][hole][hole],
269 * which mbuf_reclaim_head() cannot touch: the head is still live */
270 mbuf_add_item(ms, &item);
271 mbuf_add_item(ms, &item2);
272 mbuf_add_item(ms, &item2);
273 mbuf_add_item(ms, &item2);
275 assert_int_equal(mbuf_len(ms), 4);
276 assert_int_equal(mbuf_len(ms), ms->capacity); /* still counts as full */
277
278 mbuf_add_item(ms, &item3);
279 assert_int_equal(mbuf_len(ms), 1);
280 assert_ptr_equal(mbuf_peek(ms), &mi3);
281
282 mbuf_free(ms);
283 mbuf_free_buf(mbuf_buf);
284}
285
286int
287main(void)
288{
289 const struct CMUnitTest tests[] = {
290 cmocka_unit_test(test_mbuf_init),
291 cmocka_unit_test(test_mbuf_add_remove),
292 cmocka_unit_test(test_mbuf_dereference_reclaims_queue),
293 cmocka_unit_test(test_mbuf_extract_reclaims_tail),
296 };
297
298 return cmocka_run_group_tests_name("mbuf", tests, NULL, NULL);
299}
void free_buf(struct buffer *buf)
Free the memory allocated for a buffer.
Definition buffer.c:169
struct buffer alloc_buf(size_t size)
Allocate a buffer of the given size.
Definition buffer.c:60
Buffer management functions and garbage collection.
void mbuf_add_item(struct mbuf_set *ms, const struct mbuf_item *item)
Definition mbuf.c:104
struct mbuf_buffer * mbuf_alloc_buf(const struct buffer *buf)
Definition mbuf.c:65
void mbuf_free_buf(struct mbuf_buffer *mb)
Definition mbuf.c:76
void mbuf_dereference_instance(struct mbuf_set *ms, struct multi_instance *mi)
Definition mbuf.c:168
bool mbuf_extract_item(struct mbuf_set *ms, struct mbuf_item *item)
Definition mbuf.c:126
void mbuf_free(struct mbuf_set *ms)
Definition mbuf.c:50
struct mbuf_set * mbuf_init(unsigned int size)
Definition mbuf.c:38
#define MBUF_SIZE_MAX
Definition mbuf.h:40
static struct multi_instance * mbuf_peek(struct mbuf_set *ms)
Definition mbuf.h:101
static bool mbuf_defined(const struct mbuf_set *ms)
Definition mbuf.h:81
static unsigned int mbuf_len(const struct mbuf_set *ms)
Definition mbuf.h:87
static int mbuf_maximum_queued(const struct mbuf_set *ms)
Definition mbuf.h:93
Header file for server-mode related structures and functions.
Wrapper structure for dynamically allocated memory.
Definition buffer.h:71
struct buffer buf
Definition mbuf.h:44
int refcount
Definition mbuf.h:45
struct mbuf_buffer * buffer
Definition mbuf.h:53
struct multi_instance * instance
Definition mbuf.h:54
unsigned int head
Definition mbuf.h:59
unsigned int len
Definition mbuf.h:60
unsigned int capacity
Definition mbuf.h:61
Server-mode state structure for one single VPN tunnel.
Definition multi.h:102
static void test_mbuf_extract_reclaims_tail(void **state)
Definition test_mbuf.c:187
static void test_mbuf_add_on_full_queue_with_holes(void **state)
Definition test_mbuf.c:255
static void test_mbuf_add_remove(void **state)
Definition test_mbuf.c:63
int main(void)
Definition test_mbuf.c:287
static void test_mbuf_dereference_reclaims_queue(void **state)
Definition test_mbuf.c:153
static void test_mbuf_init(void **state)
Definition test_mbuf.c:38
static void test_mbuf_extract_reclaims_up_to_live(void **state)
Definition test_mbuf.c:218