395 lines
12 KiB
C
395 lines
12 KiB
C
/*
|
|
* Copyright (c) 2016-2024 OARC, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
*
|
|
* 3. Neither the name of the copyright holder nor the names of its
|
|
* contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "dumper.h"
|
|
#include "iaddr.h"
|
|
#include "log.h"
|
|
#include "pcaps.h"
|
|
|
|
/*
|
|
* when flags & DNSCAP_OUTPUT_ISDNS, payload points to a DNS packet
|
|
*/
|
|
void output(const char* descr, iaddr from, iaddr to, uint8_t proto, unsigned flags,
|
|
unsigned sport, unsigned dport, my_bpftimeval ts,
|
|
u_char* pkt_copy, const unsigned olen,
|
|
u_char* payload, const unsigned payloadlen)
|
|
{
|
|
struct plugin* p;
|
|
|
|
for (p = HEAD(plugins); p != NULL; p = NEXT(p, link)) {
|
|
if (p->filter && (*p->filter)(descr, &from, &to, proto, flags, sport, dport, ts, pkt_copy, olen, payload, payloadlen)) {
|
|
if (dumptrace >= 3) {
|
|
fprintf(stderr, "filtered: capturedbytes=%zu, proto=%d, isfrag=%s, isdns=%s, olen=%u, payloadlen=%u\n",
|
|
capturedbytes,
|
|
proto,
|
|
flags & DNSCAP_OUTPUT_ISFRAG ? "yes" : "no",
|
|
flags & DNSCAP_OUTPUT_ISDNS ? "yes" : "no",
|
|
olen,
|
|
payloadlen);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
msgcount++;
|
|
capturedbytes += olen;
|
|
|
|
if (dumptrace >= 3) {
|
|
fprintf(stderr, "output: capturedbytes=%zu, proto=%d, isfrag=%s, isdns=%s, olen=%u, payloadlen=%u\n",
|
|
capturedbytes,
|
|
proto,
|
|
flags & DNSCAP_OUTPUT_ISFRAG ? "yes" : "no",
|
|
flags & DNSCAP_OUTPUT_ISDNS ? "yes" : "no",
|
|
olen,
|
|
payloadlen);
|
|
}
|
|
|
|
/* Output stage. */
|
|
if (preso) {
|
|
fputs(descr, stderr);
|
|
if (flags & DNSCAP_OUTPUT_ISFRAG) {
|
|
fprintf(stderr, ";: [%s] ", ia_str(from));
|
|
fprintf(stderr, "-> [%s] (frag)\n", ia_str(to));
|
|
} else {
|
|
fprintf(stderr, "\t[%s].%u ", ia_str(from), sport);
|
|
fprintf(stderr, "[%s].%u ", ia_str(to), dport);
|
|
if ((flags & DNSCAP_OUTPUT_ISDNS) && payload)
|
|
dump_dns(payload, payloadlen, stderr, "\\\n\t");
|
|
}
|
|
putc('\n', stderr);
|
|
}
|
|
if (dump_type != nowhere) {
|
|
if (options.dump_format == pcap) {
|
|
struct pcap_pkthdr h;
|
|
|
|
memset(&h, 0, sizeof h);
|
|
h.ts = ts;
|
|
h.len = h.caplen = olen;
|
|
pcap_dump((u_char*)dumper, &h, pkt_copy);
|
|
if (flush)
|
|
pcap_dump_flush(dumper);
|
|
} else if (options.dump_format == cbor && (flags & DNSCAP_OUTPUT_ISDNS) && payload) {
|
|
int ret = output_cbor(from, to, proto, flags, sport, dport, ts, payload, payloadlen);
|
|
|
|
if (ret == DUMP_CBOR_FLUSH) {
|
|
if (dumper_close(ts)) {
|
|
fprintf(stderr, "%s: dumper_close() failed\n", ProgramName);
|
|
exit(1);
|
|
}
|
|
if (dumper_open(ts)) {
|
|
fprintf(stderr, "%s: dumper_open() failed\n", ProgramName);
|
|
exit(1);
|
|
}
|
|
} else if (ret != DUMP_CBOR_OK) {
|
|
fprintf(stderr, "%s: output to cbor failed [%u]\n", ProgramName, ret);
|
|
exit(1);
|
|
}
|
|
} else if (options.dump_format == cds) {
|
|
int ret = output_cds(from, to, proto, flags, sport, dport, ts, pkt_copy, olen, payload, payloadlen);
|
|
|
|
if (ret == DUMP_CDS_FLUSH) {
|
|
if (dumper_close(ts)) {
|
|
fprintf(stderr, "%s: dumper_close() failed\n", ProgramName);
|
|
exit(1);
|
|
}
|
|
if (dumper_open(ts)) {
|
|
fprintf(stderr, "%s: dumper_open() failed\n", ProgramName);
|
|
exit(1);
|
|
}
|
|
} else if (ret != DUMP_CDS_OK) {
|
|
fprintf(stderr, "%s: output to cds failed [%u]\n", ProgramName, ret);
|
|
exit(1);
|
|
}
|
|
}
|
|
}
|
|
for (p = HEAD(plugins); p != NULL; p = NEXT(p, link))
|
|
if (p->output)
|
|
(*p->output)(descr, from, to, proto, flags, sport, dport, ts, pkt_copy, olen, payload, payloadlen);
|
|
return;
|
|
}
|
|
|
|
int dumper_open(my_bpftimeval ts)
|
|
{
|
|
const char* t = NULL;
|
|
struct plugin* p;
|
|
|
|
assert(dump_state == dumper_closed);
|
|
|
|
while (ts.tv_usec >= MILLION) {
|
|
ts.tv_sec++;
|
|
ts.tv_usec -= MILLION;
|
|
}
|
|
if (limit_seconds != 0U)
|
|
next_interval = ts.tv_sec
|
|
- (ts.tv_sec % limit_seconds)
|
|
+ limit_seconds;
|
|
|
|
if (dump_type == to_stdout) {
|
|
t = "-";
|
|
} else if (dump_type == to_file) {
|
|
char sbuf[64];
|
|
struct tm tm;
|
|
|
|
gmtime_r((time_t*)&ts.tv_sec, &tm);
|
|
strftime(sbuf, 64, "%Y%m%d.%H%M%S", &tm);
|
|
if (asprintf(&dumpname, "%s.%s.%06lu%s",
|
|
dump_base, sbuf,
|
|
(u_long)ts.tv_usec, dump_suffix ? dump_suffix : "")
|
|
< 0
|
|
|| asprintf(&dumpnamepart, "%s.part", dumpname) < 0) {
|
|
logerr("asprintf: %s", strerror(errno));
|
|
return (TRUE);
|
|
}
|
|
t = dumpnamepart;
|
|
}
|
|
if (NULL != t) {
|
|
if (options.dump_format == pcap) {
|
|
dumper = dnscap_pcap_dump_open(pcap_dead, t);
|
|
if (dumper == NULL) {
|
|
logerr("pcap dump open: %s",
|
|
pcap_geterr(pcap_dead));
|
|
return (TRUE);
|
|
}
|
|
}
|
|
}
|
|
dumpstart = ts.tv_sec;
|
|
if (limit_seconds != 0U) {
|
|
struct timeval now;
|
|
u_int seconds;
|
|
time_t targ;
|
|
|
|
gettimeofday(&now, NULL);
|
|
while (now.tv_usec >= MILLION) {
|
|
now.tv_sec++;
|
|
now.tv_usec -= MILLION;
|
|
}
|
|
targ = (((now.tv_sec + (limit_seconds / 2))
|
|
/ limit_seconds)
|
|
+ 1)
|
|
* limit_seconds;
|
|
assert(targ > now.tv_sec);
|
|
seconds = targ - now.tv_sec;
|
|
if (next_interval == 0) {
|
|
alarm(seconds);
|
|
alarm_set = TRUE;
|
|
}
|
|
}
|
|
for (p = HEAD(plugins); p != NULL; p = NEXT(p, link)) {
|
|
int x;
|
|
if (!p->open)
|
|
continue;
|
|
x = (*p->open)(ts);
|
|
if (0 == x)
|
|
continue;
|
|
logerr("%s_open returned %d", p->name, x);
|
|
}
|
|
dump_state = dumper_opened;
|
|
return (FALSE);
|
|
}
|
|
|
|
int dumper_close(my_bpftimeval ts)
|
|
{
|
|
int ret = FALSE;
|
|
struct plugin* p;
|
|
|
|
assert(dump_state == dumper_opened);
|
|
|
|
if (print_pcap_stats)
|
|
do_pcap_stats();
|
|
|
|
if (alarm_set) {
|
|
alarm(0);
|
|
alarm_set = FALSE;
|
|
}
|
|
|
|
if (options.dump_format == pcap) {
|
|
if (dumper) {
|
|
pcap_dump_close(dumper);
|
|
dumper = FALSE;
|
|
}
|
|
} else if (options.dump_format == cbor) {
|
|
if (dump_type == to_stdout) {
|
|
ret = dump_cbor(stdout);
|
|
|
|
if (ret != DUMP_CBOR_OK) {
|
|
fprintf(stderr, "%s: output to cbor failed [%u]\n", ProgramName, ret);
|
|
exit(1);
|
|
}
|
|
} else if (dump_type == to_file) {
|
|
FILE* fp;
|
|
|
|
if (!(fp = fopen(dumpnamepart, "w"))) {
|
|
fprintf(stderr, "%s: fopen(%s) failed: %s\n", ProgramName, dumpnamepart, strerror(errno));
|
|
exit(1);
|
|
}
|
|
ret = dump_cbor(fp);
|
|
fclose(fp);
|
|
if (ret != DUMP_CBOR_OK) {
|
|
fprintf(stderr, "%s: output to cbor failed [%u]\n", ProgramName, ret);
|
|
exit(1);
|
|
}
|
|
}
|
|
} else if (options.dump_format == cds) {
|
|
if (dump_type == to_stdout) {
|
|
ret = dump_cds(stdout);
|
|
|
|
if (ret != DUMP_CDS_OK) {
|
|
fprintf(stderr, "%s: output to cds failed [%u]\n", ProgramName, ret);
|
|
exit(1);
|
|
}
|
|
} else if (dump_type == to_file) {
|
|
FILE* fp;
|
|
|
|
if (!(fp = fopen(dumpnamepart, "w"))) {
|
|
fprintf(stderr, "%s: fopen(%s) failed: %s\n", ProgramName, dumpnamepart, strerror(errno));
|
|
exit(1);
|
|
}
|
|
ret = dump_cds(fp);
|
|
fclose(fp);
|
|
if (ret != DUMP_CDS_OK) {
|
|
fprintf(stderr, "%s: output to cds failed [%u]\n", ProgramName, ret);
|
|
exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dump_type == to_stdout) {
|
|
assert(dumpname == NULL);
|
|
assert(dumpnamepart == NULL);
|
|
if (dumptrace >= 1)
|
|
fprintf(stderr, "%s: breaking\n", ProgramName);
|
|
ret = TRUE;
|
|
} else if (dump_type == to_file) {
|
|
char* cmd = NULL;
|
|
;
|
|
|
|
if (dumptrace >= 1)
|
|
fprintf(stderr, "%s: closing %s\n",
|
|
ProgramName, dumpname);
|
|
if (rename(dumpnamepart, dumpname)) {
|
|
logerr("rename: %s", strerror(errno));
|
|
return ret;
|
|
}
|
|
if (kick_cmd != NULL)
|
|
if (asprintf(&cmd, "%s %s &", kick_cmd, dumpname) < 0) {
|
|
logerr("asprintf: %s", strerror(errno));
|
|
cmd = NULL;
|
|
}
|
|
free(dumpnamepart);
|
|
dumpnamepart = NULL;
|
|
free(dumpname);
|
|
dumpname = NULL;
|
|
if (cmd != NULL) {
|
|
int x = system(cmd);
|
|
if (x)
|
|
logerr("system: \"%s\" returned %d", cmd, x);
|
|
free(cmd);
|
|
}
|
|
if (kick_cmd == NULL && options.dump_format != cbor && options.dump_format != cds)
|
|
ret = TRUE;
|
|
}
|
|
for (p = HEAD(plugins); p != NULL; p = NEXT(p, link)) {
|
|
int x;
|
|
if (!p->close)
|
|
continue;
|
|
x = (*p->close)();
|
|
if (x)
|
|
logerr("%s_close returned %d", p->name, x);
|
|
}
|
|
dump_state = dumper_closed;
|
|
return (ret);
|
|
}
|
|
|
|
#if HAVE_ZLIB_H
|
|
#if HAVE_FUNOPEN
|
|
static int
|
|
gzip_cookie_write(void* cookie, const char* buf, int size)
|
|
{
|
|
return gzwrite((gzFile)cookie, (voidpc)buf, (unsigned)size);
|
|
}
|
|
#elif HAVE_FOPENCOOKIE
|
|
static ssize_t
|
|
gzip_cookie_write(void* cookie, const char* buf, size_t size)
|
|
{
|
|
return gzwrite((gzFile)cookie, (voidpc)buf, (unsigned)size);
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
gzip_cookie_close(void* cookie)
|
|
{
|
|
return gzclose((gzFile)cookie);
|
|
}
|
|
#endif /* HAVE_ZLIB_H */
|
|
|
|
pcap_dumper_t* dnscap_pcap_dump_open(pcap_t* pcap, const char* path)
|
|
{
|
|
#if HAVE_ZLIB_H
|
|
#if HAVE_GZOPEN
|
|
if (wantgzip) {
|
|
FILE* fp = NULL;
|
|
gzFile z = gzopen(path, "w");
|
|
if (z == NULL) {
|
|
perror("gzopen");
|
|
return NULL;
|
|
}
|
|
|
|
#if HAVE_FUNOPEN
|
|
fp = funopen(z, NULL, gzip_cookie_write, NULL, gzip_cookie_close);
|
|
if (fp == NULL) {
|
|
perror("funopen");
|
|
return NULL;
|
|
}
|
|
#elif HAVE_FOPENCOOKIE
|
|
{
|
|
static cookie_io_functions_t cookiefuncs = {
|
|
NULL, gzip_cookie_write, NULL, gzip_cookie_close
|
|
};
|
|
|
|
fp = fopencookie(z, "w", cookiefuncs);
|
|
if (fp == NULL) {
|
|
perror("fopencookie");
|
|
return NULL;
|
|
}
|
|
}
|
|
#endif
|
|
return pcap_dump_fopen(pcap, fp);
|
|
}
|
|
#endif /* HAVE_GZOPEN */
|
|
#endif /* HAVE_ZLIB_H */
|
|
|
|
return pcap_dump_open(pcap, path);
|
|
}
|