|
|
@@ -6,12 +6,18 @@ |
|
|
|
// 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 |
|
|
|
// 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 <ChipMaster@YeOlPiShack.net> |
|
|
|
// Dumbed down for C++ < 11. |
|
|
|
////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////// |
|
|
|
// AAdditional Router setup: |
|
|
|
// Additional Router setup: |
|
|
|
// |
|
|
|
// ipset -N evilhosts iphash |
|
|
|
// ipset -N evilnets nethash |
|
|
@@ -23,10 +29,10 @@ |
|
|
|
// 10.10.10.1 -> 134.215.160.1 ICMP[8] |
|
|
|
// |
|
|
|
////////////////////////////////////////////////////////////////////// |
|
|
|
// TODO: wildcard for name matching |
|
|
|
// TODO: map names according to time and host. time is probably automatic |
|
|
|
|
|
|
|
#include <string.h> |
|
|
|
#include <stdlib.h> |
|
|
|
#include <string> |
|
|
|
#include <iostream> |
|
|
|
#include <fstream> |
|
|
@@ -41,6 +47,7 @@ using namespace std; |
|
|
|
// Splits: a util class to devide a line into space sep pieces |
|
|
|
////////////////////////////////////////////////////////////////////// |
|
|
|
// TODO: implement begin() + end() to make "for( : )" work |
|
|
|
// TODO: implement field enclosing & escaping chars |
|
|
|
|
|
|
|
struct Splits { |
|
|
|
|
|
|
@@ -106,20 +113,28 @@ struct TSV: public Splits { |
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////// |
|
|
|
// Subclass to match a list of prefixes against a string |
|
|
|
// Function to match a list of prefixes against a string |
|
|
|
// |
|
|
|
// This is not a subclass per-se since I don't know how to expose the |
|
|
|
// inherited { } list based constructor and I just want to knock this |
|
|
|
// out. So this is Q&D: typedef + operator. |
|
|
|
// Since C++ < 11 doesn't support constant vector initialization we'll |
|
|
|
// do this the old fashioned way with a null terminated char*[]. |
|
|
|
////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
|
|
typedef vector<string> PreMatch; |
|
|
|
bool operator==(const PreMatch &list, const string &s) { |
|
|
|
for(auto p: list) if(s.substr(0, p.size())==p) return true; |
|
|
|
bool pre_match(char **list, const string &s) { |
|
|
|
const char *p = s.c_str(); |
|
|
|
for(; *list; list++) |
|
|
|
if(!strncmp(*list, p, strlen(*list))) return true; |
|
|
|
return false; |
|
|
|
} |
|
|
|
inline bool operator!=(const PreMatch &list, const string &s) { return !(list==s); } |
|
|
|
|
|
|
|
/* And because I think this may be useful in the future I'll hang on to it. |
|
|
|
bool pre_match(const vector<string> &list, const string &s) { |
|
|
|
for( |
|
|
|
vector<string>::const_iterator p=list.begin(); |
|
|
|
p!=list.end(); |
|
|
|
p++ |
|
|
|
) |
|
|
|
if(s.substr(0, p->size())==*p) return true; |
|
|
|
return false; |
|
|
|
}*/ |
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////// |
|
|
@@ -184,21 +199,25 @@ struct Conn { |
|
|
|
|
|
|
|
// TODO: does < > have any actual meaning in this context? |
|
|
|
int cmp(const Conn >r) const { |
|
|
|
if(us<gtr.us) return -1; |
|
|
|
if(us>gtr.us) return 1; |
|
|
|
if(us!="*" && gtr.us!="*") { |
|
|
|
if(us<gtr.us) return -1; |
|
|
|
if(us>gtr.us) return 1; |
|
|
|
} |
|
|
|
// TODO: auto-wildcard port based on in? |
|
|
|
if(us_port && gtr.us_port) { // 0 = no comparison wildcard |
|
|
|
if(us_port<gtr.us_port) return -1; |
|
|
|
if(us_port>gtr.us_port) return 1; |
|
|
|
} |
|
|
|
if(them<gtr.them) return -1; |
|
|
|
if(them>gtr.them) return 1; |
|
|
|
if(them!="*" && gtr.them!="*") { |
|
|
|
if(them<gtr.them) return -1; |
|
|
|
if(them>gtr.them) return 1; |
|
|
|
} |
|
|
|
if(them_port && gtr.them_port) { // 0 = no comparison wildcard |
|
|
|
if(them_port<gtr.them_port) return -1; |
|
|
|
if(them_port>gtr.them_port) return 1; |
|
|
|
} |
|
|
|
// TODO: do we want to consider the name? |
|
|
|
if(name!="") { |
|
|
|
if(name!="*" && gtr.name!="*") { |
|
|
|
if(name<gtr.name) return -1; |
|
|
|
if(name>gtr.name) return 1; |
|
|
|
} |
|
|
@@ -264,10 +283,10 @@ struct ConnList: public vector<Conn> { |
|
|
|
////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
|
|
struct LiveBug { |
|
|
|
string seq = "-\\|/"; |
|
|
|
char pre = '\r'; |
|
|
|
int p; |
|
|
|
LiveBug(): p(0) {} |
|
|
|
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) { |
|
|
@@ -283,16 +302,17 @@ ostream &operator<<(ostream &o, LiveBug &bug) { |
|
|
|
|
|
|
|
typedef map<string,string> NameVal; |
|
|
|
|
|
|
|
const PreMatch us = { "10.10.10.", "192.168.255.", "2001:470:a:169:" }; |
|
|
|
const PreMatch dns_ignore = { "v=spf1", "https:" }; |
|
|
|
const PreMatch dns_del = { "NODATA-", "NXDOMAIN-" }; |
|
|
|
// TODO: define us[] in conf file |
|
|
|
char *us[] = { (char *)"10.10.10.", (char *)"192.168.255.", (char *)"2001:470:a:169:", 0 }; |
|
|
|
char *dns_ignore[] = { (char *)"v=spf1", (char *)"https:", 0 }; |
|
|
|
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, queries; |
|
|
|
NameVal rdns; |
|
|
|
NameVal::iterator nvp; |
|
|
|
string name, address, s; |
|
|
|
Conn conn; |
|
|
@@ -302,13 +322,19 @@ ConnList ignores; |
|
|
|
|
|
|
|
|
|
|
|
void dlog(const string msg) { |
|
|
|
#ifdef DEBUG |
|
|
|
cerr << "\r" << lnno << ": " << msg << endl; |
|
|
|
#endif |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int main(int argc, char **argv) { |
|
|
|
|
|
|
|
/// Load lists /// |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Read in ignore list /// |
|
|
|
|
|
|
|
{ |
|
|
@@ -332,60 +358,21 @@ int main(int argc, char **argv) { |
|
|
|
|
|
|
|
// TODO: need to get more specific on tying us + them + time to DNS |
|
|
|
if(ln.count>8 && strncmp(ln.fields[4], "dnsmasq[", 8)==0) { |
|
|
|
|
|
|
|
/// Query send /// |
|
|
|
|
|
|
|
if(strncmp(ln.fields[5], "query[", 6)==0) { |
|
|
|
s=ln[5].substr(6, ln[5].size()-7); |
|
|
|
if(s!="A" && s!="AAAA") continue; // we're only concerned with addresses. |
|
|
|
name = ln[6]; |
|
|
|
address = ln[8]; |
|
|
|
dlog("Query["+s+"] '"+name+"' for "+address); |
|
|
|
name+=':'+s; |
|
|
|
if(queries.find(name)==queries.end()) |
|
|
|
queries[name]=address; |
|
|
|
else |
|
|
|
dlog("WARN: Query already exists!"); |
|
|
|
|
|
|
|
/// Query reply /// |
|
|
|
|
|
|
|
} else if(ln[5]=="reply") { |
|
|
|
if(ln[5]=="reply") { |
|
|
|
name = ln[6]; |
|
|
|
address = ln[8]; |
|
|
|
// Hmm... is this reply an address? |
|
|
|
if(dns_ignore==address) continue; // nope |
|
|
|
if(dns_del==address) { |
|
|
|
// "no exist" reply so just drop them. |
|
|
|
if(*(address.end()-1)=='4') name+=":A"; |
|
|
|
else if(*(address.end()-1)=='6') name+=":AAAA"; |
|
|
|
else continue; |
|
|
|
dlog("drop query '"+name+"'"); |
|
|
|
if((nvp=queries.find(name))!=queries.end()) queries.erase(nvp); |
|
|
|
continue; |
|
|
|
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); |
|
|
|
} |
|
|
|
// IPv6 or v4 query? |
|
|
|
if(address.find(':')==name.npos) |
|
|
|
s=name+":AAAA"; |
|
|
|
else |
|
|
|
s=name+":A"; |
|
|
|
// now make source dest couplet |
|
|
|
if((nvp=queries.find(s))!=queries.end()) { |
|
|
|
address+=':'+nvp->second; |
|
|
|
//queries.erase(nvp); // remove from active query list |
|
|
|
if((nvp=rdns.find(address))!=rdns.end()) { |
|
|
|
if(nvp->second==name) continue; |
|
|
|
rdns[address] = name; |
|
|
|
dlog("Added "+address+" = "+name); |
|
|
|
#ifdef DEBUG |
|
|
|
dlog("WARN: DNS address overlap "+address+": "+rdnsp->second+" : "+name); |
|
|
|
cout << '\r' << lnno << ": " << name << endl; |
|
|
|
#endif |
|
|
|
} |
|
|
|
rdns[address] = name; |
|
|
|
dlog("Added "+address+" = "+name); |
|
|
|
#ifdef DEBUG |
|
|
|
cout '\r' << lnno << ": " << name << endl; |
|
|
|
#endif |
|
|
|
continue; |
|
|
|
} |
|
|
|
dlog("WARN: reply '"+name+"' skipped due to lack of matching query"); |
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
@@ -397,19 +384,16 @@ int main(int argc, char **argv) { |
|
|
|
&& ln[5]=="ACCEPT" |
|
|
|
) { |
|
|
|
conn = ln; |
|
|
|
if(us!=conn.us) conn.swap(); |
|
|
|
if((nvp=rdns.find(conn.them+':'+conn.us))!=rdns.end()) |
|
|
|
if(!pre_match(us, conn.us)) conn.swap(); |
|
|
|
if((nvp=rdns.find(conn.them))!=rdns.end()) |
|
|
|
conn.name = nvp->second; |
|
|
|
if(ignores.find(conn)<0) |
|
|
|
out << conn << "\n"; |
|
|
|
out << ln[0] << " " << ln[1] << " " << ln[2] << " " << conn << "\n"; |
|
|
|
else |
|
|
|
ict++; |
|
|
|
} |
|
|
|
} |
|
|
|
cout << "\nIgnored: " << ict << endl; |
|
|
|
|
|
|
|
#ifdef DEBUG |
|
|
|
cout << "\n\n" << "Total rDNS: " << rdns.size() << "\n"; |
|
|
|
#endif |
|
|
|
cout << "Total rDNS: " << rdns.size() << "\n"; |
|
|
|
return 0; |
|
|
|
} |