Added logging library and udp2file.
Updated README to reflect.
This commit is contained in:
443
Networking/udp2file.c
Normal file
443
Networking/udp2file.c
Normal file
@ -0,0 +1,443 @@
|
||||
/*============================================================================*/
|
||||
//
|
||||
// Title: udp2file.c
|
||||
// Author: RH
|
||||
// Last modified: 13/09/2025
|
||||
//
|
||||
//============================================================================
|
||||
//
|
||||
// Description:
|
||||
// ------------
|
||||
// Tool that takes a file path, ip address, port, and capture size. The tool
|
||||
// then listens on the supplied address and port for UDP datagrams and captures
|
||||
// messages in chunks up to the capture size at a time, before then writing
|
||||
// the binary data to the specified filepath.
|
||||
//
|
||||
// Compilation:
|
||||
// ------------
|
||||
// gcc -Wall -Wextra ../Logging/logging_lib.c udp2file.c -o udp2file
|
||||
//
|
||||
/*============================================================================*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <signal.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include "../Logging/logging_lib.h"
|
||||
|
||||
// program, verbose (optional), buffer check (optional), file path, ip address,
|
||||
// port, capture size
|
||||
#define MIN_NUM_ARGS 4
|
||||
#define MAX_NUM_ARGS 6
|
||||
#define NUM_REQ_ARGS 3
|
||||
|
||||
#define MAX_PAYLOAD_SIZE 65507
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// print usage information to the terminal to provide information on how the
|
||||
// program expects to be run
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void usage (void)
|
||||
{
|
||||
printf("\n");
|
||||
printf("---------------------------------------------------------------------------------------------\n");
|
||||
printf(" Usage:\n");
|
||||
printf(" ./udp2file [options] <file path> <port> <capture size>\n");
|
||||
printf("---------------------------------------------------------------------------------------------\n");
|
||||
printf(" Required Arguments:\n");
|
||||
printf(" <file path>:\n");
|
||||
printf(" \tThe path to the file to write the captured binary data to. Will create the file if it\n");
|
||||
printf(" \tdoes not already exist and will overwite it if it does.\n");
|
||||
printf(" <port>:\n");
|
||||
printf(" \tThe port to listen on for the UDP datagrams.\n");
|
||||
printf(" <capture size>:\n");
|
||||
printf(" \tThe capture size (in bytes) to read from the system UDP buffer. This is the size of\n");
|
||||
printf(" \tthe datagram payload, so an MTU of 9000 (jumbo frames) means a capture size of 8972\n");
|
||||
printf(" \tbytes. [Range: 1 to 65507.]\n");
|
||||
printf("\n");
|
||||
printf(" Optional Arguments:\n");
|
||||
printf(" -h, --help\n");
|
||||
printf(" \tDisplay this usage information then exit.\n");
|
||||
printf(" -v, --verbose\n");
|
||||
printf(" \tVerbose flag to enable additional debug output messages - at high data rate this can\n");
|
||||
printf(" \tbe very noisy and so is not recommended for operational use.\n");
|
||||
printf(" -b, --buffer-check\n");
|
||||
printf(" \tEnable additional checking of the system UDP buffer that warns if the queue is\n");
|
||||
printf(" \tgrowing and reports an error when datagrams are dropped. This is not recommended for\n");
|
||||
printf(" \tvery high data rates (>512 mbps).\n");
|
||||
printf("---------------------------------------------------------------------------------------------\n");
|
||||
printf(" Example:\n");
|
||||
printf(" ./udp_receiver ~/udp_captures/catpure_6.bin 8080 8972\n");
|
||||
printf("---------------------------------------------------------------------------------------------\n");
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// signal handler to allow application to exit cleanly
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
static int run = 1;
|
||||
|
||||
// the handler just sets 'run' to 0, which allows the main loop to exit
|
||||
// and the cleanup code after the main loop to run
|
||||
static void SignalHandler ( int dummy )
|
||||
{
|
||||
dummy++ ;
|
||||
LOGMSG_INFO(">>>>>>>>>>>> Signal received, terminating...")
|
||||
run = 0;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// main program - parse arguments, open output file, create listener
|
||||
// socket, then enter a loop waiting for datagrams and writing them to file
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
// initiliase logger to print messages to stdout
|
||||
int Verbose = 0;
|
||||
LOGGING_INIT_STDOUT
|
||||
|
||||
// signal handlers to ensure program exits cleanly
|
||||
(void) signal(SIGINT , SignalHandler) ;
|
||||
(void) signal(SIGTERM, SignalHandler) ;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// parse input arguments
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// check for help argument
|
||||
if ( (strncmp ( argv[1], "-h", strlen("-h")) == 0)
|
||||
|| (strncmp ( argv[1], "--help", strlen("--help")) == 0)
|
||||
)
|
||||
{
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( (argc < MIN_NUM_ARGS) || (argc > MAX_NUM_ARGS) )
|
||||
{
|
||||
LOGMSG_ERR("Error: incorrect number of arguments supplied. Received %d, minimum %d -> maximum %d",
|
||||
argc, MIN_NUM_ARGS, MAX_NUM_ARGS)
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// check optional arguments (excluding --help)
|
||||
int current_arg = 1;
|
||||
int buffer_check = 0;
|
||||
// check combinations first: -vb, -bv
|
||||
if ( (strncmp ( argv[current_arg], "-vb", strlen("-vb")) == 0)
|
||||
|| (strncmp ( argv[current_arg], "-bv", strlen("-bv")) == 0)
|
||||
)
|
||||
{
|
||||
Verbose = 1;
|
||||
buffer_check = 1;
|
||||
current_arg++;
|
||||
}
|
||||
|
||||
// check for verbose supplied individually - Verbose == 0 used to prevent
|
||||
// '-v' from incrementing current_args if '-vb' above has already been set
|
||||
if ( ( Verbose == 0 )
|
||||
&& ( (strncmp ( argv[current_arg], "-v", strlen("-v")) == 0)
|
||||
|| (strncmp ( argv[current_arg], "--verbose", strlen("--verbose")) == 0) )
|
||||
)
|
||||
{
|
||||
Verbose = 1;
|
||||
current_arg++;
|
||||
}
|
||||
|
||||
// check buffer flag supplied individually - buffer_check == 0 used to prevent
|
||||
// '-b' from incrementing current_args if '-bv' above has already been set
|
||||
if ( ( buffer_check == 0 )
|
||||
&& ( (strncmp ( argv[current_arg], "-b", strlen("-b")) == 0)
|
||||
|| (strncmp ( argv[current_arg], "--buffer-check", strlen("--buffer-check")) == 0) )
|
||||
)
|
||||
{
|
||||
buffer_check = 1;
|
||||
current_arg++;
|
||||
}
|
||||
|
||||
// check required arguments (file name, port, payload size)
|
||||
// there should only be 3 arguments left to process
|
||||
char *pFileName = NULL;
|
||||
uint32_t port = 0;
|
||||
size_t payloadSize = 0;
|
||||
if ( (argc - current_arg) != NUM_REQ_ARGS )
|
||||
{
|
||||
LOGMSG_ERR("Error parsing input arguments: Received %d, minimum %d -> maximum %d",
|
||||
argc, MIN_NUM_ARGS, MAX_NUM_ARGS)
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
pFileName = argv[current_arg++];
|
||||
port = atoi(argv[current_arg++]);
|
||||
payloadSize = atoi(argv[current_arg]);
|
||||
}
|
||||
|
||||
if ( (payloadSize > MAX_PAYLOAD_SIZE) || (payloadSize == 0) )
|
||||
{
|
||||
LOGMSG_ERR("Error: supplied payload size [%d] must be greater than 0 and not"
|
||||
" more than the maximum possible UDP datagram payload size [%d]",
|
||||
payloadSize, MAX_PAYLOAD_SIZE)
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOGMSG_INFO("Running udp_receiver | Verbose: %s, Buffer checking: %s, Output"
|
||||
" file: %s, Port: %d, Payload size: %d",
|
||||
(Verbose == 1) ? "Yes" : "No", (buffer_check == 1) ? "Yes" : "No",
|
||||
pFileName, port, payloadSize)
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// open the file to write the binary data to
|
||||
// -------------------------------------------------------------------------
|
||||
FILE *pDestFile = NULL;
|
||||
pDestFile = fopen(pFileName, "w");
|
||||
size_t bytes_written = 0;
|
||||
if (pDestFile == NULL)
|
||||
{
|
||||
LOGMSG_ERR("Error: unable to open the requested file %s. Errno: %s [%d]",
|
||||
pFileName, strerror(errno), errno)
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGMSG_DBG("Opened file: %s", pFileName)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// create the socket and bind to the requested port
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// the buffer to read the datagram payload, size set by program argument
|
||||
char udpRecvBuffer[payloadSize];
|
||||
|
||||
// socket and interface information
|
||||
int rx_fd = 0;
|
||||
ssize_t bytes_received = 0;
|
||||
struct sockaddr_in local_addr;
|
||||
socklen_t sock_length = sizeof(struct sockaddr);
|
||||
|
||||
// clear local_addr
|
||||
memset(&local_addr, '\0', sizeof(local_addr));
|
||||
|
||||
// populate information about the remote and the port to listen on
|
||||
local_addr.sin_family = AF_INET;
|
||||
local_addr.sin_port = htons(port);
|
||||
local_addr.sin_addr.s_addr = INADDR_ANY;
|
||||
|
||||
// create socket file descriptor
|
||||
if ( (rx_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 )
|
||||
{
|
||||
LOGMSG_ERR("Error: unable to create UDP listener socket. Errno: %s [%d]",
|
||||
strerror(errno), errno)
|
||||
fclose(pDestFile);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// set the socket REUSEADDR option
|
||||
int option = 1 ;
|
||||
if ( setsockopt(rx_fd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option)) < 0 )
|
||||
{
|
||||
LOGMSG_ERR("Error: unable to set socket options. Errno: %s [%d]",
|
||||
strerror(errno), errno)
|
||||
fclose(pDestFile);
|
||||
close(rx_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// bind the socket to the local address
|
||||
if ( bind(rx_fd, (const struct sockaddr *) &local_addr, sizeof(local_addr)) < 0 )
|
||||
{
|
||||
LOGMSG_ERR("Error: unable to bind to listener socket. Errno: %s [%d]",
|
||||
strerror(errno), errno)
|
||||
fclose(pDestFile);
|
||||
close(rx_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOGMSG_DBG("Socket %d opened for port: %d", rx_fd, port)
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// buffer checking variables for use if it is enabled
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// variables to store buffer stats into
|
||||
uint32_t local_port = 0;
|
||||
uint32_t rx_buffer_used_space = 0;
|
||||
uint32_t prev_rx_buffer_used_space = 0;
|
||||
uint32_t prev_dopped_dgram_count = 0;
|
||||
uint32_t curr_dopped_dgram_count = 0;
|
||||
|
||||
// variables for reading the buffer stats file
|
||||
ssize_t buffer_stats_bytes_read = 0;
|
||||
size_t line_len = 0;
|
||||
char *stat_line = NULL;
|
||||
|
||||
char udp_buffer_stats_path[] = "/proc/net/udp";
|
||||
FILE *pBufStatFile = NULL;
|
||||
|
||||
// check the file can be opened
|
||||
if (buffer_check == 1)
|
||||
{
|
||||
pBufStatFile = fopen(udp_buffer_stats_path, "r");
|
||||
if (pBufStatFile == NULL)
|
||||
{
|
||||
LOGMSG_ERR("Error: unable to open UDP buffer statistics file at %s. Errno: %s [%d]",
|
||||
udp_buffer_stats_path, strerror(errno), errno)
|
||||
buffer_check = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// variables to flag bad buffer states so we don't spam the log
|
||||
int buffer_overflow = 0;
|
||||
int rx_queue_growing = 0;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// main loop
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
LOGMSG_DBG("Capturing UDP datagrams - payload size: %d port: %d output file: %s",
|
||||
payloadSize, port, pFileName)
|
||||
|
||||
while(run == 1)
|
||||
{
|
||||
// non-blocking function to loop until a message appears in the UDP buffer,
|
||||
// then read bytes from the system buffer up to the amount specified by
|
||||
// payloadSize.
|
||||
// non-blocking means that the while-loop can be exited when receiving
|
||||
// a system signal
|
||||
bytes_received = recvfrom(rx_fd, udpRecvBuffer, payloadSize, MSG_DONTWAIT, (struct sockaddr *) &local_addr, &sock_length);
|
||||
|
||||
// we have read in some number of bytes successfully
|
||||
if ( bytes_received > 0 )
|
||||
{
|
||||
LOGMSG_DBG("Successfully received %d bytes (buffer size %d)", bytes_received, payloadSize)
|
||||
|
||||
// attempt to write the captured bytes to file
|
||||
bytes_written = fwrite(udpRecvBuffer, sizeof(char), bytes_received, pDestFile);
|
||||
|
||||
// we have written the data to file successfully
|
||||
if ( bytes_written == ((size_t)bytes_received) )
|
||||
{
|
||||
LOGMSG_DBG("Successfully written %d bytes to file: %s", bytes_written, pFileName)
|
||||
}
|
||||
else // error
|
||||
{
|
||||
LOGMSG_ERR("Error: Failed to write all captured data to the output file %s: Errno: %s [%d] Bytes read: %d Bytes written: %d",
|
||||
pFileName, strerror(errno), errno, bytes_received, bytes_written)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bytes_received == 0)
|
||||
{
|
||||
LOGMSG_WARN("Received 0 byte message")
|
||||
}
|
||||
else if ( (errno != EAGAIN) && (errno != EWOULDBLOCK) ) // error
|
||||
{
|
||||
LOGMSG_ERR("Error: UDP receive function failed: Errno: %s [%d] Bytes read: %d",
|
||||
strerror(errno), errno, bytes_received)
|
||||
}
|
||||
}
|
||||
|
||||
// check the system buffer and report if we are dropping datagrams
|
||||
if (buffer_check == 1)
|
||||
{
|
||||
// need to use freopen as existing lines in the file have changed
|
||||
freopen(udp_buffer_stats_path, "r", pBufStatFile);
|
||||
while( (buffer_stats_bytes_read = getline(&stat_line, &line_len, pBufStatFile)) != -1 )
|
||||
{
|
||||
// onl process the line if it is not the header
|
||||
if ( strstr(stat_line, "sl local_address rem_address") == NULL )
|
||||
{
|
||||
// parse the line and store into variables
|
||||
// %*d allows the fomratting to skip over an element (the * means to not assign to destination)
|
||||
sscanf(stat_line, "%*d: %*X:%4X %*X:%*X %*d %*X:%8X %*d:%*X %*X %*d %*d %*d %*d %*X %d",
|
||||
&local_port, &rx_buffer_used_space, &curr_dopped_dgram_count );
|
||||
|
||||
// check if we have dropped any datagrams on the port we are listening to
|
||||
if (local_port == port)
|
||||
{
|
||||
//TODO: include with a logging interval
|
||||
// LOGMSG_DBG("port: %04X tx_queue: %X rx_queue: %X dropped_datagrams: %d",
|
||||
// local_port, tx_queue, rx_queue, dropped_datagrams);
|
||||
|
||||
// log once if a buffer overflow occurs, until the rx queue is cleared
|
||||
if ( (curr_dopped_dgram_count != prev_dopped_dgram_count)
|
||||
&& (buffer_overflow == 0)
|
||||
)
|
||||
{
|
||||
LOGMSG_ERR("Error: UDP BUFFER OVERFLOW ON PORT %X - Rx Buffer: %X | Dropped datagrams: %d (previously %d)",
|
||||
local_port, rx_buffer_used_space, curr_dopped_dgram_count, prev_dopped_dgram_count)
|
||||
buffer_overflow = 1;
|
||||
}
|
||||
|
||||
// warn if the rx queue is growing (impending overflow) - at high UDP data rates there will likely be
|
||||
// no warning if the system buffer is small as a gigabit link could fill the buffer within a check period
|
||||
if ( (rx_buffer_used_space > prev_rx_buffer_used_space)
|
||||
&& (rx_queue_growing == 0)
|
||||
)
|
||||
{
|
||||
LOGMSG_WARN("UDP RX BUFFER QUEUE GROWING ON PORT %X - Rx Buffer: %X (previously %X) | Dropped datagrams: %d (previously %d)",
|
||||
local_port, rx_buffer_used_space, prev_rx_buffer_used_space, curr_dopped_dgram_count, prev_dopped_dgram_count )
|
||||
rx_queue_growing = 1;
|
||||
}
|
||||
|
||||
// clear the buffer_overflow flag so we can log further unique instances
|
||||
if ( (rx_buffer_used_space == 0)
|
||||
&& ( (rx_queue_growing == 1) || (buffer_overflow == 1) )
|
||||
)
|
||||
{
|
||||
// clear flags as buffer has returned to a nominal state
|
||||
buffer_overflow = 0;
|
||||
rx_queue_growing = 0;
|
||||
|
||||
LOGMSG_INFO("UDP RX Buffer on port %X has returned to nominal conditions - Rx Buffer: %X (previously %X) | Total dropped datagrams: %d",
|
||||
rx_buffer_used_space, prev_rx_buffer_used_space, curr_dopped_dgram_count)
|
||||
}
|
||||
|
||||
// update variables used to detect error conditions
|
||||
prev_dopped_dgram_count = curr_dopped_dgram_count;
|
||||
prev_rx_buffer_used_space = rx_buffer_used_space;
|
||||
|
||||
// we have parsed the line relevant to our port, so we can skip the rest of the file
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// short sleep to not hog resource - 1000 us at 8972 bytes gives a max
|
||||
// throughput of 8972000 Bytes/s (71776000 b/s)
|
||||
// a usleep of 50 us is recommended for gigabit throughput using jumbo
|
||||
// frames: 8972 bytes per 50 us means 179440000 Bytes/s
|
||||
// (1435520000 b/s [1.4 Gbps])
|
||||
// also beware disk usage limitations when attempting gigabit
|
||||
// captures (~178 MBps)
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// clean up
|
||||
// -------------------------------------------------------------------------
|
||||
fclose(pDestFile);
|
||||
fclose(pBufStatFile);
|
||||
close(rx_fd);
|
||||
|
||||
LOGMSG_INFO("\n######################\n\tProgram exit\n######################")
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user