* Wrapped in application object * Getting config file name from CLI args * Getting input and output filenams from CLI args * Reading and writing from STDIN & STDOUT * Send all non-data output to stderrmaster
@@ -1,5 +1,5 @@ | |||||
iptraffic: iptraffic.cpp strutil.o data.o config.o | |||||
g++ -o $@ $@.cpp strutil.o data.o config.o | |||||
iptraffic: iptraffic.cpp strutil.o data.o config.o cli.o | |||||
g++ -o $@ $@.cpp strutil.o data.o config.o cli.o | |||||
config.o: config.cpp config.h strutil.o data.o | config.o: config.cpp config.h strutil.o data.o | ||||
g++ -c -o $@ config.cpp | g++ -c -o $@ config.cpp | ||||
@@ -7,6 +7,9 @@ config.o: config.cpp config.h strutil.o data.o | |||||
data.o: data.cpp data.h strutil.o | data.o: data.cpp data.h strutil.o | ||||
g++ -c -o $@ data.cpp | g++ -c -o $@ data.cpp | ||||
cli.o: cli.cpp cli.h | |||||
g++ -c -o $@ cli.cpp | |||||
strutil.o: strutil.cpp strutil.h | strutil.o: strutil.cpp strutil.h | ||||
g++ -c -o $@ strutil.cpp | g++ -c -o $@ strutil.cpp | ||||
@@ -14,7 +17,7 @@ strutil.o: strutil.cpp strutil.h | |||||
.PHONY: run | .PHONY: run | ||||
run: iptraffic | run: iptraffic | ||||
./iptraffic 2> log # 2>&1 | head -n 20 | |||||
./iptraffic | |||||
@@ -0,0 +1,74 @@ | |||||
/********************************************************************* | |||||
* C++ Base Application Object | |||||
* Written by Jonathan A. Foster <jon@jfpossibilities.com | |||||
* Started August 31st, 2020 | |||||
*********************************************************************/ | |||||
//#include <ostream> | |||||
#include <iostream> | |||||
#include <cstdlib> | |||||
#include <sys/stat.h> | |||||
#include <sys/types.h> | |||||
#include "cli.h" | |||||
/********************************************************************* | |||||
* cBaseApp | |||||
*********************************************************************/ | |||||
cBaseApp *baseapp = 0; | |||||
cBaseApp &cBaseApp::init(int argc, char **argv) { | |||||
command_argc = argc; | |||||
command_args = argv; | |||||
} | |||||
int cBaseApp::main() { | |||||
int i, ct; | |||||
char *p; | |||||
bool switches = true; | |||||
for(i=1; i<command_argc; i++) { | |||||
if(switches && *command_args[i]=='-') { // switches! | |||||
ct=0; | |||||
if(command_args[i][1]=='-') { // long switches | |||||
if(command_args[i][2]==0) { // -- | |||||
switches=false; | |||||
continue; | |||||
} | |||||
p = command_args[i]+2; | |||||
ct = do_switch(p); | |||||
} else { // short switches | |||||
for(p=command_args[i]+1; *p; p++) { | |||||
if(ct) throw std::runtime_error( | |||||
"Only the last switch in stacked short switches can require an argument" | |||||
); | |||||
ct = do_switch(p); | |||||
} | |||||
p--; | |||||
} | |||||
// switch arguments | |||||
while(ct--) { | |||||
if(++i>=command_argc) throw std::runtime_error( | |||||
"Last switch requires more arguments" | |||||
); | |||||
do_switch_arg(p, command_args[i]); | |||||
} | |||||
} else // non-switch arguments | |||||
do_arg(command_args[i]); | |||||
} | |||||
return ExitCode; | |||||
} | |||||
int cBaseApp::crash(const std::exception &e) { | |||||
std::cerr << "Application crashed: " << e.what() << std::endl; | |||||
return 216; // just a weird number hopefully not conflicting with anything else. | |||||
} |
@@ -0,0 +1,179 @@ | |||||
////////////////////////////////////////////////////////////////////// | |||||
// C++ Base Application Object | |||||
// Written by Jonathan A. Foster <jon@jfpossibilities.com | |||||
// Started August 31st, 2020 | |||||
// | |||||
// This is a base application object for traditional C/C++ | |||||
// applications with a "int main(int argc, char//argv)" function. | |||||
////////////////////////////////////////////////////////////////////// | |||||
#ifndef __IDS_CLI_H__ | |||||
#define __IDS_CLI_H__ | |||||
#include <string> | |||||
#include <ostream> | |||||
#include <stdexcept> | |||||
////////////////////////////////////////////////////////////////////// | |||||
// Busy indicator aka. "Live Bug" | |||||
// | |||||
// This makes it stupid simple to step through a string that contains | |||||
// a single character per "frame". You can either call next() to get | |||||
// the next char in a sequence or use << with an ostream. | |||||
////////////////////////////////////////////////////////////////////// | |||||
struct LiveBug { | |||||
std::string seq; | |||||
int p; | |||||
LiveBug(): seq("|/-\\"), p(0) {} | |||||
inline char next() { if(p>=seq.size()) p=0; return seq[p++]; } | |||||
}; | |||||
inline std::ostream &operator<<(std::ostream &o, LiveBug &bug) { | |||||
return o << bug.next(); | |||||
} | |||||
////////////////////////////////////////////////////////////////////// | |||||
// Base Unix Application | |||||
// | |||||
// Its apparent to me tha all applications should be objects. Therefor | |||||
// running an app should consist of setting up an object from a class, | |||||
// running something on it. And then tearing it back down. With that | |||||
// in mind the intent of this class is to provide the boiler plate | |||||
// needed for this model and the bridge from the typical C/C++ main(). | |||||
// | |||||
// How to use: | |||||
// 1. Create a sublcass of this class with your app's functionality. | |||||
// 2. Override do_switch(), do_switch_arg() and do_arg() to process | |||||
// arguments passed from the OS. See main() for the built-in | |||||
// processing. | |||||
// 3. Override main to provide functionality not triggered by args, | |||||
// including setup, teardown or even to replace the argument | |||||
// processing. Don't call the inherited method if you don't need | |||||
// the argument handling. | |||||
// 4. override crash() if you want to do something more than dump | |||||
// the exception string to cerr. | |||||
// 5. Tell the compiler about your class with the MAIN(class) | |||||
// macro. | |||||
// | |||||
// I'm calling this a "Unix" application because its what developed | |||||
// the current main() interface concept. This is also emulated / | |||||
// provided on other platforms, but it started in Unix. | |||||
////////////////////////////////////////////////////////////////////// | |||||
// globally accessible pointer to allow polymorphism | |||||
struct cBaseApp; extern cBaseApp *baseapp; | |||||
struct cBaseApp { | |||||
int command_argc; // main(argc ...) | |||||
char **command_args; // main(... argv) | |||||
int ExitCode; // App's code returned to OS. | |||||
/// STARTUP /// | |||||
//cBaseApp(); | |||||
cBaseApp(): | |||||
command_argc(0), | |||||
command_args(0), | |||||
ExitCode(0) | |||||
{ if(!baseapp) baseapp = this; } // Only the first gets registered | |||||
/// Initialize /// | |||||
// | |||||
// This is called, prior to this->main(), with the argc & args from the C | |||||
// main() entry point. Override this to perform any initialization here that | |||||
// can't be done before the CLI args are known. | |||||
// | |||||
virtual cBaseApp &init(int argc, char **argv); | |||||
/// argument handlers /// | |||||
// | |||||
// Override these routines to implement argument processing. These are char// | |||||
// instead of string since this is how they come to us from the OS. | |||||
// | |||||
// how many args needed for val | |||||
virtual unsigned do_switch(const char *arg) { return 0; } | |||||
// proccess a val for switch | |||||
virtual void do_switch_arg(const char *sw, const std::string &val) { } | |||||
// process a non-switch arg. | |||||
virtual void do_arg(const char *arg) { } | |||||
// TODO: switch lookup methods ("has switch?", "what's switch val?", ...) | |||||
/// new main() - loop through args /// | |||||
// | |||||
// This implementation runs through the arguments passed in using the usual | |||||
// GNU semantics. It calls do_switch() and do_arg(), depending on the | |||||
// presence of a leading "-", passing the current argument (without leading | |||||
// dash(es)). if do_switch() returns > 0 then that many arguments are passed | |||||
// to do_switch_val() with the corresponding switch that triggered the | |||||
// calls. Typically do_switch() would only return 0 or 1. | |||||
// | |||||
// Switches are defined as arguments starting with one or two dashes. A | |||||
// single "--" switch will terminate switch processing, sending all | |||||
// remaining args to do_arg(). Switches with names longer than one char must | |||||
// be preceded with two dashes ("--" long switches). | |||||
// | |||||
// A switch starting with a single dash is a short switch, consisting of a | |||||
// single character. More than one character can follow a dash in which case | |||||
// they will be treated as a series of single char switches | |||||
// (Ex: -abc = -a -b -c). Only the last one is allowed to have an argument | |||||
// (do_switch()>0). | |||||
// | |||||
// This should be overridden to provide any functionality not directly | |||||
// triggered by switches or args and its functionality can be replaced | |||||
// completely if you don't need arguments. | |||||
// | |||||
virtual int main(); | |||||
/// Catch exceptions /// | |||||
// | |||||
// This is called by the boiler plate main() (see bottom) when an excpetion | |||||
// falls out. This is meant to handle std::exception descendents. You can | |||||
// catch other odd bals in main() above and deal with then accordingly. But | |||||
// good style dictates all exceptions should descend from std::exception. | |||||
// The return value is the application's exit code. | |||||
// | |||||
virtual int crash(const std::exception &e); | |||||
/// virtualize destructor /// | |||||
virtual ~cBaseApp() { } | |||||
}; | |||||
////////////////////////////////////////////////////////////////////// | |||||
// An exception class to signal CLI errors | |||||
////////////////////////////////////////////////////////////////////// | |||||
struct CLIerror: public std::runtime_error { | |||||
CLIerror(const std::string &s): runtime_error(s) {} | |||||
}; | |||||
////////////////////////////////////////////////////////////////////// | |||||
// Macro to bridge the C/C++ main() to the app class' main() | |||||
// | |||||
// This sets up a global static variable named "app" that contains | |||||
// your application object. It then copies the CLI args into it and | |||||
// calls main. An std::exception handler is setup to pass exceptions | |||||
// to the "app" object for further handling prior to exiting the app. | |||||
// The app's exit code is returned from app.main() or app.crash(). | |||||
////////////////////////////////////////////////////////////////////// | |||||
#define MAIN(__APPCLASS__) \ | |||||
__APPCLASS__ app; \ | |||||
int main(int argc, char **args) { \ | |||||
try { \ | |||||
return app.init(argc, args).main();\ | |||||
} catch(const std::exception &e) { \ | |||||
return app.crash(e); \ | |||||
} \ | |||||
} | |||||
#endif |
@@ -13,11 +13,19 @@ | |||||
// order. This is the usual way things get logged. ;-) | // order. This is the usual way things get logged. ;-) | ||||
// | // | ||||
// 2021-05-14 <ChipMaster@YeOlPiShack.net> | // 2021-05-14 <ChipMaster@YeOlPiShack.net> | ||||
// Dumbed down for C++ < 11. | |||||
// Dumbed down for < C++11. | |||||
// Split into modules | // Split into modules | ||||
// | |||||
// 2021-06-18 <ChipMaster@YeOlPiShack.net> | |||||
// Wrapped in application object and added command line handling. | |||||
// This includes: | |||||
// - Getting config file name from CLI args | |||||
// - Getting input and output filenams from CLI args | |||||
// - Reading and writing from STDIN & STDOUT | |||||
// - Send all non-data output to stderr | |||||
////////////////////////////////////////////////////////////////////// | ////////////////////////////////////////////////////////////////////// | ||||
// TODO: map names according to time and host. time is probably automatic | |||||
// TODO: map names according to time and requesting host. time is probably automatic | |||||
#include <string.h> | #include <string.h> | ||||
#include <string> | #include <string> | ||||
@@ -26,6 +34,7 @@ | |||||
#include <stdexcept> | #include <stdexcept> | ||||
#include <vector> | #include <vector> | ||||
#include <map> | #include <map> | ||||
#include "cli.h" | |||||
#include "strutil.h" | #include "strutil.h" | ||||
#include "data.h" | #include "data.h" | ||||
#include "config.h" | #include "config.h" | ||||
@@ -34,105 +43,175 @@ using namespace std; | |||||
////////////////////////////////////////////////////////////////////// | ////////////////////////////////////////////////////////////////////// | ||||
// Busy indicator aka. "Live Bug" | |||||
// Application class to process files. | |||||
////////////////////////////////////////////////////////////////////// | ////////////////////////////////////////////////////////////////////// | ||||
//#define DEBUG | |||||
struct LiveBug { | |||||
string seq; | |||||
char pre; | |||||
int p; | |||||
LiveBug(): seq("-\\|/"), pre('\r'), p(0) {} | |||||
inline char next() { if(p>=seq.size()) p=0; return seq[p++]; } | |||||
}; | |||||
ostream &operator<<(ostream &o, LiveBug &bug) { | |||||
return o << bug.pre << bug.next(); | |||||
} | |||||
struct IPtraffic: public cBaseApp { | |||||
Config config; | |||||
StringList dns_ignore, dns_del; | |||||
NameVal rdns; // Reverse DNS lookup cache | |||||
istream *log; | |||||
ostream *out; | |||||
LiveBug bug; | |||||
int line_no; | |||||
////////////////////////////////////////////////////////////////////// | |||||
// Roll through file | |||||
////////////////////////////////////////////////////////////////////// | |||||
//#define DEBUG | |||||
IPtraffic(): out(&cout), log(0) | |||||
{ // I'd rather this initialization be static... | |||||
dns_ignore.push_back("v=spf1"); | |||||
dns_ignore.push_back("https:"); | |||||
dns_del.push_back("NODATA-"); | |||||
dns_del.push_back("NXDOMAIN-"); | |||||
} | |||||
~IPtraffic() { if(log && log!=&cin) delete(log); } | |||||
// (char *) to silence warnings... | |||||
const char *dns_ignore[] = { (char *)"v=spf1", (char *)"https:", 0 }; | |||||
const char *dns_del[] = { (char *)"NODATA-", (char *)"NXDOMAIN-", 0 }; | |||||
#define PATH "/srv/backups/iptraffic" | |||||
ifstream log(PATH "/test.log"); | |||||
ofstream out(PATH "/processed.log"); | |||||
Splits ln; | |||||
int lnno = 0, ict = 0; | |||||
LiveBug bug; | |||||
NameVal rdns; | |||||
NameVal::iterator nvp; | |||||
string name, address, s; | |||||
Conn conn; | |||||
bool match; | |||||
Config config; | |||||
void dlog(const string msg) { | |||||
void dlog(const string msg) { | |||||
#ifdef DEBUG | #ifdef DEBUG | ||||
cerr << "\r" << lnno << ": " << msg << endl; | |||||
cerr << line_no << ": " << msg << endl; | |||||
#endif | #endif | ||||
} | |||||
} | |||||
// TODO: elaborate | |||||
void help() { | |||||
cerr << | |||||
"\n" | |||||
"iptraffic -c {config file} [-o {output file}] [{input file} [...]]\n"; | |||||
ExitCode = 1; | |||||
} | |||||
unsigned do_switch(const char *sw) { | |||||
if((sw[0]=='c' || sw[0]=='o') && sw[1]==0) return 1; | |||||
throw CLIerror("Unrecognized Switch"); | |||||
} | |||||
void do_switch_arg(const char *sw, const std::string &val) { | |||||
switch(*sw) { | |||||
case 'c': | |||||
config.load(val); | |||||
if(!config.us.size()) throw CLIerror( | |||||
"The configuration files MUST contain an [us] section with " | |||||
"appropriate values" | |||||
); | |||||
break; | |||||
case 'o': | |||||
if(out!=&cout) | |||||
throw CLIerror("Output file has already been specified"); | |||||
out = new ofstream(val.c_str()); // c_str(), really?!?! | |||||
} | |||||
} | |||||
void do_arg(const char *fname) { | |||||
if(log && log!=&cin) delete(log); | |||||
log = 0; | |||||
log = new ifstream(fname); | |||||
ExitCode = do_log(); | |||||
} | |||||
// NOTE: the return values isn't really used yet but the channel is here if | |||||
// it can be of use. | |||||
int do_log() { | |||||
if(!config.us.size()) throw CLIerror( | |||||
"A configuration file must be specified before input files." | |||||
); | |||||
Splits ln; | |||||
int ict=0; | |||||
NameVal::iterator nvp; | |||||
string name, address, s; | |||||
Conn conn; | |||||
bool match; | |||||
/// parse log file /// | |||||
line_no=0; | |||||
while((*log >> ln)) { | |||||
line_no++; | |||||
cerr << bug << ' ' << line_no << '\r' << flush; | |||||
/// DNS query result /// | |||||
// TODO: need to get more specific on tying us + them + time to DNS | |||||
if(ln.count>8 && strncmp(ln.fields[4], "dnsmasq[", 8)==0) { | |||||
if(ln[5]=="reply") { | |||||
name = ln[6]; | |||||
address = ln[8]; | |||||
// Hmm... is this reply an address? | |||||
if(pre_match(dns_ignore, address)) continue; // nope | |||||
if(pre_match(dns_del, address)) continue; // does not exist reply | |||||
if((nvp=rdns.find(address))!=rdns.end()) { | |||||
if(nvp->second==name) continue; | |||||
dlog("WARN: DNS address overlap "+address+": "+nvp->second+" : "+name); | |||||
} | |||||
rdns[address] = name; | |||||
dlog("Added "+address+" = "+name); | |||||
continue; | |||||
} | |||||
} | |||||
int main(int argc, char **argv) { | |||||
/// process connections /// | |||||
if(ln.count>5 | |||||
&& ln[4]=="kernel:" | |||||
&& ln[5]=="ACCEPT" | |||||
) { | |||||
conn = ln; | |||||
if(!pre_match(config.us, conn.us)) conn.swap(); | |||||
if((nvp=rdns.find(conn.them))!=rdns.end()) | |||||
conn.name = nvp->second; | |||||
if(config.ignores.find(conn)<0) | |||||
*out << ln[0] << " " << ln[1] << " " << ln[2] << " " << conn << "\n"; | |||||
else | |||||
ict++; | |||||
} | |||||
} | |||||
*out << flush; // make sure all data gets written. | |||||
cerr << "\nIgnored: " << ict << endl; | |||||
cerr << "Total rDNS: " << rdns.size() << "\n"; | |||||
return 0; | |||||
} | |||||
/// Load config /// | |||||
config.load(PATH "/iptraffic.conf"); | |||||
/// parse log file /// | |||||
int main() { | |||||
try { | |||||
cBaseApp::main(); | |||||
if(!log) { | |||||
// no inputs were specified run stdin. | |||||
log = &cin; | |||||
ExitCode = do_log(); | |||||
} | |||||
} catch(const CLIerror &e) { | |||||
cerr << "ERROR: " << e.what() << "\n"; | |||||
help(); | |||||
} | |||||
return ExitCode; | |||||
} | |||||
}; | |||||
while((log >> ln)) { | |||||
lnno++; | |||||
cout << bug << " " << lnno << flush; | |||||
/// DNS query result /// | |||||
// TODO: need to get more specific on tying us + them + time to DNS | |||||
if(ln.count>8 && strncmp(ln.fields[4], "dnsmasq[", 8)==0) { | |||||
if(ln[5]=="reply") { | |||||
name = ln[6]; | |||||
address = ln[8]; | |||||
// Hmm... is this reply an address? | |||||
if(pre_match(dns_ignore, address)) continue; // nope | |||||
if(pre_match(dns_del, address)) continue; // does not exist reply | |||||
if((nvp=rdns.find(address))!=rdns.end()) { | |||||
if(nvp->second==name) continue; | |||||
dlog("WARN: DNS address overlap "+address+": "+nvp->second+" : "+name); | |||||
} | |||||
rdns[address] = name; | |||||
dlog("Added "+address+" = "+name); | |||||
#ifdef DEBUG | |||||
cout << '\r' << lnno << ": " << name << endl; | |||||
#endif | |||||
continue; | |||||
} | |||||
} | |||||
////////////////////////////////////////////////////////////////////// | |||||
// Run it | |||||
////////////////////////////////////////////////////////////////////// | |||||
/// process connections /// | |||||
if(ln.count>5 | |||||
&& ln[4]=="kernel:" | |||||
&& ln[5]=="ACCEPT" | |||||
) { | |||||
conn = ln; | |||||
if(!pre_match(config.us, conn.us)) conn.swap(); | |||||
if((nvp=rdns.find(conn.them))!=rdns.end()) | |||||
conn.name = nvp->second; | |||||
if(config.ignores.find(conn)<0) | |||||
out << ln[0] << " " << ln[1] << " " << ln[2] << " " << conn << "\n"; | |||||
else | |||||
ict++; | |||||
} | |||||
} | |||||
cout << "\nIgnored: " << ict << endl; | |||||
cout << "Total rDNS: " << rdns.size() << "\n"; | |||||
return 0; | |||||
} | |||||
MAIN(IPtraffic) |