////////////////////////////////////////////////////////////////////// // IP traffic analyzer // Written by Jonathan A. Foster // Started April 23rd, 2021 // // The idea is to analyze iptables LOG entries in combination with // DNSmasq's query log entries and combine them to list the hosts // that were accessed. The main reasons for not just inspecting HTTP // packets through a netfilter socket is due to HTTPS hiding the // "host" field. So I'm deducing based on DNS query timing. // // NOTE: its assumed that the log being processed is in chronological // order. This is the usual way things get logged. ;-) // // 2021-05-14 // 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 // // 2021-08-11 // Move main data colation routine into its own class to be shared // with multiple tools. ////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include "cli.h" #include "strutil.h" #include "data.h" #include "config.h" using namespace std; ////////////////////////////////////////////////////////////////////// // Application class to process files. ////////////////////////////////////////////////////////////////////// //#define DEBUG struct IPtraffic: public cBaseApp { Config config; LogAnalyzer analyze; istream *log; ostream *out; LiveBug bug; int line_no; IPtraffic(): out(&cout), log(0) { // I'd rather this initialization be static... analyze.us = &(config.us.vals); } ~IPtraffic() { if(log && log!=&cin) delete(log); } void dlog(const string msg) { #ifdef DEBUG cerr << line_no << ": " << msg << endl; #endif } // TODO: elaborate int help() { cerr << "\n" "iptraffic -c {config file} [-o {output file}] [{input file} [...]]\n"; return 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.vals.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() { int ict=0; // ignored netfilter lines std::string l; /// parse log file /// if(!config.us.vals.size()) throw CLIerror( "A configuration file must be specified before input files." ); line_no=0; while(std::getline(*log, l)) { line_no++; cerr << bug << ' ' << line_no << '\r' << flush; /// process connections /// if(analyze.line(l)) { if(config.ignores.vals.find(analyze.conn)<0) *out << analyze.ln[0] << " " << analyze.ln[1] << " " << analyze.ln[2] << " " << analyze.conn << "\n"; else ict++; } } *out << flush; // make sure all data gets written. cerr << "Lines: " << line_no << "\nIgnored: " << ict << "\nTotal rDNS: " << analyze.rdns.size() << endl; return 0; } 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; } }; ////////////////////////////////////////////////////////////////////// // Run it ////////////////////////////////////////////////////////////////////// MAIN(IPtraffic)