suricata
source-netmap.c
Go to the documentation of this file.
1 /* Copyright (C) 2026 Open Information Security Foundation
2  *
3  * You can copy, redistribute or modify this Program under the terms of
4  * the GNU General Public License version 2 as published by the Free
5  * Software Foundation.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * version 2 along with this program; if not, write to the Free Software
14  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15  * 02110-1301, USA.
16  */
17 
18 #include "../suricata-common.h"
19 #include "../util-unittest.h"
20 
21 /* build a fake rx ring in one block of memory. the ring header and its slots
22  * come first and the packet buffers follow, so NETMAP_BUF(ring, k) lands on
23  * buffer number k. num_slots and nr_buf_size are const in the real struct, so
24  * we write them through a plain pointer as the memory itself is not const. */
25 static struct netmap_ring *NetmapTestRingNew(uint32_t num_slots, uint32_t buf_size)
26 {
27  size_t slots_end =
28  offsetof(struct netmap_ring, slot) + (size_t)num_slots * sizeof(struct netmap_slot);
29  size_t total = slots_end + (size_t)num_slots * buf_size;
30 
31  struct netmap_ring *ring = SCCalloc(1, total);
32  if (ring == NULL)
33  return NULL;
34 
35  *(int64_t *)(uintptr_t)&ring->buf_ofs = (int64_t)slots_end;
36  *(uint32_t *)(uintptr_t)&ring->num_slots = num_slots;
37  *(uint32_t *)(uintptr_t)&ring->nr_buf_size = buf_size;
38  ring->head = ring->cur = 0;
39  /* mark all but one slot as owned by us so nm_ring_space is not zero */
40  ring->tail = num_slots ? num_slots - 1 : 0;
41  return ring;
42 }
43 
44 /* point a slot at buffer buf_idx, set its length and flags, and fill the
45  * buffer with a byte so we can check the copy order. */
46 static void NetmapTestSetSlot(struct netmap_ring *ring, uint32_t slot_idx, uint32_t buf_idx,
47  uint16_t len, uint16_t flags, uint8_t fill)
48 {
49  ring->slot[slot_idx].buf_idx = buf_idx;
50  ring->slot[slot_idx].len = len;
51  ring->slot[slot_idx].flags = flags;
52  memset(NETMAP_BUF(ring, buf_idx), fill, len);
53 }
54 
55 static void NetmapTestNtvInit(NetmapThreadVars *ntv, uint32_t coalesce_size)
56 {
57  memset(ntv, 0, sizeof(*ntv));
58  ntv->coalesce_size = coalesce_size;
59  ntv->coalesce_buf = SCCalloc(1, coalesce_size);
60  if (ntv->coalesce_buf == NULL)
61  return;
62 }
63 
64 /* one slot, no fragments. stays as is and keeps pointing into the ring so
65  * zero-copy still works. */
66 static int NetmapReadSingleSlotTest(void)
67 {
68  struct netmap_ring *ring = NetmapTestRingNew(8, 2048);
69  FAIL_IF_NULL(ring);
70  NetmapThreadVars ntv;
71  NetmapTestNtvInit(&ntv, MAX_PAYLOAD_SIZE);
72  FAIL_IF_NULL(ntv.coalesce_buf);
73 
74  NetmapTestSetSlot(ring, 0, 0, 100, 0, 0xAA);
75 
76  struct nm_pkthdr hdr;
77  bool force_copy = true;
78  FAIL_IF_NOT(NetmapReadOnePacket(ring, &ntv, &hdr, &force_copy));
79  FAIL_IF(force_copy);
80  FAIL_IF_NOT(hdr.len == 100);
81  FAIL_IF_NOT(hdr.buf == (u_char *)NETMAP_BUF(ring, 0));
82  FAIL_IF_NOT(ring->cur == 1);
83 
84  SCFree(ntv.coalesce_buf);
85  SCFree(ring);
86  PASS;
87 }
88 
89 /* two fragments whose buffers sit next to each other. the whole frame must be
90  * rebuilt in the coalesce buffer. */
91 static int NetmapReadTwoSlotAdjacentTest(void)
92 {
93  struct netmap_ring *ring = NetmapTestRingNew(8, 2048);
94  FAIL_IF_NULL(ring);
95  NetmapThreadVars ntv;
96  NetmapTestNtvInit(&ntv, MAX_PAYLOAD_SIZE);
97  FAIL_IF_NULL(ntv.coalesce_buf);
98 
99  NetmapTestSetSlot(ring, 0, 0, 60, NS_MOREFRAG, 0x11);
100  NetmapTestSetSlot(ring, 1, 1, 40, 0, 0x22);
101 
102  struct nm_pkthdr hdr;
103  bool force_copy = false;
104  FAIL_IF_NOT(NetmapReadOnePacket(ring, &ntv, &hdr, &force_copy));
105  FAIL_IF_NOT(force_copy);
106  FAIL_IF_NOT(hdr.len == 100);
107  FAIL_IF_NOT(hdr.buf == ntv.coalesce_buf);
108  for (uint32_t i = 0; i < 60; i++)
109  FAIL_IF_NOT(ntv.coalesce_buf[i] == 0x11);
110  for (uint32_t i = 60; i < 100; i++)
111  FAIL_IF_NOT(ntv.coalesce_buf[i] == 0x22);
112  FAIL_IF_NOT(ring->cur == 2);
113 
114  SCFree(ntv.coalesce_buf);
115  SCFree(ring);
116  PASS;
117 }
118 
119 /* two fragments whose buffers are far apart. this is what the old code got
120  * wrong. both slots must still be copied in order. */
121 static int NetmapReadTwoSlotNonAdjacentTest(void)
122 {
123  struct netmap_ring *ring = NetmapTestRingNew(8, 2048);
124  FAIL_IF_NULL(ring);
125  NetmapThreadVars ntv;
126  NetmapTestNtvInit(&ntv, MAX_PAYLOAD_SIZE);
127  FAIL_IF_NULL(ntv.coalesce_buf);
128 
129  NetmapTestSetSlot(ring, 0, 5, 60, NS_MOREFRAG, 0x33);
130  NetmapTestSetSlot(ring, 1, 2, 40, 0, 0x44);
131 
132  struct nm_pkthdr hdr;
133  bool force_copy = false;
134  FAIL_IF_NOT(NetmapReadOnePacket(ring, &ntv, &hdr, &force_copy));
135  FAIL_IF_NOT(force_copy);
136  FAIL_IF_NOT(hdr.len == 100);
137  FAIL_IF_NOT(hdr.buf == ntv.coalesce_buf);
138  for (uint32_t i = 0; i < 60; i++)
139  FAIL_IF_NOT(ntv.coalesce_buf[i] == 0x33);
140  for (uint32_t i = 60; i < 100; i++)
141  FAIL_IF_NOT(ntv.coalesce_buf[i] == 0x44);
142  FAIL_IF_NOT(ring->cur == 2);
143 
144  SCFree(ntv.coalesce_buf);
145  SCFree(ring);
146  PASS;
147 }
148 
149 /* a two slot packet that wraps around the end of the ring (slot 3 then slot
150  * 0). the walk has to follow the wrap and rebuild the frame. */
151 static int NetmapReadRingWrapTest(void)
152 {
153  struct netmap_ring *ring = NetmapTestRingNew(4, 2048);
154  FAIL_IF_NULL(ring);
155  NetmapThreadVars ntv;
156  NetmapTestNtvInit(&ntv, MAX_PAYLOAD_SIZE);
157  FAIL_IF_NULL(ntv.coalesce_buf);
158 
159  NetmapTestSetSlot(ring, 3, 3, 60, NS_MOREFRAG, 0x55);
160  NetmapTestSetSlot(ring, 0, 0, 40, 0, 0x66);
161  ring->head = ring->cur = 3;
162  /* two slots owned, 3 then 0 */
163  ring->tail = 1;
164 
165  struct nm_pkthdr hdr;
166  bool force_copy = false;
167  FAIL_IF_NOT(NetmapReadOnePacket(ring, &ntv, &hdr, &force_copy));
168  FAIL_IF_NOT(force_copy);
169  FAIL_IF_NOT(hdr.len == 100);
170  FAIL_IF_NOT(hdr.buf == ntv.coalesce_buf);
171  for (uint32_t i = 0; i < 60; i++)
172  FAIL_IF_NOT(ntv.coalesce_buf[i] == 0x55);
173  for (uint32_t i = 60; i < 100; i++)
174  FAIL_IF_NOT(ntv.coalesce_buf[i] == 0x66);
175  FAIL_IF_NOT(ring->cur == 1);
176 
177  SCFree(ntv.coalesce_buf);
178  SCFree(ring);
179  PASS;
180 }
181 
182 /* an empty leading fragment must not break the copy of the next one. */
183 static int NetmapReadZeroLenFragTest(void)
184 {
185  struct netmap_ring *ring = NetmapTestRingNew(8, 2048);
186  FAIL_IF_NULL(ring);
187  NetmapThreadVars ntv;
188  NetmapTestNtvInit(&ntv, MAX_PAYLOAD_SIZE);
189  FAIL_IF_NULL(ntv.coalesce_buf);
190 
191  NetmapTestSetSlot(ring, 0, 0, 0, NS_MOREFRAG, 0x00);
192  NetmapTestSetSlot(ring, 1, 1, 50, 0, 0x77);
193 
194  struct nm_pkthdr hdr;
195  bool force_copy = false;
196  FAIL_IF_NOT(NetmapReadOnePacket(ring, &ntv, &hdr, &force_copy));
197  FAIL_IF_NOT(force_copy);
198  FAIL_IF_NOT(hdr.len == 50);
199  for (uint32_t i = 0; i < 50; i++)
200  FAIL_IF_NOT(ntv.coalesce_buf[i] == 0x77);
201  FAIL_IF_NOT(ring->cur == 2);
202 
203  SCFree(ntv.coalesce_buf);
204  SCFree(ring);
205  PASS;
206 }
207 
208 /* a frame bigger than the coalesce buffer must be dropped, not copied. */
209 static int NetmapReadOverSizeTest(void)
210 {
211  struct netmap_ring *ring = NetmapTestRingNew(8, 2048);
212  FAIL_IF_NULL(ring);
213  NetmapThreadVars ntv;
214  NetmapTestNtvInit(&ntv, 100);
215  FAIL_IF_NULL(ntv.coalesce_buf);
216 
217  NetmapTestSetSlot(ring, 0, 0, 60, NS_MOREFRAG, 0x11);
218  NetmapTestSetSlot(ring, 1, 1, 60, 0, 0x22);
219 
220  struct nm_pkthdr hdr;
221  bool force_copy = false;
222  FAIL_IF(NetmapReadOnePacket(ring, &ntv, &hdr, &force_copy));
223  FAIL_IF_NOT(ntv.drops == 1);
224  FAIL_IF_NOT(ring->cur == 2);
225 
226  SCFree(ntv.coalesce_buf);
227  SCFree(ring);
228  PASS;
229 }
230 
231 /* an oversize frame with fragments after the overflow point must be dropped
232  * whole, so the trailing slots are not read back as a bogus packet. */
233 static int NetmapReadOverSizeTrailingFragTest(void)
234 {
235  struct netmap_ring *ring = NetmapTestRingNew(8, 2048);
236  FAIL_IF_NULL(ring);
237  NetmapThreadVars ntv;
238  NetmapTestNtvInit(&ntv, 100);
239  FAIL_IF_NULL(ntv.coalesce_buf);
240 
241  NetmapTestSetSlot(ring, 0, 0, 60, NS_MOREFRAG, 0x11);
242  NetmapTestSetSlot(ring, 1, 1, 60, NS_MOREFRAG, 0x22);
243  NetmapTestSetSlot(ring, 2, 2, 20, 0, 0x33);
244 
245  struct nm_pkthdr hdr;
246  bool force_copy = false;
247  FAIL_IF(NetmapReadOnePacket(ring, &ntv, &hdr, &force_copy));
248  FAIL_IF_NOT(ntv.drops == 1);
249  FAIL_IF_NOT(ring->cur == 3);
250 
251  SCFree(ntv.coalesce_buf);
252  SCFree(ring);
253  PASS;
254 }
255 
256 /* a chain that never ends must be dropped once every slot we own has been
257  * looked at, and the ring must still move on so the reader cannot spin. */
258 static int NetmapReadTooManyFragsTest(void)
259 {
260  struct netmap_ring *ring = NetmapTestRingNew(8, 64);
261  FAIL_IF_NULL(ring);
262  NetmapThreadVars ntv;
263  NetmapTestNtvInit(&ntv, MAX_PAYLOAD_SIZE);
264  FAIL_IF_NULL(ntv.coalesce_buf);
265 
266  for (uint32_t k = 0; k < 5; k++)
267  NetmapTestSetSlot(ring, k, k, 10, NS_MOREFRAG, (uint8_t)(0x10 + k));
268  ring->head = ring->cur = 0;
269  ring->tail = 5;
270 
271  struct nm_pkthdr hdr;
272  bool force_copy = false;
273  FAIL_IF(NetmapReadOnePacket(ring, &ntv, &hdr, &force_copy));
274  FAIL_IF_NOT(ntv.drops == 1);
275  FAIL_IF_NOT(ring->cur == 5);
276 
277  SCFree(ntv.coalesce_buf);
278  SCFree(ring);
279  PASS;
280 }
281 
282 static void NetmapRegisterTests(void)
283 {
284  UtRegisterTest("NetmapReadSingleSlotTest", NetmapReadSingleSlotTest);
285  UtRegisterTest("NetmapReadTwoSlotAdjacentTest", NetmapReadTwoSlotAdjacentTest);
286  UtRegisterTest("NetmapReadTwoSlotNonAdjacentTest", NetmapReadTwoSlotNonAdjacentTest);
287  UtRegisterTest("NetmapReadRingWrapTest", NetmapReadRingWrapTest);
288  UtRegisterTest("NetmapReadZeroLenFragTest", NetmapReadZeroLenFragTest);
289  UtRegisterTest("NetmapReadOverSizeTest", NetmapReadOverSizeTest);
290  UtRegisterTest("NetmapReadOverSizeTrailingFragTest", NetmapReadOverSizeTrailingFragTest);
291  UtRegisterTest("NetmapReadTooManyFragsTest", NetmapReadTooManyFragsTest);
292 }
len
uint8_t len
Definition: app-layer-dnp3.h:2
FAIL_IF_NULL
#define FAIL_IF_NULL(expr)
Fail a test if expression evaluates to NULL.
Definition: util-unittest.h:89
UtRegisterTest
void UtRegisterTest(const char *name, int(*TestFn)(void))
Register unit test.
Definition: util-unittest.c:101
FAIL_IF_NOT
#define FAIL_IF_NOT(expr)
Fail a test if expression evaluates to false.
Definition: util-unittest.h:82
PASS
#define PASS
Pass the test.
Definition: util-unittest.h:105
FAIL_IF
#define FAIL_IF(expr)
Fail a test if expression evaluates to true.
Definition: util-unittest.h:71
flags
uint8_t flags
Definition: decode-gre.h:0
SCFree
#define SCFree(p)
Definition: util-mem.h:61
MAX_PAYLOAD_SIZE
#define MAX_PAYLOAD_SIZE
Definition: decode.h:714
SCCalloc
#define SCCalloc(nm, sz)
Definition: util-mem.h:53