Files
nDPId/examples/c-captured/c-captured.c
Toni Uhlig 1c3ef69faa nDPIsrvd collectd-exec overhaul.
* Install targets updated.
 * Removed nDPIsrvd.h token validation function (done automatically by token_get).

Signed-off-by: Toni Uhlig <matzeton@googlemail.com>
2021-03-15 14:39:43 +01:00

652 lines
19 KiB
C

#include <arpa/inet.h>
#include <errno.h>
#include <linux/limits.h>
#include <netinet/ip_icmp.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <pcap/pcap.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include "nDPIsrvd.h"
#include "utarray.h"
#include "utils.h"
//#define VERBOSE
#define DEFAULT_DATADIR "/tmp/nDPId-captured"
struct packet_data
{
nDPIsrvd_ull packet_ts_sec;
nDPIsrvd_ull packet_ts_usec;
nDPIsrvd_ull packet_len;
int base64_packet_size;
union {
char * base64_packet;
char const * base64_packet_const;
};
};
struct flow_user_data
{
uint8_t flow_new_seen;
uint8_t detection_finished;
uint8_t guessed;
uint8_t detected;
uint8_t risky;
uint8_t midstream;
nDPIsrvd_ull flow_datalink;
nDPIsrvd_ull flow_max_packets;
UT_array * packets;
};
static struct nDPIsrvd_socket * sock = NULL;
static int main_thread_shutdown = 0;
static char * pidfile = NULL;
static char * serv_optarg = NULL;
static nDPIsrvd_ull pcap_filename_rotation = 0;
static time_t pcap_filename_last_rotation = 0;
static struct tm pcap_filename_last_rotation_tm = {};
static char * user = NULL;
static char * group = NULL;
static char * datadir = NULL;
static uint8_t process_guessed = 0;
static uint8_t process_undetected = 0;
static uint8_t process_risky = 0;
static uint8_t process_midstream = 0;
static void packet_data_copy(void * dst, const void * src)
{
struct packet_data * const pd_dst = (struct packet_data *)dst;
struct packet_data const * const pd_src = (struct packet_data *)src;
*pd_dst = *pd_src;
if (pd_src->base64_packet != NULL && pd_src->base64_packet_size > 0)
{
pd_dst->base64_packet = strndup(pd_src->base64_packet, pd_src->base64_packet_size);
}
else
{
pd_dst->base64_packet = NULL;
pd_dst->base64_packet_size = 0;
}
}
static void packet_data_dtor(void * elt)
{
struct packet_data * const pd_elt = (struct packet_data *)elt;
if (pd_elt->base64_packet != NULL)
{
free(pd_elt->base64_packet);
pd_elt->base64_packet = NULL;
pd_elt->base64_packet_size = 0;
}
}
static const UT_icd packet_data_icd = {sizeof(struct packet_data), NULL, packet_data_copy, packet_data_dtor};
static char * generate_pcap_filename(struct nDPIsrvd_flow const * const flow,
struct flow_user_data const * const flow_user,
char * const dest,
size_t size)
{
char appendix[32] = {};
if (pcap_filename_rotation > 0)
{
time_t current_time = time(NULL);
if (current_time >= pcap_filename_last_rotation + (time_t)pcap_filename_rotation)
{
pcap_filename_last_rotation = current_time;
if (localtime_r(&pcap_filename_last_rotation, &pcap_filename_last_rotation_tm) == NULL)
{
return NULL;
}
}
if (strftime(appendix, sizeof(appendix), "%d_%m_%y-%H_%M_%S", &pcap_filename_last_rotation_tm) == 0)
{
return NULL;
}
}
else
{
if (snprintf(appendix, sizeof(appendix), "%llu", flow->id_as_ull) <= 0)
{
return NULL;
}
}
if (flow_user->guessed != 0 || flow_user->detected == 0 || flow_user->risky != 0 || flow_user->midstream != 0)
{
char const * flow_type = NULL;
if (flow_user->midstream != 0)
{
flow_type = "midstream";
}
else if (flow_user->guessed != 0)
{
flow_type = "guessed";
}
else if (flow_user->detected == 0)
{
flow_type = "undetected";
}
else if (flow_user->risky != 0)
{
flow_type = "risky";
}
else
{
flow_type = "unknown-type";
}
int ret = snprintf(dest, size, "%s/flow-%s-%s.pcap", datadir, flow_type, appendix);
if (ret <= 0 || (size_t)ret > size)
{
return NULL;
}
}
else
{
return NULL;
}
return dest;
}
static int packet_write_pcap_file(UT_array const * const pd_array, int pkt_datalink, char const * const filename)
{
size_t const max_packet_len = 65535;
if (pd_array->icd.copy != packet_data_copy || pd_array->icd.dtor != packet_data_dtor)
{
return 1;
}
if (utarray_len(pd_array) == 0)
{
syslog(LOG_DAEMON, "no packets received via json, can not dump anything to pcap");
return 0;
}
pcap_t * p = pcap_open_dead(pkt_datalink, max_packet_len);
if (p == NULL)
{
return 1;
}
pcap_dumper_t * pd;
if (access(filename, F_OK) == 0)
{
pd = pcap_dump_open_append(p, filename);
}
else
{
pd = pcap_dump_open(p, filename);
}
if (pd == NULL)
{
syslog(LOG_DAEMON | LOG_ERR, "pcap error %s", pcap_geterr(p));
pcap_close(p);
return 1;
}
struct packet_data * pd_elt = (struct packet_data *)utarray_front(pd_array);
do
{
if (pd_elt == NULL)
{
break;
}
unsigned char pkt_buf[max_packet_len];
size_t pkt_buf_len = sizeof(pkt_buf);
if (nDPIsrvd_base64decode(pd_elt->base64_packet, pd_elt->base64_packet_size, pkt_buf, &pkt_buf_len) != 0 ||
pkt_buf_len == 0)
{
syslog(LOG_DAEMON | LOG_ERR,
"packet base64 decode failed (%d bytes): %s",
pd_elt->base64_packet_size,
pd_elt->base64_packet);
}
else
{
struct pcap_pkthdr phdr;
phdr.ts.tv_sec = pd_elt->packet_ts_sec;
phdr.ts.tv_usec = pd_elt->packet_ts_usec;
phdr.caplen = pkt_buf_len;
phdr.len = pkt_buf_len;
pcap_dump((unsigned char *)pd, &phdr, pkt_buf);
}
} while ((pd_elt = (struct packet_data *)utarray_next(pd_array, pd_elt)) != NULL);
pcap_dump_close(pd);
pcap_close(p);
return 0;
}
#ifdef VERBOSE
static void packet_data_print(UT_array const * const pd_array)
{
if (pd_array->icd.copy != packet_data_copy || pd_array->icd.dtor != packet_data_dtor)
{
return;
}
printf("packet-data array size(): %u\n", pd_array->n);
struct packet_data * pd_elt = (struct packet_data *)utarray_front(pd_array);
do
{
if (pd_elt == NULL)
{
break;
}
printf("\tpacket-data base64 length: %d\n", pd_elt->base64_packet_size);
} while ((pd_elt = (struct packet_data *)utarray_next(pd_array, pd_elt)) != NULL);
}
#else
#define packet_data_print(pd_array)
#endif
static enum nDPIsrvd_conversion_return perror_ull(enum nDPIsrvd_conversion_return retval, char const * const prefix)
{
switch (retval)
{
case CONVERSION_OK:
break;
case CONVERISON_KEY_NOT_FOUND:
syslog(LOG_DAEMON | LOG_ERR, "%s: Key not found.", prefix);
break;
case CONVERSION_NOT_A_NUMBER:
syslog(LOG_DAEMON | LOG_ERR, "%s: Not a valid number.", prefix);
break;
case CONVERSION_RANGE_EXCEEDED:
syslog(LOG_DAEMON | LOG_ERR, "%s: Number too large.", prefix);
break;
default:
syslog(LOG_DAEMON | LOG_ERR, "Internal error, invalid conversion return value.");
break;
}
return retval;
}
static enum nDPIsrvd_callback_return captured_json_callback(struct nDPIsrvd_socket * const sock,
struct nDPIsrvd_flow * const flow)
{
if (flow == NULL)
{
return CALLBACK_OK; // We do not care for non flow/packet-flow events for NOW.
}
struct flow_user_data * const flow_user = (struct flow_user_data *)flow->flow_user_data;
#ifdef VERBOSE
struct nDPIsrvd_json_token * current_token = NULL;
struct nDPIsrvd_json_token * jtmp = NULL;
HASH_ITER(hh, sock->json.token_table, current_token, jtmp)
{
if (current_token->value != NULL)
{
printf("[%.*s : %.*s] ",
current_token->key_length,
current_token->key,
current_token->value_length,
current_token->value);
}
}
printf("EoF\n");
#endif
if (flow_user == NULL || flow_user->detection_finished != 0)
{
return CALLBACK_OK;
}
if (TOKEN_VALUE_EQUALS_SZ(TOKEN_GET_SZ(sock, "packet_event_name"), "packet-flow") != 0)
{
struct nDPIsrvd_json_token const * const pkt = TOKEN_GET_SZ(sock, "pkt");
if (pkt == NULL)
{
return CALLBACK_ERROR;
}
if (flow_user->packets == NULL)
{
utarray_new(flow_user->packets, &packet_data_icd);
}
if (flow_user->packets == NULL)
{
return CALLBACK_ERROR;
}
nDPIsrvd_ull pkt_ts_sec = 0ull;
perror_ull(TOKEN_VALUE_TO_ULL(TOKEN_GET_SZ(sock, "pkt_ts_sec"), &pkt_ts_sec), "pkt_ts_sec");
nDPIsrvd_ull pkt_ts_usec = 0ull;
perror_ull(TOKEN_VALUE_TO_ULL(TOKEN_GET_SZ(sock, "pkt_ts_usec"), &pkt_ts_usec), "pkt_ts_usec");
nDPIsrvd_ull pkt_len = 0ull;
perror_ull(TOKEN_VALUE_TO_ULL(TOKEN_GET_SZ(sock, "pkt_len"), &pkt_len), "pkt_len");
nDPIsrvd_ull pkt_l4_len = 0ull;
perror_ull(TOKEN_VALUE_TO_ULL(TOKEN_GET_SZ(sock, "pkt_l4_len"), &pkt_l4_len), "pkt_l4_len");
nDPIsrvd_ull pkt_l4_offset = 0ull;
perror_ull(TOKEN_VALUE_TO_ULL(TOKEN_GET_SZ(sock, "pkt_l4_offset"), &pkt_l4_offset), "pkt_l4_offset");
struct packet_data pd = {.packet_ts_sec = pkt_ts_sec,
.packet_ts_usec = pkt_ts_usec,
.packet_len = pkt_len,
.base64_packet_size = pkt->value_length,
.base64_packet_const = pkt->value};
utarray_push_back(flow_user->packets, &pd);
}
{
struct nDPIsrvd_json_token const * const flow_event_name = TOKEN_GET_SZ(sock, "flow_event_name");
if (TOKEN_VALUE_EQUALS_SZ(flow_event_name, "new") != 0)
{
flow_user->flow_new_seen = 1;
perror_ull(TOKEN_VALUE_TO_ULL(TOKEN_GET_SZ(sock, "flow_datalink"), &flow_user->flow_datalink),
"flow_datalink");
perror_ull(TOKEN_VALUE_TO_ULL(TOKEN_GET_SZ(sock, "flow_max_packets"), &flow_user->flow_max_packets),
"flow_max_packets");
if (TOKEN_VALUE_EQUALS_SZ(TOKEN_GET_SZ(sock, "midstream"), "1") != 0)
{
flow_user->midstream = 1;
}
return CALLBACK_OK;
}
else if (TOKEN_VALUE_EQUALS_SZ(flow_event_name, "guessed") != 0)
{
flow_user->guessed = 1;
flow_user->detection_finished = 1;
}
else if (TOKEN_VALUE_EQUALS_SZ(flow_event_name, "not-detected") != 0)
{
flow_user->detected = 0;
flow_user->detection_finished = 1;
}
else if (TOKEN_VALUE_EQUALS_SZ(flow_event_name, "detected") != 0)
{
flow_user->detected = 1;
flow_user->detection_finished = 1;
if (TOKEN_GET_SZ(sock, "flow_risk") != NULL)
{
flow_user->risky = 1;
}
}
if (flow_user->flow_new_seen == 0)
{
return CALLBACK_OK;
}
if (flow_user->packets == NULL || flow_user->flow_max_packets == 0 || utarray_len(flow_user->packets) == 0)
{
syslog(LOG_DAEMON | LOG_ERR, "flow %llu: No packets captured.", flow->id_as_ull);
return CALLBACK_OK;
}
if (flow_user->detection_finished != 0 &&
((flow_user->guessed != 0 && process_guessed != 0) ||
(flow_user->detected == 0 && process_undetected != 0) || (flow_user->risky != 0 && process_risky != 0) ||
(flow_user->midstream != 0 && process_midstream != 0)))
{
packet_data_print(flow_user->packets);
{
char pcap_filename[PATH_MAX];
if (generate_pcap_filename(flow, flow_user, pcap_filename, sizeof(pcap_filename)) == NULL)
{
syslog(LOG_DAEMON | LOG_ERR, "%s", "Internal error. Could not generate PCAP filename, exit ..");
return CALLBACK_ERROR;
}
#ifdef VERBOSE
printf("flow %llu saved to %s\n", flow->id_as_ull, pcap_filename);
#endif
if (packet_write_pcap_file(flow_user->packets, flow_user->flow_datalink, pcap_filename) != 0)
{
return CALLBACK_ERROR;
}
}
utarray_free(flow_user->packets);
flow_user->packets = NULL;
}
}
return CALLBACK_OK;
}
static void sighandler(int signum)
{
(void)signum;
if (main_thread_shutdown == 0)
{
main_thread_shutdown = 1;
}
}
static void captured_flow_end_callback(struct nDPIsrvd_socket * const sock, struct nDPIsrvd_flow * const flow)
{
(void)sock;
struct flow_user_data * const ud = (struct flow_user_data *)flow->flow_user_data;
if (ud != NULL && ud->packets != NULL)
{
utarray_free(ud->packets);
ud->packets = NULL;
}
}
static int parse_options(int argc, char ** argv)
{
int opt;
static char const usage[] =
"Usage: %s "
"[-d] [-p pidfile] [-s host] [-r rotate-every-n-seconds] [-u user] [-g group] [-D dir] [-G] [-U] [-R] [-M]\n\n"
"\t-d\tForking into background after initialization.\n"
"\t-p\tWrite the daemon PID to the given file path.\n"
"\t-s\tDestination where nDPIsrvd is listening on.\n"
"\t \tCan be either a path to UNIX socket or an IPv4/TCP-Port IPv6/TCP-Port tuple.\n"
"\t-r\tRotate PCAP files every n seconds\n"
"\t-u\tChange user.\n"
"\t-g\tChange group.\n"
"\t-D\tDatadir - Where to store PCAP files.\n"
"\t-G\tGuessed - Dump guessed flows to a PCAP file.\n"
"\t-U\tUndetected - Dump undetected flows to a PCAP file.\n"
"\t-R\tRisky - Dump risky flows to a PCAP file.\n"
"\t-M\tMidstream - Dump midstream flows to a PCAP file.\n";
while ((opt = getopt(argc, argv, "hdp:s:r:u:g:D:GURM")) != -1)
{
switch (opt)
{
case 'd':
daemonize_enable();
break;
case 'p':
free(pidfile);
pidfile = strdup(optarg);
break;
case 's':
free(serv_optarg);
serv_optarg = strdup(optarg);
break;
case 'r':
if (perror_ull(str_value_to_ull(optarg, &pcap_filename_rotation), "pcap_filename_rotation") !=
CONVERSION_OK)
{
return 1;
}
break;
case 'u':
free(user);
user = strdup(optarg);
break;
case 'g':
free(group);
group = strdup(optarg);
break;
case 'D':
free(datadir);
datadir = strdup(optarg);
break;
case 'G':
process_guessed = 1;
break;
case 'U':
process_undetected = 1;
break;
case 'R':
process_risky = 1;
break;
case 'M':
process_midstream = 1;
break;
default:
fprintf(stderr, usage, argv[0]);
return 1;
}
}
if (serv_optarg == NULL)
{
serv_optarg = strdup(DISTRIBUTOR_UNIX_SOCKET);
}
if (nDPIsrvd_setup_address(&sock->address, serv_optarg) != 0)
{
fprintf(stderr, "%s: Could not parse address `%s'\n", argv[0], serv_optarg);
return 1;
}
if (datadir == NULL)
{
datadir = strdup(DEFAULT_DATADIR);
}
if (process_guessed == 0 && process_undetected == 0 && process_risky == 0 && process_midstream == 0)
{
fprintf(stderr, "%s: Nothing to capture. Use at least one of -G / -U / -R / -M flags.\n", argv[0]);
return 1;
}
if (optind < argc)
{
fprintf(stderr, "Unexpected argument after options\n\n");
fprintf(stderr, usage, argv[0]);
return 1;
}
errno = 0;
if (mkdir(datadir, S_IRWXU) != 0 && errno != EEXIST)
{
fprintf(stderr, "%s: Could not create directory %s: %s\n", argv[0], datadir, strerror(errno));
return 1;
}
return 0;
}
static int mainloop(void)
{
while (main_thread_shutdown == 0)
{
errno = 0;
enum nDPIsrvd_read_return read_ret = nDPIsrvd_read(sock);
if (read_ret != READ_OK)
{
syslog(LOG_DAEMON | LOG_ERR, "nDPIsrvd read failed with: %s", nDPIsrvd_enum_to_string(read_ret));
return 1;
}
enum nDPIsrvd_parse_return parse_ret = nDPIsrvd_parse(sock);
if (parse_ret != PARSE_OK)
{
syslog(LOG_DAEMON | LOG_ERR, "nDPIsrvd parse failed with: %s", nDPIsrvd_enum_to_string(parse_ret));
return 1;
}
}
return 0;
}
int main(int argc, char ** argv)
{
sock = nDPIsrvd_init(0, sizeof(struct flow_user_data), captured_json_callback, captured_flow_end_callback);
if (sock == NULL)
{
fprintf(stderr, "%s: nDPIsrvd socket memory allocation failed!\n", argv[0]);
return 1;
}
if (parse_options(argc, argv) != 0)
{
return 1;
}
printf("Recv buffer size: %u\n", NETWORK_BUFFER_MAX_SIZE);
printf("Connecting to `%s'..\n", serv_optarg);
enum nDPIsrvd_connect_return connect_ret = nDPIsrvd_connect(sock);
if (connect_ret != CONNECT_OK)
{
fprintf(stderr, "%s: nDPIsrvd socket connect to %s failed!\n", argv[0], serv_optarg);
nDPIsrvd_free(&sock);
return 1;
}
signal(SIGINT, sighandler);
signal(SIGTERM, sighandler);
signal(SIGPIPE, sighandler);
if (daemonize_with_pidfile(pidfile) != 0)
{
return 1;
}
openlog("c-captured", LOG_CONS, LOG_DAEMON);
errno = 0;
if (user != NULL && change_user_group(user, group, pidfile, datadir /* :D */, NULL) != 0)
{
if (errno != 0)
{
syslog(LOG_DAEMON | LOG_ERR, "Change user/group failed: %s", strerror(errno));
}
else
{
syslog(LOG_DAEMON | LOG_ERR, "Change user/group failed.");
}
return 1;
}
chmod(datadir, S_IRWXU);
int retval = mainloop();
nDPIsrvd_free(&sock);
closelog();
return retval;
}