1 /*
2 ** Copyright (C) 2014-2021 Cisco and/or its affiliates. All rights reserved.
3 ** Copyright (C) 2010-2013 Sourcefire, Inc.
4 ** Author: Michael R. Altizer <mialtize@cisco.com>
5 **
6 ** This program is free software; you can redistribute it and/or modify
7 ** it under the terms of the GNU General Public License Version 2 as
8 ** published by the Free Software Foundation. You may not use, modify or
9 ** distribute this program under any other version of the GNU General
10 ** Public License.
11 **
12 ** This program is distributed in the hope that it will be useful,
13 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ** GNU General Public License for more details.
16 **
17 ** You should have received a copy of the GNU General Public License
18 ** along with this program; if not, write to the Free Software
19 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include <pcap.h>
27 #include <stdbool.h>
28 #include <stdlib.h>
29 #include <string.h>
30
31 #include "daq_module_api.h"
32
33 #define DAQ_DUMP_VERSION 5
34
35 #define DEFAULT_TX_DUMP_FILE "inline-out.pcap"
36 #define DEFAULT_RX_DUMP_FILE "inline-in.pcap"
37
38 #define SET_ERROR(modinst, ...) daq_base_api.set_errbuf(modinst, __VA_ARGS__)
39
40 #define CHECK_SUBAPI(ctxt, fname) \
41 (ctxt->subapi.fname.func != NULL)
42
43 #define CALL_SUBAPI_NOARGS(ctxt, fname) \
44 ctxt->subapi.fname.func(ctxt->subapi.fname.context)
45
46 #define CALL_SUBAPI(ctxt, fname, ...) \
47 ctxt->subapi.fname.func(ctxt->subapi.fname.context, __VA_ARGS__)
48
49 typedef struct
50 {
51 DAQ_ModuleInstance_h modinst;
52 DAQ_InstanceAPI_t subapi;
53
54 pcap_dumper_t *tx_dumper;
55 char *tx_filename;
56
57 pcap_dumper_t *rx_dumper;
58 char *rx_filename;
59
60 DAQ_Stats_t stats;
61 } DumpContext;
62
63 static DAQ_VariableDesc_t dump_variable_descriptions[] = {
64 { "file", "PCAP filename to output transmitted packets to (default: " DEFAULT_TX_DUMP_FILE ")", DAQ_VAR_DESC_REQUIRES_ARGUMENT },
65 { "output", "Set to none to prevent output from being written to file (deprecated)", DAQ_VAR_DESC_REQUIRES_ARGUMENT },
66 { "dump-rx", "Also dump received packets to their own PCAP file (default: " DEFAULT_RX_DUMP_FILE ")", 0 }
67 };
68
69 static DAQ_BaseAPI_t daq_base_api;
70
71 //-------------------------------------------------------------------------
72
dump_daq_module_load(const DAQ_BaseAPI_t * base_api)73 static int dump_daq_module_load(const DAQ_BaseAPI_t *base_api)
74 {
75 if (base_api->api_version != DAQ_BASE_API_VERSION || base_api->api_size != sizeof(DAQ_BaseAPI_t))
76 return DAQ_ERROR;
77
78 daq_base_api = *base_api;
79
80 return DAQ_SUCCESS;
81 }
82
dump_daq_module_unload(void)83 static int dump_daq_module_unload(void)
84 {
85 memset(&daq_base_api, 0, sizeof(daq_base_api));
86 return DAQ_SUCCESS;
87 }
88
dump_daq_get_variable_descs(const DAQ_VariableDesc_t ** var_desc_table)89 static int dump_daq_get_variable_descs(const DAQ_VariableDesc_t **var_desc_table)
90 {
91 *var_desc_table = dump_variable_descriptions;
92
93 return sizeof(dump_variable_descriptions) / sizeof(DAQ_VariableDesc_t);
94 }
95
dump_daq_instantiate(const DAQ_ModuleConfig_h modcfg,DAQ_ModuleInstance_h modinst,void ** ctxt_ptr)96 static int dump_daq_instantiate(const DAQ_ModuleConfig_h modcfg, DAQ_ModuleInstance_h modinst, void **ctxt_ptr)
97 {
98 // Simple multi-instance sanity check
99 unsigned total_instances = daq_base_api.config_get_total_instances(modcfg);
100 unsigned instance_id = daq_base_api.config_get_instance_id(modcfg);
101 if (total_instances > 1 && instance_id == 0)
102 {
103 SET_ERROR(modinst, "%s: Instance ID required for multi-instance (%u instances expected)", __func__, total_instances);
104 return DAQ_ERROR_INVAL;
105 }
106
107 DumpContext *dc = calloc(1, sizeof(DumpContext));
108 if (!dc)
109 {
110 SET_ERROR(modinst, "%s: Couldn't allocate memory for the DAQ context", __func__);
111 return DAQ_ERROR_NOMEM;
112 }
113 dc->modinst = modinst;
114
115 if (daq_base_api.resolve_subapi(modinst, &dc->subapi) != DAQ_SUCCESS)
116 {
117 SET_ERROR(modinst, "%s: Couldn't resolve subapi. No submodule configured?", __func__);
118 free(dc);
119 return DAQ_ERROR_INVAL;
120 }
121
122 const char *tx_filename = DEFAULT_TX_DUMP_FILE;
123 const char *rx_filename = NULL;
124 const char *varKey, *varValue;
125 daq_base_api.config_first_variable(modcfg, &varKey, &varValue);
126 while (varKey)
127 {
128 if (!strcmp(varKey, "file"))
129 tx_filename = varValue;
130 else if (!strcmp(varKey, "dump-rx"))
131 rx_filename = varValue ? varValue : DEFAULT_RX_DUMP_FILE;
132 else if (!strcmp(varKey, "output"))
133 {
134 if (!strcmp(varValue, "none"))
135 tx_filename = NULL;
136 else
137 {
138 SET_ERROR(modinst, "%s: Invalid output type (%s)", __func__, varValue);
139 free(dc);
140 return DAQ_ERROR_INVAL;
141 }
142 }
143 daq_base_api.config_next_variable(modcfg, &varKey, &varValue);
144 }
145
146 // Mangle the output filenames with a prefix in the multi-instance scenario
147 char prefix[32];
148 if (instance_id > 0)
149 {
150 // For now, only support mangling base filenames (no directory path allowed)
151 if (tx_filename && strchr(tx_filename, '/'))
152 {
153 SET_ERROR(modinst, "%s: Invalid TX PCAP filename for multi-instance: %s", __func__, tx_filename);
154 free(dc);
155 return DAQ_ERROR_INVAL;
156 }
157
158 if (rx_filename && strchr(rx_filename, '/'))
159 {
160 SET_ERROR(modinst, "%s: Invalid RX PCAP filename for multi-instance: %s", __func__, rx_filename);
161 free(dc);
162 return DAQ_ERROR_INVAL;
163 }
164
165 snprintf(prefix, sizeof(prefix), "%u_", instance_id);
166 }
167 else
168 prefix[0] = '\0';
169
170 if (tx_filename)
171 {
172 size_t len = strlen(tx_filename) + strlen(prefix) + 1;
173 dc->tx_filename = malloc(len);
174 if (!dc->tx_filename)
175 {
176 SET_ERROR(modinst, "%s: Couldn't allocate memory for the TX PCAP filename", __func__);
177 free(dc);
178 return DAQ_ERROR_NOMEM;
179 }
180 snprintf(dc->tx_filename, len, "%s%s", prefix, tx_filename);
181 }
182
183 if (rx_filename)
184 {
185 size_t len = strlen(rx_filename) + strlen(prefix) + 1;
186 dc->rx_filename = malloc(len);
187 if (!dc->rx_filename)
188 {
189 SET_ERROR(modinst, "%s: Couldn't allocate memory for the RX PCAP filename", __func__);
190 free(dc->tx_filename);
191 free(dc);
192 return DAQ_ERROR_NOMEM;
193 }
194 snprintf(dc->rx_filename, len, "%s%s", prefix, rx_filename);
195 }
196
197 *ctxt_ptr = dc;
198
199 return DAQ_SUCCESS;
200 }
201
dump_daq_destroy(void * handle)202 static void dump_daq_destroy(void *handle)
203 {
204 DumpContext *dc = (DumpContext *) handle;
205
206 if (dc->tx_dumper)
207 pcap_dump_close(dc->tx_dumper);
208 free(dc->tx_filename);
209 if (dc->rx_dumper)
210 pcap_dump_close(dc->rx_dumper);
211 free(dc->rx_filename);
212 free(dc);
213 }
214
dump_daq_start(void * handle)215 static int dump_daq_start(void *handle)
216 {
217 DumpContext *dc = (DumpContext*) handle;
218
219 int rval = CALL_SUBAPI_NOARGS(dc, start);
220 if (rval != DAQ_SUCCESS)
221 return rval;
222
223 int dlt = CALL_SUBAPI_NOARGS(dc, get_datalink_type);
224 int snaplen = CALL_SUBAPI_NOARGS(dc, get_snaplen);
225
226 if (dc->tx_filename)
227 {
228 pcap_t *pcap = pcap_open_dead(dlt, snaplen);
229 if (!pcap)
230 {
231 CALL_SUBAPI_NOARGS(dc, stop);
232 SET_ERROR(dc->modinst, "Could not create a dead PCAP handle!");
233 return DAQ_ERROR;
234 }
235 dc->tx_dumper = pcap_dump_open(pcap, dc->tx_filename);
236 if (!dc->tx_dumper)
237 {
238 CALL_SUBAPI_NOARGS(dc, stop);
239 SET_ERROR(dc->modinst, "Could not open PCAP %s for writing: %s", dc->tx_filename, pcap_geterr(pcap));
240 pcap_close(pcap);
241 return DAQ_ERROR;
242 }
243 pcap_close(pcap);
244 }
245
246 if (dc->rx_filename)
247 {
248 pcap_t *pcap = pcap_open_dead(dlt, snaplen);
249 if (!pcap)
250 {
251 CALL_SUBAPI_NOARGS(dc, stop);
252 SET_ERROR(dc->modinst, "Could not create a dead PCAP handle!");
253 return DAQ_ERROR;
254 }
255 dc->rx_dumper = pcap_dump_open(pcap, dc->rx_filename);
256 if (!dc->rx_dumper)
257 {
258 CALL_SUBAPI_NOARGS(dc, stop);
259 SET_ERROR(dc->modinst, "Could not open PCAP %s for writing: %s", dc->rx_filename, pcap_geterr(pcap));
260 pcap_close(pcap);
261 return DAQ_ERROR;
262 }
263 pcap_close(pcap);
264 }
265
266 return DAQ_SUCCESS;
267 }
268
dump_daq_inject(void * handle,DAQ_MsgType type,const void * hdr,const uint8_t * data,uint32_t data_len)269 static int dump_daq_inject(void *handle, DAQ_MsgType type, const void *hdr, const uint8_t *data, uint32_t data_len)
270 {
271 DumpContext *dc = (DumpContext*) handle;
272
273 if (dc->tx_dumper && type == DAQ_MSG_TYPE_PACKET)
274 {
275 const DAQ_PktHdr_t *pkthdr = (const DAQ_PktHdr_t *) hdr;
276 struct pcap_pkthdr pcap_hdr;
277
278 pcap_hdr.ts.tv_sec = pkthdr->ts.tv_sec;
279 pcap_hdr.ts.tv_usec = pkthdr->ts.tv_usec;
280 pcap_hdr.caplen = data_len;
281 pcap_hdr.len = data_len;
282
283 pcap_dump((u_char *) dc->tx_dumper, &pcap_hdr, data);
284 }
285
286 if (CHECK_SUBAPI(dc, inject))
287 {
288 int rval = CALL_SUBAPI(dc, inject, type, hdr, data, data_len);
289 if (rval != DAQ_SUCCESS)
290 return rval;
291 }
292
293 dc->stats.packets_injected++;
294 return DAQ_SUCCESS;
295 }
296
dump_daq_inject_relative(void * handle,const DAQ_Msg_t * msg,const uint8_t * data,uint32_t data_len,int reverse)297 static int dump_daq_inject_relative(void *handle, const DAQ_Msg_t *msg, const uint8_t *data, uint32_t data_len, int reverse)
298 {
299 DumpContext *dc = (DumpContext*) handle;
300
301 if (dc->tx_dumper && msg->type == DAQ_MSG_TYPE_PACKET)
302 {
303 const DAQ_PktHdr_t *pkthdr = (const DAQ_PktHdr_t *) msg->hdr;
304 struct pcap_pkthdr pcap_hdr;
305
306 // Reuse the timestamp from the original packet for the injected packet
307 pcap_hdr.ts.tv_sec = pkthdr->ts.tv_sec;
308 pcap_hdr.ts.tv_usec = pkthdr->ts.tv_usec;
309 pcap_hdr.caplen = data_len;
310 pcap_hdr.len = data_len;
311
312 pcap_dump((u_char *) dc->tx_dumper, &pcap_hdr, data);
313 }
314
315 if (CHECK_SUBAPI(dc, inject_relative))
316 {
317 int rval = CALL_SUBAPI(dc, inject_relative, msg, data, data_len, reverse);
318 if (rval != DAQ_SUCCESS)
319 return rval;
320 }
321
322 dc->stats.packets_injected++;
323 return DAQ_SUCCESS;
324 }
325
dump_daq_stop(void * handle)326 static int dump_daq_stop(void *handle)
327 {
328 DumpContext *dc = (DumpContext*) handle;
329 int rval = CALL_SUBAPI_NOARGS(dc, stop);
330
331 if (rval != DAQ_SUCCESS)
332 return rval;
333
334 if (dc->tx_dumper)
335 {
336 pcap_dump_close(dc->tx_dumper);
337 dc->tx_dumper = NULL;
338 }
339
340 if (dc->rx_dumper)
341 {
342 pcap_dump_close(dc->rx_dumper);
343 dc->rx_dumper = NULL;
344 }
345
346 return DAQ_SUCCESS;
347 }
348
dump_daq_get_stats(void * handle,DAQ_Stats_t * stats)349 static int dump_daq_get_stats(void *handle, DAQ_Stats_t *stats)
350 {
351 DumpContext *dc = (DumpContext*) handle;
352 int rval = DAQ_SUCCESS;
353
354 if (CHECK_SUBAPI(dc, get_stats))
355 {
356 rval = CALL_SUBAPI(dc, get_stats, stats);
357 /* Use our own concept of verdict and injected packet stats */
358 for (int i = 0; i < MAX_DAQ_VERDICT; i++)
359 stats->verdicts[i] = dc->stats.verdicts[i];
360 stats->packets_injected = dc->stats.packets_injected;
361 }
362 else
363 *stats = dc->stats;
364
365 return rval;
366 }
367
dump_daq_reset_stats(void * handle)368 static void dump_daq_reset_stats(void *handle)
369 {
370 DumpContext *dc = (DumpContext*) handle;
371 if (CHECK_SUBAPI(dc, reset_stats))
372 CALL_SUBAPI_NOARGS(dc, reset_stats);
373 memset(&dc->stats, 0, sizeof(dc->stats));
374 }
375
dump_daq_get_capabilities(void * handle)376 static uint32_t dump_daq_get_capabilities(void *handle)
377 {
378 DumpContext *dc = (DumpContext*) handle;
379 uint32_t caps = CHECK_SUBAPI(dc, get_capabilities) ? CALL_SUBAPI_NOARGS(dc, get_capabilities) : 0;
380 caps |= DAQ_CAPA_BLOCK | DAQ_CAPA_REPLACE | DAQ_CAPA_INJECT;
381 return caps;
382 }
383
dump_daq_msg_receive(void * handle,const unsigned max_recv,const DAQ_Msg_t * msgs[],DAQ_RecvStatus * rstat)384 static unsigned dump_daq_msg_receive(void *handle, const unsigned max_recv, const DAQ_Msg_t *msgs[], DAQ_RecvStatus *rstat)
385 {
386 DumpContext *dc = (DumpContext*) handle;
387 unsigned num_receive = CALL_SUBAPI(dc, msg_receive, max_recv, msgs, rstat);
388
389 if (dc->rx_dumper)
390 {
391 for (unsigned idx = 0; idx < num_receive; idx++)
392 {
393 const DAQ_Msg_t *msg = msgs[idx];
394
395 if (msg->type != DAQ_MSG_TYPE_PACKET)
396 continue;
397
398 const DAQ_PktHdr_t *hdr = (const DAQ_PktHdr_t *) msg->hdr;
399 const uint8_t *data = msg->data;
400 struct pcap_pkthdr pcap_hdr;
401
402 pcap_hdr.ts.tv_sec = hdr->ts.tv_sec;
403 pcap_hdr.ts.tv_usec = hdr->ts.tv_usec;
404 pcap_hdr.caplen = msg->data_len;
405 pcap_hdr.len = hdr->pktlen;
406 pcap_dump((u_char *) dc->rx_dumper, &pcap_hdr, data);
407 }
408 }
409
410 return num_receive;
411 }
412
dump_daq_msg_finalize(void * handle,const DAQ_Msg_t * msg,DAQ_Verdict verdict)413 static int dump_daq_msg_finalize(void *handle, const DAQ_Msg_t *msg, DAQ_Verdict verdict)
414 {
415 static const int s_fwd[MAX_DAQ_VERDICT] = { 1, 0, 1, 1, 0, 1 };
416 DumpContext *dc = (DumpContext *) handle;
417
418 dc->stats.verdicts[verdict]++;
419 if (dc->tx_dumper && msg->type == DAQ_MSG_TYPE_PACKET && s_fwd[verdict])
420 {
421 const DAQ_PktHdr_t *hdr = (const DAQ_PktHdr_t *) msg->hdr;
422 const uint8_t *data = msg->data;
423 struct pcap_pkthdr pcap_hdr;
424
425 pcap_hdr.ts.tv_sec = hdr->ts.tv_sec;
426 pcap_hdr.ts.tv_usec = hdr->ts.tv_usec;
427 pcap_hdr.caplen = msg->data_len;
428 pcap_hdr.len = hdr->pktlen;
429 pcap_dump((u_char *) dc->tx_dumper, &pcap_hdr, data);
430 }
431
432 return CALL_SUBAPI(dc, msg_finalize, msg, verdict);
433 }
434
435 //-------------------------------------------------------------------------
436
437 #ifdef BUILDING_SO
438 DAQ_SO_PUBLIC DAQ_ModuleAPI_t DAQ_MODULE_DATA =
439 #else
440 DAQ_ModuleAPI_t dump_daq_module_data =
441 #endif
442 {
443 /* .api_version = */ DAQ_MODULE_API_VERSION,
444 /* .api_size = */ sizeof(DAQ_ModuleAPI_t),
445 /* .module_version = */ DAQ_DUMP_VERSION,
446 /* .name = */ "dump",
447 /* .type = */ DAQ_TYPE_WRAPPER | DAQ_TYPE_INLINE_CAPABLE,
448 /* .load = */ dump_daq_module_load,
449 /* .unload = */ dump_daq_module_unload,
450 /* .get_variable_descs = */ dump_daq_get_variable_descs,
451 /* .instantiate = */ dump_daq_instantiate,
452 /* .destroy = */ dump_daq_destroy,
453 /* .set_filter = */ NULL,
454 /* .start = */ dump_daq_start,
455 /* .inject = */ dump_daq_inject,
456 /* .inject_relative = */ dump_daq_inject_relative,
457 /* .interrupt = */ NULL,
458 /* .stop = */ dump_daq_stop,
459 /* .ioctl = */ NULL,
460 /* .get_stats = */ dump_daq_get_stats,
461 /* .reset_stats = */ dump_daq_reset_stats,
462 /* .get_snaplen = */ NULL,
463 /* .get_capabilities = */ dump_daq_get_capabilities,
464 /* .get_datalink_type = */ NULL,
465 /* .config_load = */ NULL,
466 /* .config_swap = */ NULL,
467 /* .config_free = */ NULL,
468 /* .msg_receive = */ dump_daq_msg_receive,
469 /* .msg_finalize = */ dump_daq_msg_finalize,
470 /* .get_msg_pool_info = */ NULL,
471 };
472
473