Added logging library and udp2file.
Updated README to reflect.
This commit is contained in:
321
Logging/logging_lib.c
Normal file
321
Logging/logging_lib.c
Normal file
@ -0,0 +1,321 @@
|
||||
//*============================================================================*/
|
||||
// Title: logging.c
|
||||
// Author: RSH
|
||||
// Description:
|
||||
// General purpose logging function with timestamping and choice of output
|
||||
// streams: stout, stderr, or a supplied file (opened in append mode).
|
||||
// TODO:
|
||||
// * Have a logging receive server
|
||||
// * provide iso compliant option
|
||||
// * test output options
|
||||
// * useful print/formatting outputs (lines, tabs, etc.)
|
||||
//TODO: would be nice if logger auto chunked at 80 chars
|
||||
/*============================================================================*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "logging_lib.h"
|
||||
|
||||
|
||||
// static const int tsBufLen = strlen("2025-01-01T01:50:01.234567890Z") + 1 ;
|
||||
// static const int tsTmpLen = strlen("2025-01-01T01:50:01.") + 1 ;
|
||||
|
||||
// #define LOG_MSG getTimestamp(tsBuf); snprintf(msgBuffer, tsBufLen+2, "%s: ", tsBuf); sprintf(msgBuffer + tsBufLen +1,
|
||||
// #define RP )
|
||||
|
||||
// static int getTimestamp(char * tsBuf)
|
||||
// {
|
||||
// struct timespec now;
|
||||
// struct tm tm;
|
||||
// int retval = clock_gettime(CLOCK_REALTIME, &now);
|
||||
// gmtime_r(&now.tv_sec, &tm);
|
||||
// strftime(tsBuf, tsTmpLen, "%Y-%m-%dT%H:%M:%S.", &tm);
|
||||
// sprintf(tsBuf + tsTmpLen -1, "%09luZ", now.tv_nsec);
|
||||
// return retval;
|
||||
// }
|
||||
|
||||
|
||||
// Initialise buffers for the different severity strings
|
||||
static char logDbg[] = DEBUG_PREFIX;
|
||||
static char logInfo[] = INFO_PREFIX;
|
||||
static char logWarn[] = WARN_PREFIX;
|
||||
static char logErr[] = ERR_PREFIX;
|
||||
|
||||
static char logInfoColour[] = COLOUR_INFO_PREFIX;
|
||||
static char logWarnColour[] = COLOUR_WARN_PREFIX;
|
||||
static char logErrColour[] = COLOUR_ERR_PREFIX;
|
||||
|
||||
// Constants for the lenght of various elements of the timestamp and function ID
|
||||
static const int tsTmpLen = strlen(STD_TIMESTAMP_SEC)+1;
|
||||
static const int tsBufLen = strlen(STD_TIMESTAMP_WHOLE)+1;
|
||||
static const int tsFunLen = strlen(FUNC_STR);
|
||||
|
||||
|
||||
static char logFileName[MAX_LOG_FILE_NAME_LEN] = "";
|
||||
static FILE *logOutput = NULL;
|
||||
static T_LogOutput outputState = notSet;
|
||||
static int envNoColour = 0;
|
||||
|
||||
/*============================================================================*/
|
||||
/* Logging_CheckInited */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
// This checks that the library has been inited and is called by other
|
||||
// logging functions - throws a warning and inits the library to stdout if it
|
||||
// has not already been initialised.
|
||||
/*----------------------------------------------------------------------------*/
|
||||
static void Logging_CheckInited(char *timestamp, const char *caller)
|
||||
{
|
||||
if ( (logOutput == NULL) || (outputState == notSet) )
|
||||
{
|
||||
logOutput = stdout;
|
||||
outputState = standardOut;
|
||||
fprintf(logOutput
|
||||
, "%s%sLogging function %s() called without logging library being initialised >>>> defaulting to stdout\n"
|
||||
, timestamp, logWarn, caller);
|
||||
}
|
||||
}
|
||||
/*============================================================================*/
|
||||
|
||||
/*============================================================================*/
|
||||
/* Logging_LogMsgTS */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
// Example intended usage:
|
||||
// #define LOG_SOURCE "FileName"
|
||||
// #define ME someFunction
|
||||
// int Verbose = 1 // could be set at runtime via arg (e.g. ./prog -v)
|
||||
// void someFunc (void)
|
||||
// {
|
||||
// Logging_LogMsgTS(LOG_SOURCE, ME, LOG_DEBUG, "This is some noisy info");
|
||||
// }
|
||||
// #undef
|
||||
// Output:
|
||||
// [ 2025-06-07 02:12:02.005 ] (DEBUG) | FileName in function: someFunction(): This is some noisy info
|
||||
//
|
||||
//
|
||||
// OR without custom defines:
|
||||
// void someFunc (void)
|
||||
// {
|
||||
// Logging_LogMsgTS(__FILE__, __func__, LOG_DEBUG, "This is some noisy info");
|
||||
// }
|
||||
// Output:
|
||||
// [ 2025-06-07 02:12:02.005 ] (DEBUG) | logTest.c in function: someFunc(): This is some noisy info
|
||||
/*----------------------------------------------------------------------------*/
|
||||
void Logging_LogMsgTS( const char * where
|
||||
, const char * caller
|
||||
, int severity
|
||||
, const char * message, ... )
|
||||
{
|
||||
char buffer[MAX_LOG_BUF_LEN];
|
||||
int callerLen = strlen(where) + strlen(caller) + tsFunLen;
|
||||
char *severityMsg = NULL;
|
||||
int severityLen = 0;
|
||||
int printfColour = 0;
|
||||
|
||||
va_list args;
|
||||
va_start(args, message);
|
||||
|
||||
// first get the current time and insert at the front of the buffer
|
||||
struct timespec now;
|
||||
struct tm tm;
|
||||
if ( clock_gettime(CLOCK_REALTIME, &now) < 0)
|
||||
{
|
||||
// Output the error to the file if used
|
||||
if(outputState == logFile)
|
||||
{
|
||||
LOGMSG_N_ERR("Logging", "Logging_LogMsgTS", "Unable to get system time [errno: %d (%s)]"
|
||||
, errno, strerror(errno))
|
||||
}
|
||||
// Put error on stderr as well
|
||||
fprintf(logOutput
|
||||
, "%sLogging function Logging_LogMsgTS(): Unable to get system time [errno: %d (%s)]\n"
|
||||
, (envNoColour == 1) ? logErr : logErrColour , errno, strerror(errno));
|
||||
return;
|
||||
}
|
||||
gmtime_r(&now.tv_sec, &tm);
|
||||
strftime(buffer, tsTmpLen, "[ %Y-%m-%d %H:%M:%S.", &tm);
|
||||
sprintf( (buffer + tsTmpLen -1), "%03lu ] ", (now.tv_nsec/1000000) % 1000000);
|
||||
|
||||
|
||||
// check if the function has been called (making use of the pre-assembled timestamp)
|
||||
Logging_CheckInited(buffer, __func__);
|
||||
|
||||
// determine which string to use for the severity print out based on message
|
||||
// severity level, output stream and whether the environment has NO_COLOR
|
||||
// set [see Logging_Init()]
|
||||
printfColour = ( (envNoColour == 1) || (outputState == logFile) ) ? NO_COLOUR : PRINT_COLOUR;
|
||||
switch (severity)
|
||||
{
|
||||
case 0:
|
||||
severityMsg = logDbg;
|
||||
break;
|
||||
case 1:
|
||||
severityMsg = (printfColour == NO_COLOUR) ? logInfo : logInfoColour;
|
||||
break;
|
||||
case 2:
|
||||
severityMsg = (printfColour == NO_COLOUR) ? logWarn : logWarnColour;
|
||||
break;
|
||||
case 3:
|
||||
severityMsg = (printfColour == NO_COLOUR) ? logErr : logErrColour;
|
||||
break;
|
||||
default:
|
||||
severityMsg = (printfColour == NO_COLOUR) ? logInfo : logInfoColour;
|
||||
}
|
||||
severityLen = strlen(severityMsg);
|
||||
|
||||
// add the severity to the buffer
|
||||
sprintf( (buffer + tsBufLen), "%s", severityMsg);
|
||||
// then add the process/file name and function name to the output buffer
|
||||
sprintf( (buffer + tsBufLen + severityLen), "%s in function: %s(): ", where, caller);
|
||||
// finally add the supplied message and format with args
|
||||
vsprintf( (buffer + tsBufLen + severityLen + callerLen), message, args );
|
||||
// print the message to the output
|
||||
fprintf(logOutput, "%s\n", buffer);
|
||||
va_end(args);
|
||||
}
|
||||
/*============================================================================*/
|
||||
|
||||
/*============================================================================*/
|
||||
/* Logging_LogMsg*/
|
||||
/*----------------------------------------------------------------------------*/
|
||||
// Like Logging_LogMsgTS above, but does not insert a timestamp
|
||||
/*----------------------------------------------------------------------------*/
|
||||
void Logging_LogMsg( const char * where
|
||||
, const char * caller
|
||||
, int severity
|
||||
, const char * message, ... )
|
||||
{
|
||||
char buffer[MAX_LOG_BUF_LEN];
|
||||
int callerLen = strlen(where) + strlen(caller) + tsFunLen;
|
||||
char * severityMsg = NULL;
|
||||
int severityLen = 0;
|
||||
int printfColour = 0;
|
||||
|
||||
va_list args;
|
||||
va_start(args, message);
|
||||
|
||||
// check if the function has been called (making use of the pre-assembled timestamp)
|
||||
Logging_CheckInited(NULL, __func__);
|
||||
|
||||
// determine which string to use for the severity print out based on message
|
||||
// severity level, output stream and whether the environment has NO_COLOR
|
||||
// set [see Logging_Init()]
|
||||
printfColour = ( (envNoColour == 1) || (outputState == logFile) ) ? NO_COLOUR : PRINT_COLOUR;
|
||||
switch (severity)
|
||||
{
|
||||
case 0:
|
||||
severityMsg = logDbg;
|
||||
break;
|
||||
case 1:
|
||||
severityMsg = (printfColour == NO_COLOUR) ? logInfo : logInfoColour;
|
||||
break;
|
||||
case 2:
|
||||
severityMsg = (printfColour == NO_COLOUR) ? logWarn : logWarnColour;
|
||||
break;
|
||||
case 3:
|
||||
severityMsg = (printfColour == NO_COLOUR) ? logErr : logErrColour;
|
||||
break;
|
||||
default:
|
||||
severityMsg = (printfColour == NO_COLOUR) ? logInfo : logInfoColour;
|
||||
}
|
||||
severityLen = strlen(severityMsg);
|
||||
|
||||
// add the severity to the buffer
|
||||
sprintf( buffer, "%s", severityMsg);
|
||||
// then add the process/file name and function name to the output buffer
|
||||
sprintf( (buffer + severityLen), "%s in function: %s(): ", where, caller);
|
||||
// finally add the supplied message and format with args
|
||||
vsprintf( (buffer + severityLen + callerLen), message, args );
|
||||
// print the message to the output
|
||||
fprintf(logOutput, "%s\n", buffer);
|
||||
va_end(args);
|
||||
}
|
||||
/*============================================================================*/
|
||||
|
||||
/*============================================================================*/
|
||||
/* Logging_Init */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
// It is not necessary to call Logging_Init, but it is strongly recommended.
|
||||
// If it is not called, the first call to a logging function will set the
|
||||
// output to stdout as default and throw a warning.
|
||||
/*----------------------------------------------------------------------------*/
|
||||
#define ME "Logging_Init"
|
||||
int Logging_Init(T_LogOutput output, char * fileName)
|
||||
{
|
||||
// check if the ENV variable NO_COLOR is set (https://no-color.org/)
|
||||
char *colorEnv = getenv("NO_COLOR");
|
||||
if( (colorEnv != NULL) && (colorEnv[0] != '\0') )
|
||||
{
|
||||
envNoColour = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
envNoColour = 0;
|
||||
}
|
||||
|
||||
// if a file name has been supplied, attempt to open it,
|
||||
// else check if the user wants the output to be
|
||||
// stderr - otherwise the default output type is stdout
|
||||
if (fileName != NULL)
|
||||
{
|
||||
logOutput = fopen(fileName, "a");
|
||||
if (logOutput == NULL)
|
||||
{
|
||||
logOutput = stdout;
|
||||
Logging_LogMsg( __FILE__, ME, LOG_ERR,
|
||||
"Can't open file: %s [errno: %d (%s)] >>>>>>> defaulting to stdout"
|
||||
, fileName, errno, strerror(errno) );
|
||||
outputState = standardOut;
|
||||
//return a fail in case the caller wants to handle (retry, terminate, etc.)
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
//logOuput already set by successful fopen()
|
||||
outputState = logFile;
|
||||
memcpy(logFileName, fileName, (strlen(fileName) * sizeof(char)));
|
||||
Logging_LogMsgTS(__FILE__, ME, LOG_INFO, "Set log output to file: %s", logFileName);
|
||||
}
|
||||
}
|
||||
else if (output == standardErr)
|
||||
{
|
||||
logOutput = stderr;
|
||||
outputState = standardErr;
|
||||
Logging_LogMsgTS(__FILE__, ME, LOG_INFO, "Set log output to stderr");
|
||||
}
|
||||
else
|
||||
{
|
||||
logOutput = stdout;
|
||||
outputState = standardOut;
|
||||
Logging_LogMsgTS(__FILE__, ME, LOG_INFO, "Set log output to stdout");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#undef ME
|
||||
/*============================================================================*/
|
||||
/*============================================================================*/
|
||||
/* Logging_Term */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
// If Logging_Init was not called prior, or the output stream is set to stdout
|
||||
// or stderr, then there is strictly no need to call Logging_Term though it is
|
||||
// strongly recommended.
|
||||
// It is necessary to call Logging_Term if the logging output is set to a file!
|
||||
/*----------------------------------------------------------------------------*/
|
||||
#define ME "Logging_Term"
|
||||
void Logging_Term( void )
|
||||
{
|
||||
if (outputState == logFile)
|
||||
{
|
||||
fclose(logOutput);
|
||||
memset(logFileName, '\0', sizeof(logFileName));
|
||||
}
|
||||
logOutput = NULL;
|
||||
outputState = notSet;
|
||||
}
|
||||
#undef ME
|
||||
/*============================================================================*/
|
||||
118
Logging/logging_lib.h
Normal file
118
Logging/logging_lib.h
Normal file
@ -0,0 +1,118 @@
|
||||
#ifndef LOGGING_H
|
||||
#define LOGGING_H
|
||||
|
||||
#define MAX_LOG_BUF_LEN 1024
|
||||
#define MAX_LOG_FILE_NAME_LEN 400
|
||||
|
||||
// define severity levels for log message highlighting
|
||||
#define LOG_DEBUG 0
|
||||
#define LOG_INFO 1
|
||||
#define LOG_WARN 2
|
||||
#define LOG_ERR 3
|
||||
|
||||
// Macros for verbosity level
|
||||
#define LOG_VERBOSE 1
|
||||
#define LOG_QUIET 0
|
||||
|
||||
// Macros for colour console printing
|
||||
#define PRINT_COLOUR 1
|
||||
#define NO_COLOUR 0
|
||||
|
||||
#define DEBUG_PREFIX "(DEBUG) | "
|
||||
#define INFO_PREFIX "(INFO) | "
|
||||
#define WARN_PREFIX "(WARNING) | "
|
||||
#define ERR_PREFIX "(ERROR) | "
|
||||
|
||||
// \x1b[31m ERROR \x1b[0m : \x1b[31m sets string to red, we then print 'ERROR', then reset terminal so it doesn stay red!
|
||||
// ANSI escape code colouring for highlighting different messages
|
||||
#define COLOUR_INFO_PREFIX "(\x1b[36mINFO\x1b[0m) | "
|
||||
#define COLOUR_WARN_PREFIX "(\x1b[33mWARNING\x1b[0m) | "
|
||||
#define COLOUR_ERR_PREFIX "(\x1b[31mERROR\x1b[0m) | "
|
||||
|
||||
// Define the timstamp format strings and function caller ID string
|
||||
#define STD_TIMESTAMP_SEC "[ 2025-06-04 12:52:11."
|
||||
#define STD_TIMESTAMP_WHOLE "[ 2025-06-04 12:52:11.999 ]"
|
||||
#define FUNC_STR " in function: (): "
|
||||
|
||||
// Enum for use in determining current output stream
|
||||
typedef enum {
|
||||
notSet = 0,
|
||||
standardOut = 1,
|
||||
standardErr = 2,
|
||||
logFile = 3
|
||||
}T_LogOutput;
|
||||
|
||||
//*============================================================================*/
|
||||
// Macros for initialisation function
|
||||
//*----------------------------------------------------------------------------*/
|
||||
#define LOGGING_INIT_STDOUT Logging_Init(standardOut, NULL);
|
||||
|
||||
#define LOGGING_INIT_STDERR Logging_Init(standardErr, NULL);
|
||||
|
||||
#define LOGGING_INIT_FILE(FileName) Logging_Init(logFile, FileName);
|
||||
/*============================================================================*/
|
||||
|
||||
/*============================================================================*/
|
||||
// Useful variadic macros for logging functions - see https://gcc.gnu.org/onlinedocs/gcc-15.1.0/gcc/Variadic-Macros.html
|
||||
//*----------------------------------------------------------------------------*/
|
||||
// Timestamped logging with file and func preprocessors used to define 'where' and 'caller'
|
||||
//*----------------------------------------------------------------------------*/
|
||||
#define LOGMSG_TS_DBG(message, ...) if(Verbose == LOG_VERBOSE) { Logging_LogMsgTS(__FILE__, __func__, LOG_DEBUG, message, ##__VA_ARGS__);}
|
||||
|
||||
#define LOGMSG_TS_INFO(message, ...) Logging_LogMsgTS(__FILE__, __func__, LOG_INFO, message, ##__VA_ARGS__);
|
||||
|
||||
#define LOGMSG_TS_WARN(message, ...) Logging_LogMsgTS(__FILE__, __func__, LOG_WARN, message, ##__VA_ARGS__);
|
||||
|
||||
#define LOGMSG_TS_ERR(message, ...) Logging_LogMsgTS(__FILE__, __func__, LOG_ERR, message, ##__VA_ARGS__);
|
||||
//*----------------------------------------------------------------------------*/
|
||||
// Basic logging with file and func preprocessors used to define 'where' and 'caller'
|
||||
//*----------------------------------------------------------------------------*/
|
||||
#define LOGMSG_DBG(message, ...) if(Verbose == LOG_VERBOSE) { Logging_LogMsg(__FILE__, __func__, LOG_DEBUG, message, ##__VA_ARGS__);}
|
||||
|
||||
#define LOGMSG_INFO(message, ...) Logging_LogMsg(__FILE__, __func__, LOG_INFO, message, ##__VA_ARGS__);
|
||||
|
||||
#define LOGMSG_WARN(message, ...) Logging_LogMsg(__FILE__, __func__, LOG_WARN, message, ##__VA_ARGS__);
|
||||
|
||||
#define LOGMSG_ERR(message, ...) Logging_LogMsg(__FILE__, __func__, LOG_ERR, message, ##__VA_ARGS__);
|
||||
//*----------------------------------------------------------------------------*/
|
||||
// Timestamped logging with arguments used to define 'where' and 'caller' (N = name)
|
||||
//*----------------------------------------------------------------------------*/
|
||||
#define LOGMSG_TS_N_DBG(what, who, message, ...) if(Verbose == LOG_VERBOSE) {Logging_LogMsgTS(what, who, LOG_DEBUG, message, ##__VA_ARGS__);}
|
||||
|
||||
#define LOGMSG_TS_N_INFO(what, who, message, ...) Logging_LogMsgTS(what, who, LOG_INFO, message, ##__VA_ARGS__);
|
||||
|
||||
#define LOGMSG_TS_N_WARN(what, who, message, ...) Logging_LogMsgTS(what, who, LOG_WARN, message, ##__VA_ARGS__);
|
||||
|
||||
#define LOGMSG_TS_N_ERR(what, who, message, ...) Logging_LogMsgTS(what, who, LOG_ERR, message, ##__VA_ARGS__);
|
||||
//*----------------------------------------------------------------------------*/
|
||||
// Basic logging with arguments used to define 'where' and 'caller' (N = name)
|
||||
//*----------------------------------------------------------------------------*/
|
||||
#define LOGMSG_N_DBG(what, who, message, ...) if(Verbose == LOG_VERBOSE) { Logging_LogMsg(what, who, LOG_DEBUG, message, ##__VA_ARGS__);}
|
||||
|
||||
#define LOGMSG_N_INFO(what, who, message, ...) Logging_LogMsg(what, who, LOG_INFO, message, ##__VA_ARGS__);
|
||||
|
||||
#define LOGMSG_N_WARN(what, who, message, ...) Logging_LogMsg(what, who, LOG_WARN, message, ##__VA_ARGS__);
|
||||
|
||||
#define LOGMSG_N_ERR(what, who, message, ...) Logging_LogMsg(what, who, LOG_ERR, message, ##__VA_ARGS__);
|
||||
//*============================================================================*/
|
||||
|
||||
//*============================================================================*/
|
||||
// Prototypes for logging functions
|
||||
//*----------------------------------------------------------------------------*/
|
||||
|
||||
void Logging_LogMsgTS( const char * where
|
||||
, const char * caller
|
||||
, int severity
|
||||
, const char * message, ... );
|
||||
|
||||
void Logging_LogMsg( const char * where
|
||||
, const char * caller
|
||||
, int severity
|
||||
, const char * message, ... );
|
||||
|
||||
int Logging_Init(T_LogOutput output, char * fileName);
|
||||
|
||||
void Logging_Term( void );
|
||||
//*============================================================================*/
|
||||
|
||||
#endif //LOGGING_H
|
||||
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;
|
||||
}
|
||||
10
README.md
10
README.md
@ -1,3 +1,13 @@
|
||||
# Utilities
|
||||
|
||||
Collection of C programs to perform various utilities and provide examples.
|
||||
Compilation instructions are given at the top of each source file, or in a README if a Makefile is used.
|
||||
|
||||
## Logging
|
||||
|
||||
A general purpose logging library with optional timestamping and terminal colouring for log message severity.
|
||||
This is used by most applications I build, certainly all within this repository.
|
||||
|
||||
## Networking
|
||||
|
||||
Contains a standard UDP listener implementation that writes to a file.
|
||||
Reference in New Issue
Block a user