* Add README.md * Reorder Makefile * Remove remarks that don't have any baring outside of here. * Add INI based conig file for defining local networks and ignores. * Break out data classes into their own module (needed for config). * Break out string utilities into their own module. * Remove "us" & "ignore" definitions from source.master
@@ -2,3 +2,4 @@ | |||
*.o | |||
/iptraffic | |||
/log | |||
/README.html |
@@ -1,10 +1,21 @@ | |||
iptraffic: iptraffic.cpp strutil.o data.o config.o | |||
g++ -o $@ $@.cpp strutil.o data.o config.o | |||
config.o: config.cpp config.h strutil.o data.o | |||
g++ -c -o $@ config.cpp | |||
data.o: data.cpp data.h strutil.o | |||
g++ -c -o $@ data.cpp | |||
strutil.o: strutil.cpp strutil.h | |||
g++ -c -o $@ strutil.cpp | |||
.PHONY: run | |||
run: iptraffic | |||
./iptraffic 2> log # 2>&1 | head -n 20 | |||
iptraffic: iptraffic.cpp | |||
g++ -o $@ $@.cpp | |||
.PHONY: clean distclean | |||
@@ -0,0 +1,162 @@ | |||
Poor Man's IDS | |||
============== | |||
### (Yes Women can use it too) | |||
The goal of this project is to keep an eye on the requests going in | |||
and out of my network onto the Internet (iNet). This is made | |||
necessary for two reasons: | |||
1. By looking for unusual activity I can get a heads up about | |||
unwanted software or even "spy hardware" on my systems, ie. | |||
"Detect Intrusions". | |||
2. Almost all software now days, especially those created by gigabuck | |||
giants, makes requests out onto the iNet that I did not ask for | |||
and don't want happening. But even Mozilla makes network traffic I | |||
didn't ask for and don't want. | |||
So this tool is my way of "watching the watchers". This is not a | |||
plug-n-play tool that _magically_ grants the user a "suit of | |||
invulnerability". But it is a tool for those looking for more insight | |||
into their iNet traffic either due to security concerns or curiosity. | |||
This software is in the very early stages. Right now it just combines | |||
data from logs from a couple of different software packages. I've | |||
already setup many blocks for traffic I don't want happening, like | |||
g00gle Analytics, some ad servers, ... | |||
For the curious I'll post my current block lists in this repository | |||
from time to time. But be **WARNED** that its likely to break your | |||
iNet experience if you use them. I'm a cyber-rebel at heart and tend | |||
to take an "if its doing something I don't want, I have no use for | |||
it" approach. Meaning I'd rather not use a site / program if it | |||
violates my concerns, rather than just "go with the flow". And I will | |||
likely discover I'm breaking stuff I actually want, like: I realized | |||
I've broken my ability to post comments on | |||
[HaD](https://hackaday.com/), but I was curious about what "Server X" | |||
did. | |||
So! If you're not "faint of heart" come join me on my adventure in | |||
iNet security exploration. | |||
Phase 1 - General Setup & Operation | |||
----------------------------------- | |||
In general I feel its necessary to have a **real-world** idea of what | |||
you're dealing with before diving in and writing software to deal | |||
with what you _think_ your dealing with. So the basic plan is simple: | |||
1. Setup my network routing devices, which are running Linux, to log | |||
DNS queries and network connections. Specifically, anything that is | |||
a _new_ unrelated packet gets logged. | |||
2. Collect the logs on a machine, with backups on alternate machines. | |||
3. Run the logs through a filter to combine the DNS query data with | |||
the packet data. | |||
4. Analyze results to determine phase 2 needs. | |||
In this phase I'm jumping the gun a bit, but cyber-thugs are actively | |||
beating on all of our doors, even as I'm getting my stuff together | |||
and there are certain kinds of traffic I know I want to put an end | |||
to. In that light I've already spotted interesting servers I'm | |||
blocking. And I've beefed up my firewall to my mail server with more | |||
permanent blocks from obvious MTA bashers. | |||
In this phase I'm using the most excellent | |||
"[dnsmasq](https://thekelleys.org.uk/dnsmasq/doc.html)" to log the | |||
queries **and** block host names I don't want being accessed. I do | |||
this by assigning the bogus address "127.0.0.255" to the names in my | |||
server's "hosts" file, which is used by "dnsmasq" to answer DNS | |||
queries. That address is a **valid** "localhost" address, so will | |||
**immediately** fail requests unless you put a http(s) server. And | |||
I'm sure you can imagine those possibilities. | |||
The other source of information and block capabilities is "iptables" | |||
/ "ip6tables". I added rules to log "new" packets. The parsing | |||
software is what this repository is about right now. | |||
Note on routers | |||
--------------- | |||
I'm using Netgear appliances for WiFi and the first tier firewall | |||
connected to my iNet connection. Since a lot of modern ISP connection | |||
devices provide their own idea of security I have to turn off their | |||
firewall stuff and configure them in a transparent bridging | |||
configuration. | |||
I use Netgear because they actively provide tool chains for select | |||
models of their equipment and encourage people to load their own | |||
software mods on them. Have a look at | |||
[My Open Router](https://www.myopenrouter.com/). On my devices I | |||
have settled on [Shibby's Tomato](https://tomato.groov.pl/). Its very | |||
compact, extremely flexible and seems to have everything I've needed | |||
when I've needed it. As an example my dnsmasq & iptables setup for my | |||
gateway router required no changes to the firmware. I just put some | |||
of the lesser used config pages to use. | |||
The future | |||
---------- | |||
The goal is an active alert system. This system should provide | |||
immediate feedback on unknown connections allowing the user to either | |||
grant or deny access and maintain the appropriate block lists. | |||
But as "reality" exposes itself things are likely to change. | |||
The CODE | |||
-------- | |||
I'm using C++ to write this. I'm targeting C++98 at this time. | |||
Although I think C++11 defines the minimum viable version of C++ | |||
there are places that its still not available, which is most likely | |||
to happen with oddball tool chains like those provided by Netgear. | |||
But even my RaspberryPI 2B doesn't support it. Well... maybe with an | |||
OS upgrade... I'm hoping to support a very wide Linux platform | |||
coverage. | |||
I'm doing all of my development with Linux. It may be possible to do | |||
something similar, maybe with very little modification, on BSD based | |||
platforms. I don't have them and therefor won't be personally working | |||
on it. But, unless it makes things really ugly, I'm not opposed to | |||
contributions on that front. | |||
Installation & use | |||
------------------ | |||
This is still **VERY** crude and incomplete. | |||
1. Configure the source: the basic setup is hardcoded in the source. | |||
I'm just testing right now. ;-) There are three lines near the | |||
bottom of "iptraffic.cpp" to be concerned with: | |||
* `#define PATH "/srv/backups/iptraffic"`: this is the base path | |||
to the work directory. Its prepended to other paths, read on. | |||
* `ifstream log(PATH "/test.log");`: defines the log file to | |||
process. | |||
* `ofstream out(PATH "/processed.log");`: the file with the | |||
combined data that's written out. | |||
* `config.load(PATH "/iptraffic.conf");`: Path to a configuration | |||
file that lists networks to consider as "us" and connections to | |||
ignore, basically the set that is considered "OK". | |||
2. Type "make" to compile. Hopefully it compiles for you. | |||
3. Run "iptraffic". | |||
4. Check the output. |
@@ -0,0 +1,54 @@ | |||
////////////////////////////////////////////////////////////////////// | |||
// IP traffic analyzer - configuration file | |||
// Written by Jonathan A. Foster <jon@jfpossibilities.com> | |||
// Started May 14th, 2021 | |||
// Copyright JF Possibilities, Inc. All rights reserved. | |||
////////////////////////////////////////////////////////////////////// | |||
#include <fstream> | |||
#include "config.h" | |||
#include "strutil.h" | |||
void Config::load(const std::string &fname) { | |||
std::string l; | |||
std::ifstream f(fname.c_str()); | |||
TSV tsv; | |||
Conn conn; | |||
while(std::getline(f, l)) { | |||
l = strip(l); | |||
if(l=="" || l[0]=='#') continue; | |||
if(l.size()>2 && l[0]=='[' && l.end()[-1]==']') { | |||
heading: | |||
if(l=="[us]") { | |||
/// Read in "us" list /// | |||
while(std::getline(f, l)) { | |||
l=strip(l); | |||
if(l=="" || l[0]=='#') continue; | |||
if(l.size()>2 && l[0]=='[' && l.end()[-1]==']') goto heading; | |||
us.push_back(l); | |||
} | |||
} else if(l=="[ignores]") { | |||
/// Read in ignore list /// | |||
while(std::getline(f, l)) { | |||
if(l=="" || l[0]=='#') continue; | |||
if(l.size()>2 && l[0]=='[' && l.end()[-1]==']') goto heading; | |||
tsv = l; | |||
if(tsv.count>6) { | |||
tsv >> conn; | |||
ignores.push_back(conn); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,27 @@ | |||
////////////////////////////////////////////////////////////////////// | |||
// IP traffic analyzer - configuration file | |||
// Written by Jonathan A. Foster <jon@jfpossibilities.com> | |||
// Started May 14th, 2021 | |||
// Copyright JF Possibilities, Inc. All rights reserved. | |||
// | |||
// This class reads and holds configuration data for "iptraffic". The | |||
// file is basically an INI format with '#' for remarks instead of ';' | |||
// and sections can be lists, not just name/value pairs. In fact this | |||
// first version is strictly only lists. | |||
////////////////////////////////////////////////////////////////////// | |||
#ifndef __JFP_IPTRAFFIC_CONF_H__ | |||
#define __JFP_IPTRAFFIC_CONF_H__ | |||
#include <string> | |||
#include <vector> | |||
#include "data.h" | |||
struct Config { | |||
std::vector<std::string> us; | |||
ConnList ignores; | |||
void load(const std::string &fname); | |||
}; | |||
#endif |
@@ -0,0 +1,144 @@ | |||
////////////////////////////////////////////////////////////////////// | |||
// IP traffic analyzer - data objects | |||
// Written by Jonathan A. Foster <ChipMaster@YeOlPiShack.net> | |||
// Started April 23rd, 2021 | |||
// Copyright JF Possibilities, Inc. All rights reserved. | |||
////////////////////////////////////////////////////////////////////// | |||
#include <string.h> | |||
#include <stdlib.h> | |||
#include <stdexcept> | |||
#include "data.h" | |||
////////////////////////////////////////////////////////////////////// | |||
// Conn | |||
////////////////////////////////////////////////////////////////////// | |||
void Conn::clear() { | |||
us = them = name = protocol = ""; | |||
in=false; | |||
us_port = them_port = 0; | |||
} | |||
void Conn::swap() { | |||
std::string s; | |||
int x; | |||
s = us; | |||
us = them; | |||
them =s; | |||
x = us_port; | |||
us_port = them_port; | |||
them_port = x; | |||
in=!in; | |||
} | |||
Conn &Conn::operator=(const Splits &sp) { | |||
int x; | |||
clear(); | |||
for(x=0; x<sp.count; x++) { | |||
if(!strncmp(sp.fields[x], "SRC=", 4)) { | |||
us = sp.fields[x]+4; | |||
continue; | |||
} | |||
if(!strncmp(sp.fields[x], "DST=", 4)) { | |||
them = sp.fields[x]+4; | |||
continue; | |||
} | |||
if(!strncmp(sp.fields[x], "SPT=", 4)) { | |||
us_port = atoi(sp.fields[x]+4); | |||
continue; | |||
} | |||
if(!strncmp(sp.fields[x], "DPT=", 4)) { | |||
them_port = atoi(sp.fields[x]+4); | |||
continue; | |||
} | |||
if(!strncmp(sp.fields[x], "TYPE=", 5) && protocol=="ICMP") { | |||
us_port = them_port = atoi(sp.fields[x]+5); | |||
continue; | |||
} | |||
if(!strncmp(sp.fields[x], "PROTO=", 6)) | |||
protocol = sp.fields[x]+6; | |||
} | |||
} | |||
// TODO: does < > have any actual meaning in this context? | |||
int Conn::cmp(const Conn >r) const { | |||
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!="*") { | |||
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!="*" && gtr.name!="*") { | |||
if(name<gtr.name) return -1; | |||
if(name>gtr.name) return 1; | |||
} | |||
if(protocol<gtr.protocol) return -1; | |||
if(protocol>gtr.protocol) return 1; | |||
if(in<gtr.in) return -1; | |||
if(in>gtr.in) return 1; | |||
return 0; | |||
} | |||
std::ostream &operator<<(std::ostream &out, const Conn &c) { | |||
out << c.us | |||
<< ( c.in ? " <- " : " -> " ) | |||
<< c.them | |||
<< " " << c.protocol | |||
<< "[" << ( c.in ? c.us_port : c.them_port ) << "] " | |||
<< c.name; | |||
return out; | |||
} | |||
const Splits &operator>>(const Splits &tsv, Conn &conn) { | |||
if(tsv.count<7) throw std::runtime_error("Conn=TSV: too few columns"); | |||
conn.clear(); | |||
conn.us = tsv[0]; | |||
conn.us_port = atoi(tsv.fields[1]); | |||
conn.them = tsv[2]; | |||
conn.them_port = atoi(tsv.fields[3]); | |||
conn.name = tsv[4]; | |||
conn.protocol = tsv[5]; | |||
conn.in = tsv[6]=="1"; | |||
return tsv; | |||
} | |||
////////////////////////////////////////////////////////////////////// | |||
// ConnList | |||
////////////////////////////////////////////////////////////////////// | |||
int ConnList::find(Conn &needle) { | |||
int r; | |||
for(r=0; r<size(); r++) if((*this)[r]==needle) return r; | |||
return -1; | |||
} |
@@ -0,0 +1,68 @@ | |||
////////////////////////////////////////////////////////////////////// | |||
// IP traffic analyzer - data objects | |||
// Written by Jonathan A. Foster <ChipMaster@YeOlPiShack.net> | |||
// Started April 23rd, 2021 | |||
// Copyright JF Possibilities, Inc. All rights reserved. | |||
// | |||
// This is useful for breaking a text file line into fields. | |||
// | |||
// 2021-05-14 <ChipMaster@YeOlPiShack.net> | |||
// Restructure: broke out of monolithic iptraffic.cpp and made its | |||
// own module. | |||
////////////////////////////////////////////////////////////////////// | |||
#ifndef __JFP_IPTRAFFIC_DATA_H__ | |||
#define __JFP_IPTRAFFIC_DATA_H__ | |||
#include <string> | |||
#include <ostream> | |||
#include <vector> | |||
#include "strutil.h" | |||
////////////////////////////////////////////////////////////////////// | |||
// Network connection between "us" and "them" | |||
////////////////////////////////////////////////////////////////////// | |||
typedef unsigned short word; | |||
struct Conn { | |||
std::string us; // address on our side | |||
word us_port; // the port on our side | |||
std::string them; // address on their side | |||
word them_port; // the port on their side | |||
std::string name; // name of the address | |||
std::string protocol; // protocol used to communicate | |||
bool in; // whether this was an inward bound connection. | |||
Conn(): us_port(0), them_port(0), in(false) {} | |||
// clear data | |||
void clear(); | |||
// swap polarity of record | |||
void swap(); | |||
// scan & copy data from log record in | |||
Conn &operator=(const Splits &sp); | |||
// compare to another Conn | |||
int cmp(const Conn >r) const; | |||
inline bool operator<(const Conn >r) const { return cmp(gtr) <0; } | |||
inline bool operator<=(const Conn >r) const { return cmp(gtr)<=0; } | |||
inline bool operator>(const Conn >r) const { return cmp(gtr) >0; } | |||
inline bool operator>=(const Conn >r) const { return cmp(gtr)>=0; } | |||
inline bool operator==(const Conn >r) const { return cmp(gtr)==0; } | |||
inline bool operator!=(const Conn >r) const { return cmp(gtr)!=0; } | |||
}; | |||
// A text output of Conn | |||
std::ostream &operator<<(std::ostream &out, const Conn &c); | |||
// Copy data from Splits into Conn | |||
const Splits &operator>>(const Splits &tsv, Conn &conn); | |||
////////////////////////////////////////////////////////////////////// | |||
// List of connections | |||
////////////////////////////////////////////////////////////////////// | |||
struct ConnList: public std::vector<Conn> { | |||
int find(Conn &needle); | |||
}; | |||
#endif |
@@ -14,271 +14,26 @@ | |||
// | |||
// 2021-05-14 <ChipMaster@YeOlPiShack.net> | |||
// Dumbed down for C++ < 11. | |||
// Split into modules | |||
////////////////////////////////////////////////////////////////////// | |||
////////////////////////////////////////////////////////////////////// | |||
// Additional Router setup: | |||
// | |||
// ipset -N evilhosts iphash | |||
// ipset -N evilnets nethash | |||
////////////////////////////////////////////////////////////////////// | |||
////////////////////////////////////////////////////////////////////// | |||
// Obvious ignores: | |||
// | |||
// 10.10.10.1 -> 134.215.160.1 ICMP[8] | |||
// | |||
////////////////////////////////////////////////////////////////////// | |||
// TODO: map names according to time and host. time is probably automatic | |||
#include <string.h> | |||
#include <stdlib.h> | |||
#include <string> | |||
#include <iostream> | |||
#include <fstream> | |||
#include <stdexcept> | |||
#include <vector> | |||
#include <map> | |||
#include "strutil.h" | |||
#include "data.h" | |||
#include "config.h" | |||
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 { | |||
/// CONFIG /// | |||
enum { FieldMax=256, LineMax=1024 }; | |||
/// properties /// | |||
char line[LineMax]; // Line buffer | |||
int len; // Length of line (after split()) | |||
char sep; // Separator character. | |||
bool combine; // Treat multiple consecutive seps as one (combine) | |||
char *fields[FieldMax]; // pointers to fields in line | |||
int count; // How many fields there were | |||
// construct | |||
Splits(): count(0), len(0), sep(' '), combine(true) { line[LineMax-1] = 0; } | |||
// Convert field[] to string | |||
inline string operator[](int i) const { string s(fields[i]); return s; } | |||
// split line. Returns count. | |||
int split() { | |||
len = count = 0; | |||
if(!*line) return count; | |||
fields[0] = line; | |||
while(len<LineMax && line[len]) { | |||
if(line[len]==sep) { | |||
line[len++]=0; | |||
if(combine) while(len<LineMax && line[len]==sep) len++; | |||
if(++count<FieldMax) { | |||
// this shouldn't happen | |||
if(len>=LineMax) throw | |||
runtime_error("Splits::split: end of buffer null missing!"); | |||
fields[count] = line+len; | |||
} else | |||
throw runtime_error("Splits::split: Too many fields in the line"); | |||
} else | |||
len++; | |||
} | |||
return count++; | |||
} | |||
}; | |||
// istream >> operator: getline() + .split() | |||
istream &operator>>(istream &in, Splits &sp) { | |||
if(in.getline(sp.line, sp.LineMax-1)) sp.split(); | |||
return in; | |||
} | |||
////////////////////////////////////////////////////////////////////// | |||
// TSV version of Splits | |||
////////////////////////////////////////////////////////////////////// | |||
struct TSV: public Splits { | |||
TSV() { sep='\t'; combine=false; } | |||
}; | |||
////////////////////////////////////////////////////////////////////// | |||
// Function to match a list of prefixes against a string | |||
// | |||
// Since C++ < 11 doesn't support constant vector initialization we'll | |||
// do this the old fashioned way with a null terminated char*[]. | |||
////////////////////////////////////////////////////////////////////// | |||
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; | |||
} | |||
/* 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; | |||
}*/ | |||
////////////////////////////////////////////////////////////////////// | |||
// Connection between "us" and "them" | |||
////////////////////////////////////////////////////////////////////// | |||
typedef unsigned short word; | |||
struct Conn { | |||
string us; // address on our side | |||
word us_port; // the port on our side | |||
string them; // address on their side | |||
word them_port; // the port on their side | |||
string name; // name of the address | |||
string protocol; // protocol used to communicate | |||
bool in; // whether this was an inward bound connection. | |||
Conn(): in(false) {} | |||
Conn &clear() { us = them = name = protocol = ""; in=false; us_port = them_port = 0; } | |||
// swap polarity of record | |||
Conn &swap() { | |||
string s; | |||
int x; | |||
s = us; | |||
us = them; | |||
them =s; | |||
x = us_port; | |||
us_port = them_port; | |||
them_port = x; | |||
in=!in; | |||
return *this; | |||
} | |||
// scan & copy data from log record in | |||
Conn &operator=(const Splits &sp) { | |||
int x; | |||
clear(); | |||
for(x=0; x<sp.count; x++) { | |||
if(!strncmp(sp.fields[x], "SRC=", 4)) { | |||
us = sp.fields[x]+4; | |||
continue; | |||
} | |||
if(!strncmp(sp.fields[x], "DST=", 4)) { | |||
them = sp.fields[x]+4; | |||
continue; | |||
} | |||
if(!strncmp(sp.fields[x], "SPT=", 4)) { | |||
us_port = atoi(sp.fields[x]+4); | |||
continue; | |||
} | |||
if(!strncmp(sp.fields[x], "DPT=", 4)) { | |||
them_port = atoi(sp.fields[x]+4); | |||
continue; | |||
} | |||
if(!strncmp(sp.fields[x], "TYPE=", 5) && protocol=="ICMP") { | |||
us_port = them_port = atoi(sp.fields[x]+5); | |||
continue; | |||
} | |||
if(!strncmp(sp.fields[x], "PROTO=", 6)) | |||
protocol = sp.fields[x]+6; | |||
} | |||
} | |||
// TODO: does < > have any actual meaning in this context? | |||
int cmp(const Conn >r) const { | |||
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!="*") { | |||
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!="*" && gtr.name!="*") { | |||
if(name<gtr.name) return -1; | |||
if(name>gtr.name) return 1; | |||
} | |||
if(protocol<gtr.protocol) return -1; | |||
if(protocol>gtr.protocol) return 1; | |||
if(in<gtr.in) return -1; | |||
if(in>gtr.in) return 1; | |||
return 0; | |||
} | |||
inline bool operator<(const Conn >r) const { return cmp(gtr) <0; } | |||
inline bool operator<=(const Conn >r) const { return cmp(gtr)<=0; } | |||
inline bool operator>(const Conn >r) const { return cmp(gtr) >0; } | |||
inline bool operator>=(const Conn >r) const { return cmp(gtr)>=0; } | |||
inline bool operator==(const Conn >r) const { return cmp(gtr)==0; } | |||
inline bool operator!=(const Conn >r) const { return cmp(gtr)!=0; } | |||
}; | |||
// A text output of this record | |||
ostream &operator<<(ostream &out, const Conn &c) { | |||
out << c.us | |||
<< ( c.in ? " <- " : " -> " ) | |||
<< c.them | |||
<< " " << c.protocol | |||
<< "[" << ( c.in ? c.us_port : c.them_port ) << "] " | |||
<< c.name; | |||
return out; | |||
} | |||
// Copy data from TSV in | |||
const TSV &operator>>(const TSV &tsv, Conn &conn) { | |||
if(tsv.count<7) throw runtime_error("Conn=TSV: too few columns"); | |||
conn.clear(); | |||
conn.us = tsv[0]; | |||
conn.us_port = atoi(tsv.fields[1]); | |||
conn.them = tsv[2]; | |||
conn.them_port = atoi(tsv.fields[3]); | |||
conn.name = tsv[4]; | |||
conn.protocol = tsv[5]; | |||
conn.in = tsv[6]=="1"; | |||
return tsv; | |||
} | |||
////////////////////////////////////////////////////////////////////// | |||
// List of connections | |||
////////////////////////////////////////////////////////////////////// | |||
struct ConnList: public vector<Conn> { | |||
int find(Conn &needle) { | |||
int r; | |||
for(r=0; r<size(); r++) if((*this)[r]==needle) return r; | |||
return -1; | |||
} | |||
}; | |||
////////////////////////////////////////////////////////////////////// | |||
// Busy indicator aka. "Live Bug" | |||
////////////////////////////////////////////////////////////////////// | |||
@@ -300,12 +55,9 @@ ostream &operator<<(ostream &o, LiveBug &bug) { | |||
////////////////////////////////////////////////////////////////////// | |||
//#define DEBUG | |||
typedef map<string,string> NameVal; | |||
// 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 }; | |||
// (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"); | |||
@@ -317,7 +69,7 @@ NameVal::iterator nvp; | |||
string name, address, s; | |||
Conn conn; | |||
bool match; | |||
ConnList ignores; | |||
Config config; | |||
@@ -331,22 +83,9 @@ void dlog(const string msg) { | |||
int main(int argc, char **argv) { | |||
/// Load lists /// | |||
/// Load config /// | |||
/// Read in ignore list /// | |||
{ | |||
TSV tsv; | |||
ifstream in(PATH "/ignores.lst"); | |||
while(in >> tsv) { | |||
if(tsv.count>6) { | |||
tsv >> conn; | |||
ignores.push_back(conn); | |||
} | |||
} | |||
} | |||
config.load(PATH "/iptraffic.conf"); | |||
/// parse log file /// | |||
@@ -384,10 +123,10 @@ int main(int argc, char **argv) { | |||
&& ln[5]=="ACCEPT" | |||
) { | |||
conn = ln; | |||
if(!pre_match(us, conn.us)) conn.swap(); | |||
if(!pre_match(config.us, conn.us)) conn.swap(); | |||
if((nvp=rdns.find(conn.them))!=rdns.end()) | |||
conn.name = nvp->second; | |||
if(ignores.find(conn)<0) | |||
if(config.ignores.find(conn)<0) | |||
out << ln[0] << " " << ln[1] << " " << ln[2] << " " << conn << "\n"; | |||
else | |||
ict++; | |||
@@ -0,0 +1,93 @@ | |||
////////////////////////////////////////////////////////////////////// | |||
// String splitter | |||
// Written by Jonathan A. Foster <ChipMaster@YeOlPiShack.net> | |||
// Started April 23rd, 2021 | |||
// Copyright JF Possibilities, Inc. All rights reserved. | |||
////////////////////////////////////////////////////////////////////// | |||
#include <string.h> | |||
#include <stdexcept> | |||
// Sounds an awful lot like a German pastry | |||
#include "strutil.h" | |||
////////////////////////////////////////////////////////////////////// | |||
// Splits | |||
////////////////////////////////////////////////////////////////////// | |||
Splits &Splits::operator=(const char *_line) { | |||
if(strlen(_line)>=LineMax) | |||
throw std::runtime_error("Splits::Splits(char*): string is longer than buffer"); | |||
strncpy(line, _line, LineMax-1); | |||
split(); | |||
return *this; | |||
} | |||
int Splits::split() { | |||
len = count = 0; | |||
if(!*line) return count; | |||
fields[0] = line; | |||
while(len<LineMax && line[len]) { | |||
if(line[len]==sep) { | |||
line[len++]=0; | |||
if(combine) while(len<LineMax && line[len]==sep) len++; | |||
if(++count<FieldMax) { | |||
// this shouldn't happen | |||
if(len>=LineMax) throw | |||
std::runtime_error("Splits::split: end of buffer null missing!"); | |||
fields[count] = line+len; | |||
} else | |||
throw std::runtime_error("Splits::split: Too many fields in the line"); | |||
} else | |||
len++; | |||
} | |||
return count++; | |||
} | |||
std::istream &operator>>(std::istream &in, Splits &sp) { | |||
if(in.getline(sp.line, sp.LineMax-1)) sp.split(); | |||
return in; | |||
} | |||
////////////////////////////////////////////////////////////////////// | |||
// pre_match() | |||
////////////////////////////////////////////////////////////////////// | |||
bool pre_match(const char **list, const std::string &s) { | |||
const char *p = s.c_str(); | |||
for(; *list; list++) | |||
if(!strncmp(*list, p, strlen(*list))) return true; | |||
return false; | |||
} | |||
// And if vectors can be used... | |||
bool pre_match(const StringList &list, const std::string &s) { | |||
for( | |||
StringList::const_iterator p=list.begin(); | |||
p!=list.end(); | |||
p++ | |||
) | |||
if(s.substr(0, p->size())==*p) return true; | |||
return false; | |||
} | |||
////////////////////////////////////////////////////////////////////// | |||
// misc | |||
////////////////////////////////////////////////////////////////////// | |||
std::string strip(const std::string &s) { | |||
int x, y; | |||
for(x=0; x<s.size() && s[x]<=' '; x++); | |||
for(y=s.size()-1; y>=x && s[y]<=' '; y--); | |||
if(y<x) return ""; | |||
return s.substr(x, y-x+1); | |||
} |
@@ -0,0 +1,104 @@ | |||
////////////////////////////////////////////////////////////////////// | |||
// String Utilities | |||
// Written by Jonathan A. Foster <ChipMaster@YeOlPiShack.net> | |||
// Started April 23rd, 2021 | |||
// Copyright JF Possibilities, Inc. All rights reserved. | |||
// | |||
// This is useful for breaking a text file line into fields. | |||
// | |||
// 2021-05-14 <ChipMaster@YeOlPiShack.net> | |||
// Restructure: broke out of monolithic iptraffic.cpp and made its | |||
// own module. | |||
////////////////////////////////////////////////////////////////////// | |||
#ifndef __JFP_STRUTIL_H__ | |||
#define __JFP_STRUTIL_H__ | |||
#include <string> | |||
#include <vector> | |||
#include <map> | |||
#include <istream> | |||
////////////////////////////////////////////////////////////////////// | |||
// Useful typedefs | |||
////////////////////////////////////////////////////////////////////// | |||
typedef std::vector<std::string> StringList; | |||
typedef std::map<std::string,std::string> NameVal; | |||
////////////////////////////////////////////////////////////////////// | |||
// Splits: a util class to divide a line into space sep pieces | |||
////////////////////////////////////////////////////////////////////// | |||
// TODO: implement begin() + end() to make "for( : )" work | |||
// TODO: implement field enclosing & escaping chars | |||
struct Splits { | |||
/// CONFIG /// | |||
enum { FieldMax=256, LineMax=1024 }; | |||
/// properties /// | |||
char line[LineMax]; // Line buffer | |||
int len; // Length of line (after split()) | |||
char sep; // Separator character. | |||
bool combine; // Treat multiple consecutive seps as one (combine) | |||
char *fields[FieldMax]; // pointers to fields in line | |||
int count; // How many fields there were | |||
// construct | |||
Splits(): count(0), len(0), sep(' '), combine(true) { line[LineMax-1] = 0; } | |||
Splits(const std::string &_line) { *this=_line; } | |||
// Assign from string | |||
inline Splits &operator=(const std::string &_line) { return *this=_line.c_str(); } | |||
Splits &operator=(const char *_line); | |||
// Convert field[] to string | |||
inline std::string operator[](int i) const { std::string s(fields[i]); return s; } | |||
// split line. Returns count. | |||
int split(); | |||
}; | |||
// istream >> operator: getline() + .split() | |||
std::istream &operator>>(std::istream &in, Splits &sp); | |||
////////////////////////////////////////////////////////////////////// | |||
// TSV version of Splits | |||
////////////////////////////////////////////////////////////////////// | |||
struct TSV: public Splits { | |||
TSV() { sep='\t'; combine=false; } | |||
// Need some weird casties to make C++ remember its base class | |||
TSV(const std::string &_line): Splits(_line) {} | |||
inline TSV &operator=(const std::string &_line) { return *this=_line.c_str(); } | |||
inline TSV &operator=(const char *_line) { *((Splits *)this)=_line; return *this; } | |||
}; | |||
////////////////////////////////////////////////////////////////////// | |||
// Function to match a list of prefixes against a string | |||
// | |||
// Since C++ < 11 doesn't support constant vector initialization we'll | |||
// do this the old fashioned way with a null terminated char*[]. | |||
////////////////////////////////////////////////////////////////////// | |||
bool pre_match(const char **list, const std::string &s); | |||
// And if vectors can be used... | |||
bool pre_match(const StringList &list, const std::string &s); | |||
////////////////////////////////////////////////////////////////////// | |||
// Strip leading and trailing space from a string | |||
////////////////////////////////////////////////////////////////////// | |||
std::string strip(const std::string &s); | |||
#endif |