|
- //////////////////////////////////////////////////////////////////////
- // 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 <arpa/inet.h>
- #include <string.h>
- #include <stdlib.h>
- #include <stdexcept>
- #include <iostream>
- #include "data.h"
-
-
-
- //////////////////////////////////////////////////////////////////////
- // Utils
- //////////////////////////////////////////////////////////////////////
-
- std::string ipv6opt(const std::string &addr) {
- in6_addr buf;
- char s[256];
-
- if(inet_pton(AF_INET6, addr.c_str(), &buf)<1) throw
- std::runtime_error("ipv6opt: inet_pton() says '"+addr+"' is not a valid IPv6 address");
- if(!inet_ntop(AF_INET6, &buf, s, 255)) throw // should never happen
- std::runtime_error("ipv6opt: inet_ntop() refused to convert the address back");
- return std::string(s);
- }
-
-
-
- int addr_wild_comp(const std::string &str1, const std::string &str2) {
- int spre1=0, spre2=0;
-
- if(str1=="*" || str2=="*") return 0;
- if(str1!="" && (str1.end()[-1]=='.' || str1.end()[-1]==':')) spre1=str1.size();
- if(str2!="" && (str2.end()[-1]=='.' || str2.end()[-1]==':')) spre2=str2.size();
- if(spre2>spre1) spre1=spre2;
- if(spre1) return strncmp(str1.c_str(), str2.c_str(), spre1);
- else if(str1<str2) return -1;
- else if(str1>str2) return 1;
- return 0;
- }
-
-
-
- //////////////////////////////////////////////////////////////////////
- // Conn
- //////////////////////////////////////////////////////////////////////
-
- void Conn::clear() {
- us = them = name = protocol = "";
- in=false;
- us_port = them_port = 0;
- }
-
-
-
- void Conn::compact() {
- if(us.find(':')!=us.npos) us=ipv6opt(us);
- if(them.find(':')!=us.npos) them=ipv6opt(them);
- }
-
-
-
- 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 {
- int r;
-
- if(r = addr_wild_comp(us, gtr.us)) return r;
- // 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(r = addr_wild_comp(them, gtr.them)) return r;
- 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!="*") {
- 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;
- }
-
-
-
- //////////////////////////////////////////////////////////////////////
- // LogAnalyzer
- //////////////////////////////////////////////////////////////////////
-
- LogAnalyzer::LogAnalyzer():
- us(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-");
- }
-
-
-
- bool LogAnalyzer::line(const std::string &in) {
- int ict=0;
- NameVal::iterator nvp;
- std::string name, address, s;
-
- /// setup ///
-
- if(!us)
- throw std::runtime_error("LogAnalyzer::line: us list is not assigned");
- ln=in;
-
- /// 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" || ln[5]=="cached") {
- name = ln[6];
- address = ln[8];
-
- /* NOTE: CNAME resolution seems to follow this order in logs:
-
- 1. A result line (reply/cached) with an address of <CNAME>
- 2. One or more consecutive result lines for the canonical name
-
- Looking over the logs it doesn't appear that dnsmasq will log
- anything between the original and CNAME resolutions. The exception
- is if a CNAME record is cached and it has to resolve what it
- points to. In this case there would be a "cached" and then a
- "forwarded" record eventually followed by "reply ... <CNAME>".
- In that case we want to operate on the reply.
-
- I just saw that CNAME log entries can be chained. It looks like
- they are an "is <CNAME>" entry followed by another. We want to
- keep the original name (alias).
- */
- // we're handling a CNAME entry
- if(address=="<CNAME>") {
- // If we don't have a cname yet then this is a CNAME to a CNAME.
- if(alias=="" || cname!="") {
- alias = name;
- cname = "";
- }
- return 0;
- }
- // If in cname _mode_:
- if(alias!="") {
- if(cname=="") {
- cname = name; // This is our target name
- name = alias; // substitute the alias
- } else if(cname==name) {
- name = alias; // substitute the alias
- } else {
- cname = ""; // These are different records reset
- name = "";
- }
- }
-
- // Hmm... is this reply an address?
- if(pre_match(dns_ignore, address)) return 0; // nope
- if(pre_match(dns_del, address)) return 0; // does not exist reply
- if((nvp=rdns.find(address))!=rdns.end()) {
- if(nvp->second==name) return 0;
- //dlog("WARN: DNS address overlap "+address+": "+nvp->second+" : "+name);
- }
- rdns[address] = name;
- //dlog("Added "+address+" = "+name);
- return 0;
- } else if(alias!="") {
- // we've fallen out of CNAME resolution.
- alias = "";
- cname = "";
- }
- }
-
- /// process connections ///
-
- if((ln.count>5 // old Linux style
- && ln[4]=="kernel:"
- && ln[5]=="ACCEPT"
- ) || (ln.count>6 // new Linux style
- && ln[4]=="vmunix:"
- && ln[6]=="ACCEPT")
- ) {
- conn = ln;
- conn.compact();
- if(!pre_match(*us, conn.us)) conn.swap();
- if((nvp=rdns.find(conn.them))!=rdns.end()) conn.name = nvp->second;
- return 1;
- }
- return 0;
- }
|