diff --git a/Makefile b/Makefile index fec1f4b..3d0f5eb 100644 --- a/Makefile +++ b/Makefile @@ -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 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 g++ -c -o $@ data.cpp +cli.o: cli.cpp cli.h + g++ -c -o $@ cli.cpp + strutil.o: strutil.cpp strutil.h g++ -c -o $@ strutil.cpp @@ -14,7 +17,7 @@ strutil.o: strutil.cpp strutil.h .PHONY: run run: iptraffic - ./iptraffic 2> log # 2>&1 | head -n 20 + ./iptraffic diff --git a/cli.cpp b/cli.cpp new file mode 100644 index 0000000..045b8e4 --- /dev/null +++ b/cli.cpp @@ -0,0 +1,74 @@ +/********************************************************************* + * C++ Base Application Object + * Written by Jonathan A. Foster +#include +#include +#include +#include +#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) 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. +} diff --git a/cli.h b/cli.h new file mode 100644 index 0000000..d8c6e9c --- /dev/null +++ b/cli.h @@ -0,0 +1,179 @@ +////////////////////////////////////////////////////////////////////// +// C++ Base Application Object +// Written by Jonathan A. Foster +#include +#include + + + +////////////////////////////////////////////////////////////////////// +// 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 diff --git a/iptraffic.cpp b/iptraffic.cpp index 77859da..e186cd8 100644 --- a/iptraffic.cpp +++ b/iptraffic.cpp @@ -13,11 +13,19 @@ // order. This is the usual way things get logged. ;-) // // 2021-05-14 -// Dumbed down for C++ < 11. +// Dumbed down for < C++11. // Split into modules +// +// 2021-06-18 +// 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 #include @@ -26,6 +34,7 @@ #include #include #include +#include "cli.h" #include "strutil.h" #include "data.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 - cerr << "\r" << lnno << ": " << msg << endl; + cerr << line_no << ": " << msg << endl; #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)