Move sources to 'src' directory

This commit is contained in:
Arnout Engelen
2016-03-27 23:14:33 +02:00
parent fa8e7cb49e
commit e7a74b00fb
29 changed files with 14 additions and 14 deletions

77
src/MakeApp.mk Normal file
View File

@@ -0,0 +1,77 @@
#prefix := /usr
prefix := /usr/local
sbin := $(prefix)/sbin
man8 := $(prefix)/share/man/man8
all: nethogs decpcap_test
# nethogs_testsum
CFLAGS?=-Wall -Wextra
CXXFLAGS?=-Wall -Wextra
OBJS=packet.o connection.o process.o decpcap.o cui.o inode2prog.o conninode.o devices.o
NCURSES_LIBS?=-lncurses
.PHONY: tgz
.PHONY: check uninstall
check:
@echo "Not implemented"
install: nethogs nethogs.8
install -d -m 755 $(DESTDIR)$(sbin)
install -m 755 nethogs $(DESTDIR)$(sbin)
install -d -m 755 $(DESTDIR)$(man8)
install -m 644 nethogs.8 $(DESTDIR)$(man8)
@echo
@echo "Installed nethogs to $(DESTDIR)$(sbin)"
@echo
@echo "You might have to add this directory to your PATH and/or refresh your shells' path cache with a command like 'hash -r'."
uninstall:
rm $(DESTDIR)$(sbin)/nethogs
rm $(DESTDIR)$(man8)/nethogs.8
nethogs: main.cpp nethogs.cpp $(OBJS)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LDFLAGS) main.cpp $(OBJS) -o nethogs -lpcap -lm ${NCURSES_LIBS} -DVERSION=\"$(VERSION)\" -DSUBVERSION=\"$(SUBVERSION)\" -DMINORVERSION=\"$(MINORVERSION)\"
nethogs_testsum: nethogs_testsum.cpp $(OBJS)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LDFLAGS) nethogs_testsum.cpp $(OBJS) -o nethogs_testsum -lpcap -lm ${NCURSES_LIBS} -DVERSION=\"$(VERSION)\" -DSUBVERSION=\"$(SUBVERSION)\" -DMINORVERSION=\"$(MINORVERSION)\"
decpcap_test: decpcap_test.cpp decpcap.o
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LDFLAGS) decpcap_test.cpp decpcap.o -o decpcap_test -lpcap -lm
#-lefence
process.o: process.cpp process.h nethogs.h
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c process.cpp
packet.o: packet.cpp packet.h nethogs.h
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c packet.cpp
connection.o: connection.cpp connection.h nethogs.h
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c connection.cpp
decpcap.o: decpcap.c decpcap.h
$(CC) $(CPPFLAGS) $(CFLAGS) -c decpcap.c
inode2prog.o: inode2prog.cpp inode2prog.h nethogs.h
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c inode2prog.cpp
conninode.o: conninode.cpp nethogs.h conninode.h
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c conninode.cpp
#devices.o: devices.cpp devices.h
# $(CXX) $(CXXFLAGS) -c devices.cpp
cui.o: cui.cpp cui.h nethogs.h
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c cui.cpp -DVERSION=\"$(VERSION)\" -DSUBVERSION=\"$(SUBVERSION)\" -DMINORVERSION=\"$(MINORVERSION)\"
TESTS=conninode_test
.PHONY: test
test: $(TESTS)
for test in $(TESTS); do echo $$test ; ./$$test ; done
.PHONY: clean
clean:
rm -f $(OBJS)
rm -f $(TESTS)
rm -f nethogs
rm -f test
rm -f decpcap_test

112
src/MakeLib.mk Normal file
View File

@@ -0,0 +1,112 @@
LIBRARY=libnethogs.so
LIBNAME=$(LIBRARY).$(LIBVERSION).$(LIBSUBVERSION).$(LIBMINORVERSION)
SO_NAME=$(LIBRARY).$(LIBVERSION)
prefix := /usr/local
libdir := $(prefix)/lib
incdir := $(prefix)/include
all: $(LIBNAME) libnethogs.a
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
LDFLAGS:= -shared -Wl,-soname,$(SO_NAME)
else
LDFLAGS:= -shared -Wl,-install_name,$(SO_NAME)
endif
CXXINCLUDES :=
VISIBILITY=-fvisibility=hidden
ODIR_BASE := obj
ifeq ($(DEBUG),1)
# Debug mode options
$(info Bulding debug version)
ODIR:=$(ODIR_BASE)/lib/debug
CFLAGS?=-Wall -Wextra -O0 -g -fPIC $(VISIBILITY)
CXXFLAGS?=-Wall -Wextra --std=c++0x -O0 -g -fPIC $(VISIBILITY) $(CXXINCLUDES)
else
# Release mode options
ODIR:=$(ODIR_BASE)/lib/release
CFLAGS?=-Wall -Wextra -O3 -fPIC $(VISIBILITY)
CXXFLAGS?=-Wall -Wextra --std=c++0x -O3 -fPIC $(VISIBILITY) $(CXXINCLUDES)
endif
OBJ_NAMES= libnethogs.o packet.o connection.o process.o decpcap.o inode2prog.o conninode.o devices.o
OBJS=$(addprefix $(ODIR)/,$(OBJ_NAMES))
#$(info $(OBJS))
.PHONY: tgz
.PHONY: uninstall
install: $(LIBNAME)
install -d -m 755 $(DESTDIR)$(libdir)
install -m 755 $(LIBNAME) $(DESTDIR)$(libdir)
@echo "Installed $(LIBNAME) to $(DESTDIR)$(libdir)"
ldconfig
install_dev: install
@ln -f -s $(DESTDIR)$(libdir)/$(LIBNAME) $(DESTDIR)$(libdir)/$(LIBRARY)
install -m 755 libnethogs.a $(DESTDIR)$(libdir)
@echo "Installed libnethogs.a to $(DESTDIR)$(libdir)"
install -d -m 755 $(DESTDIR)$(incdir)
install -m 755 libnethogs.h $(DESTDIR)$(incdir)
@echo "Installed libnethogs.h to $(DESTDIR)$(incdir)"
ldconfig
uninstall:
rm -f $(DESTDIR)$(libdir)/$(LIBNAME)
rm -f $(DESTDIR)$(libdir)/$(LIBRARY)
rm -f $(DESTDIR)$(libdir)/libnethogs.a
rm -f $(DESTDIR)$(incdir)/libnethogs.h
ldconfig
$(LIBNAME): $(OBJS)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LDFLAGS) $(OBJS) -o $@ -lpcap
libnethogs.a: $(OBJS)
$(AR) rcs $@ $(OBJS)
#-lefence
$(ODIR)/process.o: process.cpp process.h nethogs.h
@mkdir -p $(ODIR)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -o $@ -c process.cpp
$(ODIR)/packet.o: packet.cpp packet.h nethogs.h
@mkdir -p $(ODIR)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -o $@ -c packet.cpp
$(ODIR)/connection.o: connection.cpp connection.h nethogs.h
@mkdir -p $(ODIR)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -o $@ -c connection.cpp
$(ODIR)/decpcap.o: decpcap.c decpcap.h
@mkdir -p $(ODIR)
$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ -c decpcap.c
$(ODIR)/inode2prog.o: inode2prog.cpp inode2prog.h nethogs.h
@mkdir -p $(ODIR)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -o $@ -c inode2prog.cpp
$(ODIR)/conninode.o: conninode.cpp nethogs.h conninode.h
@mkdir -p $(ODIR)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -o $@ -c conninode.cpp
$(ODIR)/devices.o: devices.cpp devices.h
@mkdir -p $(ODIR)
$(CXX) $(CXXFLAGS) -o $@ -c devices.cpp
$(ODIR)/libnethogs.o: libnethogs.cpp libnethogs.h
@mkdir -p $(ODIR)
$(CXX) $(CXXFLAGS) -o $@ -c libnethogs.cpp -DVERSION=\"$(LIBVERSION)\" -DSUBVERSION=\"$(LIBSUBVERSION)\" -DMINORVERSION=\"$(LIBMINORVERSION)\"
.PHONY: clean
clean:
rm -f $(OBJS)
rm -f $(LIBNAME)
rm -f libnethogs.a
mkdir -p $(ODIR)
rmdir -p --ignore-fail-on-non-empty $(ODIR)

212
src/connection.cpp Normal file
View File

@@ -0,0 +1,212 @@
/*
* connection.cpp
*
* Copyright (c) 2004-2006,2008 Arnout Engelen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
*USA.
*
*/
#include <iostream>
#include <cassert>
#ifdef __APPLE__
#include <sys/malloc.h>
#else
#include <malloc.h>
#endif
#include "nethogs.h"
#include "connection.h"
#include "process.h"
ConnList *connections = NULL;
void PackList::add(Packet *p) {
if (content == NULL) {
content = new PackListNode(new Packet(*p));
return;
}
if (content->val->time.tv_sec == p->time.tv_sec) {
content->val->len += p->len;
return;
}
/* store copy of packet, so that original may be freed */
content = new PackListNode(new Packet(*p), content);
}
/* sums up the total bytes used and removes 'old' packets */
u_int32_t PackList::sumanddel(timeval t) {
u_int32_t retval = 0;
PackListNode *current = content;
PackListNode *previous = NULL;
while (current != NULL) {
// std::cout << "Comparing " << current->val->time.tv_sec << " <= " <<
// t.tv_sec - PERIOD << endl;
if (current->val->time.tv_sec <= t.tv_sec - PERIOD) {
if (current == content)
content = NULL;
else if (previous != NULL)
previous->next = NULL;
delete current;
return retval;
}
retval += current->val->len;
previous = current;
current = current->next;
}
return retval;
}
/* packet may be deleted by caller */
Connection::Connection(Packet *packet) {
assert(packet != NULL);
connections = new ConnList(this, connections);
sent_packets = new PackList();
recv_packets = new PackList();
sumSent = 0;
sumRecv = 0;
if (DEBUG) {
std::cout << "New connection, with package len " << packet->len
<< std::endl;
}
if (packet->Outgoing()) {
sumSent += packet->len;
sent_packets->add(packet);
refpacket = new Packet(*packet);
} else {
sumRecv += packet->len;
recv_packets->add(packet);
refpacket = packet->newInverted();
}
lastpacket = packet->time.tv_sec;
if (DEBUG)
std::cout << "New reference packet created at " << refpacket << std::endl;
}
Connection::~Connection() {
if (DEBUG)
std::cout << "Deleting connection" << std::endl;
/* refpacket is not a pointer to one of the packets in the lists
* so deleted */
delete (refpacket);
if (sent_packets != NULL)
delete sent_packets;
if (recv_packets != NULL)
delete recv_packets;
ConnList *curr_conn = connections;
ConnList *prev_conn = NULL;
while (curr_conn != NULL) {
if (curr_conn->getVal() == this) {
ConnList *todelete = curr_conn;
curr_conn = curr_conn->getNext();
if (prev_conn == NULL) {
connections = curr_conn;
} else {
prev_conn->setNext(curr_conn);
}
delete (todelete);
} else {
prev_conn = curr_conn;
curr_conn = curr_conn->getNext();
}
}
}
/* the packet will be freed by the calling code */
void Connection::add(Packet *packet) {
lastpacket = packet->time.tv_sec;
if (packet->Outgoing()) {
if (DEBUG) {
std::cout << "Outgoing: " << packet->len << std::endl;
}
sumSent += packet->len;
sent_packets->add(packet);
} else {
if (DEBUG) {
std::cout << "Incoming: " << packet->len << std::endl;
}
sumRecv += packet->len;
if (DEBUG) {
std::cout << "sumRecv now: " << sumRecv << std::endl;
}
recv_packets->add(packet);
}
}
Connection *findConnectionWithMatchingSource(Packet *packet) {
assert(packet->Outgoing());
ConnList *current = connections;
while (current != NULL) {
/* the reference packet is always outgoing */
if (packet->matchSource(current->getVal()->refpacket)) {
return current->getVal();
}
current = current->getNext();
}
return NULL;
}
Connection *findConnectionWithMatchingRefpacketOrSource(Packet *packet) {
ConnList *current = connections;
while (current != NULL) {
/* the reference packet is always *outgoing* */
if (packet->match(current->getVal()->refpacket)) {
return current->getVal();
}
current = current->getNext();
}
return findConnectionWithMatchingSource(packet);
}
/*
* finds connection to which this packet belongs.
* a packet belongs to a connection if it matches
* to its reference packet
*/
Connection *findConnection(Packet *packet) {
if (packet->Outgoing())
return findConnectionWithMatchingRefpacketOrSource(packet);
else {
Packet *invertedPacket = packet->newInverted();
Connection *result =
findConnectionWithMatchingRefpacketOrSource(invertedPacket);
delete invertedPacket;
return result;
}
}
/*
* Connection::sumanddel
*
* sums up the total bytes used
* and removes 'old' packets.
*
* Returns sum of sent packages (by address)
* sum of recieved packages (by address)
*/
void Connection::sumanddel(timeval t, u_int32_t *recv, u_int32_t *sent) {
(*sent) = (*recv) = 0;
*sent = sent_packets->sumanddel(t);
*recv = recv_packets->sumanddel(t);
}

106
src/connection.h Normal file
View File

@@ -0,0 +1,106 @@
/*
* connection.h
*
* Copyright (c) 2004-2006,2008 Arnout Engelen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
*USA.
*
*/
#ifndef __CONNECTION_H
#define __CONNECTION_H
#include <iostream>
#include "packet.h"
class PackListNode {
public:
PackListNode(Packet *m_val, PackListNode *m_next = NULL) {
val = m_val;
next = m_next;
}
~PackListNode() {
delete val;
if (next != NULL)
delete next;
}
PackListNode *next;
Packet *val;
};
class PackList {
public:
PackList() { content = NULL; }
PackList(Packet *m_val) {
assert(m_val != NULL);
content = new PackListNode(m_val);
}
~PackList() {
if (content != NULL)
delete content;
}
/* sums up the total bytes used and removes 'old' packets */
u_int32_t sumanddel(timeval t);
/* calling code may delete packet */
void add(Packet *p);
private:
PackListNode *content;
};
class Connection {
public:
/* constructs a connection, makes a copy of
* the packet as 'refpacket', and adds the
* packet to the packlist */
/* packet may be deleted by caller */
Connection(Packet *packet);
~Connection();
/* add a packet to the packlist
* will delete the packet structure
* when it is 'merged with' (added to) another
* packet
*/
void add(Packet *packet);
int getLastPacket() { return lastpacket; }
/* sums up the total bytes used
* and removes 'old' packets. */
void sumanddel(timeval curtime, u_int32_t *recv, u_int32_t *sent);
/* for checking if a packet is part of this connection */
/* the reference packet is always *outgoing*. */
Packet *refpacket;
/* total sum or sent/received bytes */
u_int32_t sumSent;
u_int32_t sumRecv;
private:
PackList *sent_packets;
PackList *recv_packets;
int lastpacket;
};
/* Find the connection this packet belongs to */
/* (the calling code may free the packet afterwards) */
Connection *findConnection(Packet *packet);
#endif

198
src/conninode.cpp Normal file
View File

@@ -0,0 +1,198 @@
/*
* conninode.cpp
*
* Copyright (c) 2008,2009 Arnout Engelen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
*USA.
*
*/
#include <netinet/in.h>
#include <map>
#include <cstdio>
#include <stdlib.h>
#include "nethogs.h"
#include "conninode.h"
#if defined __APPLE__
#ifndef s6_addr32
#define s6_addr32 __u6_addr.__u6_addr32
#endif
#endif
extern local_addr *local_addrs;
extern bool bughuntmode;
/*
* connection-inode table. takes information from /proc/net/tcp.
* key contains source ip, source port, destination ip, destination
* port in format: '1.2.3.4:5-1.2.3.4:5'
*/
std::map<std::string, unsigned long> conninode;
/*
* parses a /proc/net/tcp-line of the form:
* sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt
*uid timeout inode
* 10: 020310AC:1770 9DD8A9C3:A525 01 00000000:00000000 00:00000000 00000000
*0 0 2119 1 c0f4f0c0 206 40 10 3 -1
* 11: 020310AC:0404 936B2ECF:0747 01 00000000:00000000 00:00000000 00000000
*1000 0 2109 1 c0f4fc00 368 40 20 2 -1
*
* and of the form:
* 2: 0000000000000000FFFF0000020310AC:0016
*0000000000000000FFFF00009DD8A9C3:A526 01 00000000:00000000 02:000A7214
*00000000 0 0 2525 2 c732eca0 201 40 1 2 -1
*
*/
void addtoconninode(char *buffer) {
short int sa_family;
struct in6_addr result_addr_local;
struct in6_addr result_addr_remote;
char rem_addr[128], local_addr[128];
int local_port, rem_port;
struct in6_addr in6_local;
struct in6_addr in6_remote;
if (bughuntmode) {
std::cout << "ci: " << buffer;
}
unsigned long inode;
int matches = sscanf(buffer, "%*d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %*X "
"%*X:%*X %*X:%*X %*X %*d %*d %ld %*512s\n",
local_addr, &local_port, rem_addr, &rem_port, &inode);
if (matches != 5) {
fprintf(stderr, "Unexpected buffer: '%s'\n", buffer);
exit(0);
}
if (inode == 0) {
/* connection is in TIME_WAIT state. We rely on
* the old data still in the table. */
return;
}
if (strlen(local_addr) > 8) {
/* this is an IPv6-style row */
/* Demangle what the kernel gives us */
sscanf(local_addr, "%08X%08X%08X%08X", &in6_local.s6_addr32[0],
&in6_local.s6_addr32[1], &in6_local.s6_addr32[2],
&in6_local.s6_addr32[3]);
sscanf(rem_addr, "%08X%08X%08X%08X", &in6_remote.s6_addr32[0],
&in6_remote.s6_addr32[1], &in6_remote.s6_addr32[2],
&in6_remote.s6_addr32[3]);
if ((in6_local.s6_addr32[0] == 0x0) && (in6_local.s6_addr32[1] == 0x0) &&
(in6_local.s6_addr32[2] == 0xFFFF0000)) {
/* IPv4-compatible address */
result_addr_local = *((struct in6_addr *)&(in6_local.s6_addr32[3]));
result_addr_remote = *((struct in6_addr *)&(in6_remote.s6_addr32[3]));
sa_family = AF_INET;
} else {
/* real IPv6 address */
// inet_ntop(AF_INET6, &in6_local, addr6, sizeof(addr6));
// INET6_getsock(addr6, (struct sockaddr *) &localaddr);
// inet_ntop(AF_INET6, &in6_remote, addr6, sizeof(addr6));
// INET6_getsock(addr6, (struct sockaddr *) &remaddr);
// localaddr.sin6_family = AF_INET6;
// remaddr.sin6_family = AF_INET6;
result_addr_local = in6_local;
result_addr_remote = in6_remote;
sa_family = AF_INET6;
}
} else {
/* this is an IPv4-style row */
sscanf(local_addr, "%X", (unsigned int *)&result_addr_local);
sscanf(rem_addr, "%X", (unsigned int *)&result_addr_remote);
sa_family = AF_INET;
}
char *hashkey = (char *)malloc(HASHKEYSIZE * sizeof(char));
char *local_string = (char *)malloc(50);
char *remote_string = (char *)malloc(50);
inet_ntop(sa_family, &result_addr_local, local_string, 49);
inet_ntop(sa_family, &result_addr_remote, remote_string, 49);
snprintf(hashkey, HASHKEYSIZE * sizeof(char), "%s:%d-%s:%d", local_string,
local_port, remote_string, rem_port);
free(local_string);
// if (DEBUG)
// fprintf (stderr, "Hashkey: %s\n", hashkey);
// std::cout << "Adding to conninode\n" << std::endl;
conninode[hashkey] = inode;
/* workaround: sometimes, when a connection is actually from 172.16.3.1 to
* 172.16.3.3, packages arrive from 195.169.216.157 to 172.16.3.3, where
* 172.16.3.1 and 195.169.216.157 are the local addresses of different
* interfaces */
for (class local_addr *current_local_addr = local_addrs;
current_local_addr != NULL;
current_local_addr = current_local_addr->next) {
/* TODO maybe only add the ones with the same sa_family */
snprintf(hashkey, HASHKEYSIZE * sizeof(char), "%s:%d-%s:%d",
current_local_addr->string, local_port, remote_string, rem_port);
conninode[hashkey] = inode;
}
free(hashkey);
free(remote_string);
}
/* opens /proc/net/tcp[6] and adds its contents line by line */
int addprocinfo(const char *filename) {
FILE *procinfo = fopen(filename, "r");
char buffer[8192];
if (procinfo == NULL)
return 0;
fgets(buffer, sizeof(buffer), procinfo);
do {
if (fgets(buffer, sizeof(buffer), procinfo))
addtoconninode(buffer);
} while (!feof(procinfo));
fclose(procinfo);
return 1;
}
void refreshconninode() {
/* we don't forget old mappings, just overwrite */
// delete conninode;
// conninode = new HashTable (256);
#if defined(__APPLE__)
addprocinfo("net.inet.tcp.pcblist");
#else
if (!addprocinfo("/proc/net/tcp")) {
std::cout << "Error: couldn't open /proc/net/tcp\n";
exit(0);
}
addprocinfo("/proc/net/tcp6");
#endif
// if (DEBUG)
// reviewUnknown();
}

23
src/conninode.h Normal file
View File

@@ -0,0 +1,23 @@
/*
* conninode.h
*
* Copyright (c) 2008 Arnout Engelen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
*USA.
*
*/
// handling the connection->inode mapping
void refreshconninode();

24
src/conninode_test.cpp Normal file
View File

@@ -0,0 +1,24 @@
#include "conninode.cpp"
local_addr *local_addrs = NULL;
bool bughuntmode = false;
int main() {
if (!addprocinfo("testfiles/proc_net_tcp")) {
std::cerr << "Failed to load testfiles/proc_net_tcp" << std::endl;
return 1;
}
if (!addprocinfo("testfiles/proc_net_tcp_big")) {
std::cerr << "Failed to load testfiles/proc_net_tcp_big" << std::endl;
return 2;
}
#if not defined(__APPLE__)
if (!addprocinfo("/proc/net/tcp")) {
std::cerr << "Failed to load /proc/net/tcp" << std::endl;
return 3;
}
#endif
return 0;
}

442
src/cui.cpp Normal file
View File

@@ -0,0 +1,442 @@
/*
* cui.cpp
*
* Copyright (c) 2004-2006,2008,2010,2011 Arnout Engelen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
*USA.
*
*/
/* NetHogs console UI */
#include <string>
#include <pwd.h>
#include <sys/types.h>
#include <cstdlib>
#include <cerrno>
#include <cstdlib>
#include <algorithm>
#include <ncurses.h>
#include "nethogs.h"
#include "process.h"
std::string *caption;
extern const char version[];
extern ProcList *processes;
extern timeval curtime;
extern Process *unknowntcp;
extern Process *unknownudp;
extern Process *unknownip;
extern bool sortRecv;
extern int viewMode;
extern unsigned refreshlimit;
extern unsigned refreshcount;
#define PID_MAX 4194303
const int COLUMN_WIDTH_PID = 7;
const int COLUMN_WIDTH_USER = 8;
const int COLUMN_WIDTH_DEV = 5;
const int COLUMN_WIDTH_SENT = 11;
const int COLUMN_WIDTH_RECEIVED = 11;
const int COLUMN_WIDTH_UNIT = 6;
const char *COLUMN_FORMAT_PID = "%7d";
const char *COLUMN_FORMAT_SENT = "%11.3f";
const char *COLUMN_FORMAT_RECEIVED = "%11.3f";
class Line {
public:
Line(const char *name, double n_recv_value, double n_sent_value, pid_t pid,
uid_t uid, const char *n_devicename) {
assert(pid >= 0);
assert(pid <= PID_MAX);
m_name = name;
sent_value = n_sent_value;
recv_value = n_recv_value;
devicename = n_devicename;
m_pid = pid;
m_uid = uid;
assert(m_pid >= 0);
}
void show(int row, unsigned int proglen);
void log();
double sent_value;
double recv_value;
private:
const char *m_name;
const char *devicename;
pid_t m_pid;
uid_t m_uid;
};
#include <sstream>
std::string itoa(int i) {
std::stringstream out;
out << i;
return out.str();
}
/**
* @returns the username that corresponds to this uid
*/
std::string uid2username(uid_t uid) {
struct passwd *pwd = NULL;
errno = 0;
/* points to a static memory area, should not be freed */
pwd = getpwuid(uid);
if (pwd == NULL)
if (errno == 0)
return itoa(uid);
else
forceExit(false, "Error calling getpwuid(3) for uid %d: %d %s", uid,
errno, strerror(errno));
else
return std::string(pwd->pw_name);
}
/**
* Render the provided text at the specified location, truncating if the length
* of the text exceeds a maximum. If the
* text must be truncated, the string ".." will be rendered, followed by max_len
* - 2 characters of the provided text.
*/
static void mvaddstr_truncate_leading(int row, int col, const char *str,
std::size_t str_len,
std::size_t max_len) {
if (str_len < max_len) {
mvaddstr(row, col, str);
} else {
mvaddstr(row, col, "..");
addnstr(str + 2, max_len - 2);
}
}
/**
* Render the provided text at the specified location, truncating if the length
* of the text exceeds a maximum. If the
* text must be truncated, the text will be rendered up to max_len - 2
* characters and then ".." will be rendered.
*/
static void mvaddstr_truncate_trailing(int row, int col, const char *str,
std::size_t str_len,
std::size_t max_len) {
if (str_len < max_len) {
mvaddstr(row, col, str);
} else {
mvaddnstr(row, col, str, max_len - 2);
addstr("..");
}
}
void Line::show(int row, unsigned int proglen) {
assert(m_pid >= 0);
assert(m_pid <= PID_MAX);
const int column_offset_pid = 0;
const int column_offset_user = column_offset_pid + COLUMN_WIDTH_PID + 1;
const int column_offset_program = column_offset_user + COLUMN_WIDTH_USER + 1;
const int column_offset_dev = column_offset_program + proglen + 2;
const int column_offset_sent = column_offset_dev + COLUMN_WIDTH_DEV + 1;
const int column_offset_received = column_offset_sent + COLUMN_WIDTH_SENT + 1;
const int column_offset_unit =
column_offset_received + COLUMN_WIDTH_RECEIVED + 1;
// PID column
if (m_pid == 0)
mvaddch(row, column_offset_pid + COLUMN_WIDTH_PID - 1, '?');
else
mvprintw(row, column_offset_pid, COLUMN_FORMAT_PID, m_pid);
std::string username = uid2username(m_uid);
mvaddstr_truncate_trailing(row, column_offset_user, username.c_str(),
username.size(), COLUMN_WIDTH_USER);
mvaddstr_truncate_leading(row, column_offset_program, m_name, strlen(m_name),
proglen);
mvaddstr(row, column_offset_dev, devicename);
mvprintw(row, column_offset_sent, COLUMN_FORMAT_SENT, sent_value);
mvprintw(row, column_offset_received, COLUMN_FORMAT_RECEIVED, recv_value);
if (viewMode == VIEWMODE_KBPS) {
mvaddstr(row, column_offset_unit, "KB/sec");
} else if (viewMode == VIEWMODE_TOTAL_MB) {
mvaddstr(row, column_offset_unit, "MB ");
} else if (viewMode == VIEWMODE_TOTAL_KB) {
mvaddstr(row, column_offset_unit, "KB ");
} else if (viewMode == VIEWMODE_TOTAL_B) {
mvaddstr(row, column_offset_unit, "B ");
}
}
void Line::log() {
std::cout << m_name << '/' << m_pid << '/' << m_uid << "\t" << sent_value
<< "\t" << recv_value << std::endl;
}
int GreatestFirst(const void *ma, const void *mb) {
Line **pa = (Line **)ma;
Line **pb = (Line **)mb;
Line *a = *pa;
Line *b = *pb;
double aValue;
if (sortRecv) {
aValue = a->recv_value;
} else {
aValue = a->sent_value;
}
double bValue;
if (sortRecv) {
bValue = b->recv_value;
} else {
bValue = b->sent_value;
}
if (aValue > bValue) {
return -1;
}
if (aValue == bValue) {
return 0;
}
return 1;
}
void init_ui() {
WINDOW *screen = initscr();
raw();
noecho();
cbreak();
nodelay(screen, TRUE);
caption = new std::string("NetHogs");
caption->append(getVersion());
// caption->append(", running at ");
}
void exit_ui() {
clear();
endwin();
delete caption;
}
void ui_tick() {
switch (getch()) {
case 'q':
/* quit */
quit_cb(0);
break;
case 's':
/* sort on 'sent' */
sortRecv = false;
break;
case 'r':
/* sort on 'received' */
sortRecv = true;
break;
case 'm':
/* switch mode: total vs kb/s */
viewMode = (viewMode + 1) % VIEWMODE_COUNT;
break;
}
}
void show_trace(Line *lines[], int nproc) {
std::cout << "\nRefreshing:\n";
/* print them */
for (int i = 0; i < nproc; i++) {
lines[i]->log();
delete lines[i];
}
/* print the 'unknown' connections, for debugging */
ConnList *curr_unknownconn = unknowntcp->connections;
while (curr_unknownconn != NULL) {
std::cout << "Unknown connection: "
<< curr_unknownconn->getVal()->refpacket->gethashstring()
<< std::endl;
curr_unknownconn = curr_unknownconn->getNext();
}
}
void show_ncurses(Line *lines[], int nproc) {
int rows; // number of terminal rows
int cols; // number of terminal columns
unsigned int proglen; // max length of the "PROGRAM" column
double sent_global = 0;
double recv_global = 0;
getmaxyx(stdscr, rows, cols); /* find the boundaries of the screeen */
if (cols < 62) {
clear();
mvprintw(0, 0,
"The terminal is too narrow! Please make it wider.\nI'll wait...");
return;
}
if (cols > PROGNAME_WIDTH)
cols = PROGNAME_WIDTH;
proglen = cols - 55;
clear();
mvprintw(0, 0, "%s", caption->c_str());
attron(A_REVERSE);
mvprintw(2, 0,
" PID USER %-*.*s DEV SENT RECEIVED ",
proglen, proglen, "PROGRAM");
attroff(A_REVERSE);
/* print them */
int i;
for (i = 0; i < nproc; i++) {
if (i + 3 < rows)
lines[i]->show(i + 3, proglen);
recv_global += lines[i]->recv_value;
sent_global += lines[i]->sent_value;
delete lines[i];
}
attron(A_REVERSE);
int totalrow = std::min(rows - 1, 3 + 1 + i);
mvprintw(totalrow, 0, " TOTAL %-*.*s %11.3f %11.3f ",
proglen, proglen, " ", sent_global, recv_global);
if (viewMode == VIEWMODE_KBPS) {
mvprintw(3 + 1 + i, cols - COLUMN_WIDTH_UNIT, "KB/sec ");
} else if (viewMode == VIEWMODE_TOTAL_B) {
mvprintw(3 + 1 + i, cols - COLUMN_WIDTH_UNIT, "B ");
} else if (viewMode == VIEWMODE_TOTAL_KB) {
mvprintw(3 + 1 + i, cols - COLUMN_WIDTH_UNIT, "KB ");
} else if (viewMode == VIEWMODE_TOTAL_MB) {
mvprintw(3 + 1 + i, cols - COLUMN_WIDTH_UNIT, "MB ");
}
attroff(A_REVERSE);
mvprintw(totalrow + 1, 0, "");
refresh();
}
// Display all processes and relevant network traffic using show function
void do_refresh() {
refreshconninode();
refreshcount++;
ProcList *curproc = processes;
ProcList *previousproc = NULL;
int nproc = processes->size();
/* initialise to null pointers */
Line *lines[nproc];
int n = 0;
#ifndef NDEBUG
// initialise to null pointers
for (int i = 0; i < nproc; i++)
lines[i] = NULL;
#endif
while (curproc != NULL) {
// walk though its connections, summing up their data, and
// throwing away connections that haven't received a package
// in the last PROCESSTIMEOUT seconds.
assert(curproc != NULL);
assert(curproc->getVal() != NULL);
assert(nproc == processes->size());
/* remove timed-out processes (unless it's one of the the unknown process)
*/
if ((curproc->getVal()->getLastPacket() + PROCESSTIMEOUT <=
curtime.tv_sec) &&
(curproc->getVal() != unknowntcp) &&
(curproc->getVal() != unknownudp) && (curproc->getVal() != unknownip)) {
if (DEBUG)
std::cout << "PROC: Deleting process\n";
ProcList *todelete = curproc;
Process *p_todelete = curproc->getVal();
if (previousproc) {
previousproc->next = curproc->next;
curproc = curproc->next;
} else {
processes = curproc->getNext();
curproc = processes;
}
delete todelete;
delete p_todelete;
nproc--;
// continue;
} else {
// add a non-timed-out process to the list of stuff to show
float value_sent = 0, value_recv = 0;
if (viewMode == VIEWMODE_KBPS) {
// std::cout << "kbps viemode" << std::endl;
curproc->getVal()->getkbps(&value_recv, &value_sent);
} else if (viewMode == VIEWMODE_TOTAL_KB) {
// std::cout << "total viemode" << std::endl;
curproc->getVal()->gettotalkb(&value_recv, &value_sent);
} else if (viewMode == VIEWMODE_TOTAL_MB) {
// std::cout << "total viemode" << std::endl;
curproc->getVal()->gettotalmb(&value_recv, &value_sent);
} else if (viewMode == VIEWMODE_TOTAL_B) {
// std::cout << "total viemode" << std::endl;
curproc->getVal()->gettotalb(&value_recv, &value_sent);
} else {
forceExit(false, "Invalid viewMode: %d", viewMode);
}
uid_t uid = curproc->getVal()->getUid();
assert(curproc->getVal()->pid >= 0);
assert(n < nproc);
lines[n] =
new Line(curproc->getVal()->name, value_recv, value_sent,
curproc->getVal()->pid, uid, curproc->getVal()->devicename);
previousproc = curproc;
curproc = curproc->next;
n++;
#ifndef NDEBUG
assert(nproc == processes->size());
if (curproc == NULL)
assert(n - 1 < nproc);
else
assert(n < nproc);
#endif
}
}
/* sort the accumulated lines */
qsort(lines, nproc, sizeof(Line *), GreatestFirst);
if (tracemode || DEBUG)
show_trace(lines, nproc);
else
show_ncurses(lines, nproc);
if (refreshlimit != 0 && refreshcount >= refreshlimit)
quit_cb(0);
}

30
src/cui.h Normal file
View File

@@ -0,0 +1,30 @@
/*
* cui.h
*
* Copyright (c) 2004 Arnout Engelen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
*USA.
*
*/
/* NetHogs console UI */
void do_refresh();
void init_ui();
void exit_ui();
/* periodically gives some CPU-time to the UI */
void ui_tick();

321
src/decpcap.c Normal file
View File

@@ -0,0 +1,321 @@
/*
* decpcap.c
*
* Copyright (c) 2004-2006,2011 Arnout Engelen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
*USA.
*
*/
#include <net/ethernet.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <netinet/tcp.h>
#include <string.h> // for memcpy
#include <pcap.h>
#include "decpcap.h"
#define DP_DEBUG 0
/* functions to set up a handle (which is basically just a pcap handle) */
struct dp_handle *dp_fillhandle(pcap_t *phandle) {
struct dp_handle *retval =
(struct dp_handle *)malloc(sizeof(struct dp_handle));
int i;
retval->pcap_handle = phandle;
for (i = 0; i < dp_n_packet_types; i++) {
retval->callback[i] = NULL;
}
retval->linktype = pcap_datalink(retval->pcap_handle);
switch (retval->linktype) {
case (DLT_EN10MB):
fprintf(stdout, "Ethernet link detected\n");
break;
case (DLT_PPP):
fprintf(stdout, "PPP link detected\n");
break;
case (DLT_LINUX_SLL):
fprintf(stdout, "Linux Cooked Socket link detected\n");
break;
default:
fprintf(stdout, "No PPP or Ethernet link: %d\n", retval->linktype);
// TODO maybe error? or 'other' callback?
break;
}
return retval;
}
struct dp_handle *dp_open_offline(char *fname, char *ebuf) {
pcap_t *temp = pcap_open_offline(fname, ebuf);
if (temp == NULL) {
return NULL;
}
return dp_fillhandle(temp);
}
struct dp_handle *dp_open_live(const char *device, int snaplen, int promisc,
int to_ms, char *errbuf) {
pcap_t *temp = pcap_open_live(device, snaplen, promisc, to_ms, errbuf);
if (temp == NULL) {
return NULL;
}
return dp_fillhandle(temp);
}
/* functions to add callbacks */
void dp_addcb(struct dp_handle *handle, enum dp_packet_type type,
dp_callback callback) {
handle->callback[type] = callback;
}
/* functions for parsing the payloads */
void dp_parse_tcp(struct dp_handle *handle, const dp_header *header,
const u_char *packet) {
// const struct tcphdr * tcp = (struct tcphdr *) packet;
// u_char * payload = (u_char *) packet + sizeof (struct tcphdr);
if (handle->callback[dp_packet_tcp] != NULL) {
int done =
(handle->callback[dp_packet_tcp])(handle->userdata, header, packet);
if (done)
return;
}
// TODO: maybe `pass on' payload to lower-level protocol parsing
}
void dp_parse_ip(struct dp_handle *handle, const dp_header *header,
const u_char *packet) {
const struct ip *ip = (struct ip *)packet;
if (DP_DEBUG) {
fprintf(stdout, "Looking at packet with length %ud\n", header->len);
}
u_char *payload = (u_char *)packet + sizeof(struct ip);
if (handle->callback[dp_packet_ip] != NULL) {
int done =
(handle->callback[dp_packet_ip])(handle->userdata, header, packet);
if (done)
return;
}
switch (ip->ip_p) {
case IPPROTO_TCP:
dp_parse_tcp(handle, header, payload);
break;
default:
// TODO: maybe support for non-tcp IP packets
break;
}
}
void dp_parse_ip6(struct dp_handle *handle, const dp_header *header,
const u_char *packet) {
const struct ip6_hdr *ip6 = (struct ip6_hdr *)packet;
u_char *payload = (u_char *)packet + sizeof(struct ip6_hdr);
if (handle->callback[dp_packet_ip6] != NULL) {
int done =
(handle->callback[dp_packet_ip6])(handle->userdata, header, packet);
if (done)
return;
}
switch ((ip6->ip6_ctlun).ip6_un1.ip6_un1_nxt) {
case IPPROTO_TCP:
dp_parse_tcp(handle, header, payload);
break;
default:
// TODO: maybe support for non-tcp ipv6 packets
break;
}
}
void dp_parse_ethernet(struct dp_handle *handle, const dp_header *header,
const u_char *packet) {
const struct ether_header *ethernet = (struct ether_header *)packet;
u_char *payload = (u_char *)packet + sizeof(struct ether_header);
u_int16_t protocol = 0;
/* call handle if it exists */
if (handle->callback[dp_packet_ethernet] != NULL) {
int done = (handle->callback[dp_packet_ethernet])(handle->userdata, header,
packet);
/* return if handle decides we're done */
if (done)
return;
}
/* parse payload */
protocol = ntohs(ethernet->ether_type);
switch (protocol) {
case ETHERTYPE_IP:
dp_parse_ip(handle, header, payload);
break;
case ETHERTYPE_IPV6:
dp_parse_ip6(handle, header, payload);
break;
default:
// TODO: maybe support for other protocols apart from IPv4 and IPv6
break;
}
}
/* ppp header, i hope ;) */
/* glanced from ethereal, it's 16 bytes, and the payload packet type is
* in the last 2 bytes... */
struct ppp_header {
u_int16_t dummy1;
u_int16_t dummy2;
u_int16_t dummy3;
u_int16_t dummy4;
u_int16_t dummy5;
u_int16_t dummy6;
u_int16_t dummy7;
u_int16_t packettype;
};
void dp_parse_ppp(struct dp_handle *handle, const dp_header *header,
const u_char *packet) {
const struct ppp_header *ppp = (struct ppp_header *)packet;
u_char *payload = (u_char *)packet + sizeof(struct ppp_header);
u_int16_t protocol = 0;
/* call handle if it exists */
if (handle->callback[dp_packet_ppp] != NULL) {
int done =
(handle->callback[dp_packet_ppp])(handle->userdata, header, packet);
/* return if handle decides we're done */
if (done)
return;
}
/* parse payload */
protocol = ntohs(ppp->packettype);
switch (protocol) {
case ETHERTYPE_IP:
dp_parse_ip(handle, header, payload);
break;
case ETHERTYPE_IPV6:
dp_parse_ip6(handle, header, payload);
break;
default:
// TODO: support for other than IPv4 and IPv6
break;
}
}
/* linux cooked header, i hope ;) */
/* glanced from libpcap/ssl.h */
#define SLL_ADDRLEN 8 /* length of address field */
struct sll_header {
u_int16_t sll_pkttype; /* packet type */
u_int16_t sll_hatype; /* link-layer address type */
u_int16_t sll_halen; /* link-layer address length */
u_int8_t sll_addr[SLL_ADDRLEN]; /* link-layer address */
u_int16_t sll_protocol; /* protocol */
};
void dp_parse_linux_cooked(struct dp_handle *handle, const dp_header *header,
const u_char *packet) {
const struct sll_header *sll = (struct sll_header *)packet;
u_char *payload = (u_char *)packet + sizeof(struct sll_header);
u_int16_t protocol = 0;
/* call handle if it exists */
if (handle->callback[dp_packet_sll] != NULL) {
int done =
(handle->callback[dp_packet_sll])(handle->userdata, header, packet);
/* return if handle decides we're done */
if (done)
return;
}
/* parse payload */
protocol = ntohs(sll->sll_protocol);
switch (protocol) {
case ETHERTYPE_IP:
dp_parse_ip(handle, header, payload);
break;
case ETHERTYPE_IPV6:
dp_parse_ip6(handle, header, payload);
break;
default:
// TODO: support for other than IPv4 / IPv6
break;
}
}
/* functions to do the monitoring */
void dp_pcap_callback(u_char *u_handle, const struct pcap_pkthdr *header,
const u_char *packet) {
struct dp_handle *handle = (struct dp_handle *)u_handle;
struct dp_header;
/* make a copy of the userdata for every packet */
u_char *userdata_copy = (u_char *)malloc(handle->userdata_size);
memcpy(userdata_copy, handle->userdata, handle->userdata_size);
switch (handle->linktype) {
case (DLT_EN10MB):
dp_parse_ethernet(handle, header, packet);
break;
case (DLT_PPP):
dp_parse_ppp(handle, header, packet);
break;
case (DLT_LINUX_SLL):
dp_parse_linux_cooked(handle, header, packet);
break;
case (DLT_RAW):
case (DLT_NULL):
// hope for the best
dp_parse_ip(handle, header, packet);
break;
default:
fprintf(stdout, "Unknown linktype %d", handle->linktype);
break;
}
free(userdata_copy);
}
int dp_dispatch(struct dp_handle *handle, int count, u_char *user, int size) {
handle->userdata = user;
handle->userdata_size = size;
return pcap_dispatch(handle->pcap_handle, count, dp_pcap_callback,
(u_char *)handle);
}
int dp_setnonblock(struct dp_handle *handle, int i, char *errbuf) {
return pcap_setnonblock(handle->pcap_handle, i, errbuf);
}
char *dp_geterr(struct dp_handle *handle) {
return pcap_geterr(handle->pcap_handle);
}

92
src/decpcap.h Normal file
View File

@@ -0,0 +1,92 @@
/*
* decpcap.h
*
* Copyright (c) 2004-2006,2011 Arnout Engelen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
*USA.
*
*/
#ifndef __DECPCAP_H
#define __DECPCAP_H
#include <stdlib.h>
#include <stdio.h>
#include <pcap.h>
#define DP_ERRBUF_SIZE PCAP_ERRBUF_SIZE
/* definitions */
enum dp_packet_type {
dp_packet_ethernet,
dp_packet_ppp,
dp_packet_sll,
dp_packet_ip,
dp_packet_ip6,
dp_packet_tcp,
dp_packet_udp,
dp_n_packet_types
};
/*enum dp_link_type {
dp_link_ethernet,
dp_link_ppp,
dp_n_link_types
};*/
/*struct dp_header {
* pcap
};*/
typedef struct pcap_pkthdr dp_header;
typedef int (*dp_callback)(u_char *, const dp_header *, const u_char *);
struct dp_handle {
pcap_t *pcap_handle;
dp_callback callback[dp_n_packet_types];
int linktype;
u_char *userdata;
int userdata_size;
};
/* functions to set up a handle (which is basically just a pcap handle) */
struct dp_handle *dp_open_live(const char *device, int snaplen, int promisc,
int to_ms, char *errbuf);
struct dp_handle *dp_open_offline(char *fname, char *ebuf);
/* functions to add callbacks */
void dp_addcb(struct dp_handle *handle, enum dp_packet_type type,
dp_callback callback);
/* functions to parse payloads */
void dp_parse(enum dp_packet_type type, void *packet);
/* functions to start monitoring */
int dp_dispatch(struct dp_handle *handler, int count, u_char *user, int size);
/* functions that simply call libpcap */
int dp_datalink(struct dp_handle *handle);
int dp_setnonblock(struct dp_handle *handle, int i, char *errbuf);
char *dp_geterr(struct dp_handle *handle);
#endif

48
src/decpcap_test.cpp Normal file
View File

@@ -0,0 +1,48 @@
/*
* decpcap_test.cpp
*
* Copyright (c) 2006,2011 Arnout Engelen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
*USA.
*
*/
#include <iostream>
extern "C" {
#include "decpcap.h"
}
int process_tcp(u_char * /* userdata */, const dp_header * /* header */,
const u_char * /* m_packet */) {
std::cout << "Callback for processing TCP packet called" << std::endl;
return 0;
}
int main(int argc, char **argv) {
if (argc < 2) {
std::cout << "Please, enter a filename" << std::endl;
}
char *errbuf = new char[DP_ERRBUF_SIZE];
dp_handle *newhandle = dp_open_offline(argv[1], errbuf);
dp_addcb(newhandle, dp_packet_tcp, process_tcp);
int ret = dp_dispatch(newhandle, -1, NULL, 0);
if (ret == -1) {
std::cout << "Error dispatching: " << dp_geterr(newhandle);
}
}

90
src/devices.cpp Normal file
View File

@@ -0,0 +1,90 @@
/*
* devices.cpp
*
* Copyright (c) 2011 Arnout Engelen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
*USA.
*
*/
#include "devices.h"
#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <net/if.h>
#include <ifaddrs.h>
bool selected(int devc, char **devicenames, char *devicename) {
if (devc == 0)
return true;
for (int i = 0; i < devc; i++)
if (strcmp(devicenames[i], devicename) == 0)
return true;
return false;
}
bool already_seen(device *devices, char *devicename) {
for (class device *current_device = devices; current_device != NULL;
current_device = current_device->next) {
if (strcmp(current_device->name, devicename) == 0)
return true;
}
return false;
}
// The interface is up, not a loopback and running?
bool up_running(int ifa_flags) {
return !(ifa_flags & IFF_LOOPBACK) && (ifa_flags & IFF_UP) &&
(ifa_flags & IFF_RUNNING);
}
/**
* This function can return null, if no good interface is found
* When 'all' is set to 'false', the function avoids loopback interface and
* down/not running interfaces
*/
device *get_devices(int devc, char **devicenames, bool all) {
struct ifaddrs *ifaddr, *ifa;
if (getifaddrs(&ifaddr) == -1) {
std::cerr << "Failed to get interface addresses" << std::endl;
// perror("getifaddrs");
return NULL;
}
device *devices = NULL;
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL)
continue;
if (!selected(devc, devicenames, ifa->ifa_name))
continue;
if (already_seen(devices, ifa->ifa_name))
continue;
if (!all && !up_running(ifa->ifa_flags))
continue;
devices = new device(strdup(ifa->ifa_name), devices);
}
freeifaddrs(ifaddr);
return devices;
}
device *get_default_devices() { return get_devices(0, NULL, false); }

50
src/devices.h Normal file
View File

@@ -0,0 +1,50 @@
/*
* devices.h
*
* Copyright (c) 2011 Arnout Engelen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
*USA.
*
*/
#ifndef __DEVICES_H
#define __DEVICES_H
#include <cstddef> // NULL
class device {
public:
device(const char *m_name, device *m_next = NULL) {
name = m_name;
next = m_next;
}
const char *name;
device *next;
};
/** get all devices that are up, running and not loopback */
device *get_default_devices();
/**
* Get all specified devices.
* If no devices are specified, get all devices.
*
* when 'all' is set, also return loopback interfaces and interfaces
* that are down or not running
*/
device *get_devices(int devc, char **devv, bool all);
#endif

249
src/inode2prog.cpp Normal file
View File

@@ -0,0 +1,249 @@
/*
* inode2prog.cpp
*
* Copyright (c) 2005,2006,2008,2009 Arnout Engelen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
*USA.
*
*/
#include <sys/types.h>
#include <cerrno>
#include <cstring>
#include <dirent.h>
#include <ctype.h>
#include <cstdlib>
#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <string>
#include <map>
#include <sys/stat.h>
#include <fcntl.h>
#include <climits>
#include "inode2prog.h"
extern bool bughuntmode;
// Not sure, but assuming there's no more PID's than go into 64 unsigned bits..
const int MAX_PID_LENGTH = 20;
// Max length of filenames in /proc/<pid>/fd/*. These are numeric, so 10 digits
// seems like a safe assumption.
const int MAX_FDLINK = 10;
/* maps from inode to program-struct */
std::map<unsigned long, prg_node *> inodeproc;
bool is_number(const char *string) {
while (*string) {
if (!isdigit(*string))
return false;
string++;
}
return true;
}
unsigned long str2ulong(const char *ptr) {
unsigned long retval = 0;
while ((*ptr >= '0') && (*ptr <= '9')) {
retval *= 10;
retval += *ptr - '0';
ptr++;
}
return retval;
}
int str2int(const char *ptr) {
int retval = 0;
while ((*ptr >= '0') && (*ptr <= '9')) {
retval *= 10;
retval += *ptr - '0';
ptr++;
}
return retval;
}
static std::string read_file(int fd) {
char buf[255];
std::string content;
for (int length; (length = read(fd, buf, sizeof(buf))) > 0;) {
if (length < 0) {
std::fprintf(stderr, "Error reading file: %s\n", std::strerror(errno));
std::exit(34);
}
content.append(buf, length);
}
return content;
}
static std::string read_file(const char *filepath) {
int fd = open(filepath, O_RDONLY);
if (fd < 0) {
std::fprintf(stderr, "Error opening %s: %s\n", filepath,
std::strerror(errno));
std::exit(3);
return NULL;
}
std::string contents = read_file(fd);
if (close(fd)) {
std::fprintf(stderr, "Error opening %s: %s\n", filepath,
std::strerror(errno));
std::exit(34);
}
return contents;
}
std::string getprogname(pid_t pid) {
const int maxfilenamelen = 14 + MAX_PID_LENGTH + 1;
char filename[maxfilenamelen];
std::snprintf(filename, maxfilenamelen, "/proc/%d/cmdline", pid);
return read_file(filename);
}
void setnode(unsigned long inode, pid_t pid) {
prg_node *current_value = inodeproc[inode];
if (current_value == NULL || current_value->pid != pid) {
prg_node *newnode = new prg_node;
newnode->inode = inode;
newnode->pid = pid;
newnode->name = getprogname(pid);
inodeproc[inode] = newnode;
delete current_value;
}
}
void get_info_by_linkname(const char *pid, const char *linkname) {
if (strncmp(linkname, "socket:[", 8) == 0) {
setnode(str2ulong(linkname + 8), str2int(pid));
}
}
/* updates the `inodeproc' inode-to-prg_node
* for all inodes belonging to this PID
* (/proc/pid/fd/42)
* */
void get_info_for_pid(const char *pid) {
char dirname[10 + MAX_PID_LENGTH];
size_t dirlen = 10 + strlen(pid);
snprintf(dirname, dirlen, "/proc/%s/fd", pid);
DIR *dir = opendir(dirname);
if (!dir) {
if (bughuntmode) {
std::cout << "Couldn't open dir " << dirname << ": " << strerror(errno)
<< "\n";
}
return;
}
/* walk through /proc/%s/fd/... */
dirent *entry;
while ((entry = readdir(dir))) {
if (entry->d_type != DT_LNK)
continue;
// std::cout << "Looking at: " << entry->d_name << std::endl;
size_t fromlen = dirlen + strlen(entry->d_name) + 1;
char fromname[10 + MAX_PID_LENGTH + 1 + MAX_FDLINK];
snprintf(fromname, fromlen, "%s/%s", dirname, entry->d_name);
// std::cout << "Linking from: " << fromname << std::endl;
int linklen = 80;
char linkname[linklen];
int usedlen = readlink(fromname, linkname, linklen - 1);
if (usedlen == -1) {
continue;
}
assert(usedlen < linklen);
linkname[usedlen] = '\0';
get_info_by_linkname(pid, linkname);
}
closedir(dir);
}
/* updates the `inodeproc' inode-to-prg_node mapping
* for all processes in /proc */
void reread_mapping() {
DIR *proc = opendir("/proc");
if (proc == 0) {
std::cerr << "Error reading /proc, neede to get inode-to-pid-maping\n";
exit(1);
}
dirent *entry;
while ((entry = readdir(proc))) {
if (entry->d_type != DT_DIR)
continue;
if (!is_number(entry->d_name))
continue;
get_info_for_pid(entry->d_name);
}
closedir(proc);
}
struct prg_node *findPID(unsigned long inode) {
/* we first look in inodeproc */
struct prg_node *node = inodeproc[inode];
if (node != NULL) {
if (bughuntmode) {
std::cout << ":) Found pid in inodeproc table" << std::endl;
}
return node;
}
#ifndef __APPLE__
reread_mapping();
#endif
struct prg_node *retval = inodeproc[inode];
if (bughuntmode) {
if (retval == NULL) {
std::cout << ":( No pid after inodeproc refresh" << std::endl;
} else {
std::cout << ":) Found pid after inodeproc refresh" << std::endl;
}
}
return retval;
}
void prg_cache_clear(){};
/*void main () {
std::cout << "Fooo\n";
reread_mapping();
std::cout << "Haihai\n";
}*/

44
src/inode2prog.h Normal file
View File

@@ -0,0 +1,44 @@
/*
* inode2prog.h
*
* Copyright (c) 2005,2008 Arnout Engelen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
*USA.
*
*/
#ifndef __INODE2PROG_h
#define __INODE2PROG_h
/* this should be called quickly after the packet
* arrived, since the inode may disappear from the table
* quickly, too :) */
#include "nethogs.h"
struct prg_node {
long inode;
pid_t pid;
std::string name;
};
struct prg_node *findPID(unsigned long inode);
void prg_cache_clear();
// reread the inode-to-prg_node-mapping
void reread_mapping();
#endif

329
src/libnethogs.cpp Normal file
View File

@@ -0,0 +1,329 @@
extern "C" {
#include "libnethogs.h"
}
#include "nethogs.cpp"
#include <iostream>
#include <memory>
#include <map>
#include <vector>
#include <cstring>
#include <fcntl.h>
#include <errno.h>
//////////////////////////////
extern ProcList *processes;
extern Process *unknowntcp;
extern Process *unknownudp;
extern Process *unknownip;
//////////////////////////////
// The self_pipe is used to interrupt the select() in the main loop
static std::pair<int, int> self_pipe = std::make_pair(-1, -1);
static bool monitor_run_flag = false;
typedef std::map<void *, NethogsMonitorRecord> NethogsRecordMap;
static NethogsRecordMap monitor_record_map;
static int monitor_refresh_delay = 1;
static time_t monitor_last_refresh_time = 0;
// selectable file descriptors for the main loop
static fd_set pc_loop_fd_set;
static std::vector<int> pc_loop_fd_list;
static bool pc_loop_use_select = true;
static handle *handles = NULL;
static std::pair<int, int> create_self_pipe() {
int pfd[2];
if (pipe(pfd) == -1)
return std::make_pair(-1, -1);
if (fcntl(pfd[0], F_SETFL, fcntl(pfd[0], F_GETFL) | O_NONBLOCK) == -1)
return std::make_pair(-1, -1);
if (fcntl(pfd[1], F_SETFL, fcntl(pfd[1], F_GETFL) | O_NONBLOCK) == -1)
return std::make_pair(-1, -1);
return std::make_pair(pfd[0], pfd[1]);
}
static bool wait_for_next_trigger() {
if (pc_loop_use_select) {
FD_ZERO(&pc_loop_fd_set);
int nfds = 0;
for (std::vector<int>::const_iterator it = pc_loop_fd_list.begin();
it != pc_loop_fd_list.end(); ++it) {
int const fd = *it;
nfds = std::max(nfds, *it + 1);
FD_SET(fd, &pc_loop_fd_set);
}
timeval timeout = {monitor_refresh_delay, 0};
if (select(nfds, &pc_loop_fd_set, 0, 0, &timeout) != -1) {
if (FD_ISSET(self_pipe.first, &pc_loop_fd_set)) {
return false;
}
}
} else {
// If select() not possible, pause to prevent 100%
usleep(1000);
}
return true;
}
static int nethogsmonitor_init() {
process_init();
device *devices = get_default_devices();
if (devices == NULL) {
std::cerr << "No devices to monitor" << std::endl;
return NETHOGS_STATUS_NO_DEVICE;
}
device *current_dev = devices;
bool promiscuous = false;
int nb_devices = 0;
int nb_failed_devices = 0;
while (current_dev != NULL) {
++nb_devices;
if (!getLocal(current_dev->name, false)) {
std::cerr << "getifaddrs failed while establishing local IP."
<< std::endl;
++nb_failed_devices;
continue;
}
char errbuf[PCAP_ERRBUF_SIZE];
dp_handle *newhandle =
dp_open_live(current_dev->name, BUFSIZ, promiscuous, 100, errbuf);
if (newhandle != NULL) {
dp_addcb(newhandle, dp_packet_ip, process_ip);
dp_addcb(newhandle, dp_packet_ip6, process_ip6);
dp_addcb(newhandle, dp_packet_tcp, process_tcp);
dp_addcb(newhandle, dp_packet_udp, process_udp);
/* The following code solves sf.net bug 1019381, but is only available
* in newer versions (from 0.8 it seems) of libpcap
*
* update: version 0.7.2, which is in debian stable now, should be ok
* also.
*/
if (dp_setnonblock(newhandle, 1, errbuf) == -1) {
fprintf(stderr, "Error putting libpcap in nonblocking mode\n");
}
handles = new handle(newhandle, current_dev->name, handles);
if (pc_loop_use_select) {
// some devices may not support pcap_get_selectable_fd
int const fd = pcap_get_selectable_fd(newhandle->pcap_handle);
if (fd != -1) {
pc_loop_fd_list.push_back(fd);
} else {
pc_loop_use_select = false;
pc_loop_fd_list.clear();
fprintf(stderr, "failed to get selectable_fd for %s\n",
current_dev->name);
}
}
} else {
fprintf(stderr, "ERROR: opening handler for device %s: %s\n",
current_dev->name, strerror(errno));
++nb_failed_devices;
}
current_dev = current_dev->next;
}
if (nb_devices == nb_failed_devices) {
return NETHOGS_STATUS_FAILURE;
}
// use the Self-Pipe trick to interrupt the select() in the main loop
if (pc_loop_use_select) {
self_pipe = create_self_pipe();
if (self_pipe.first == -1 || self_pipe.second == -1) {
std::cerr << "Error creating pipe file descriptors\n";
pc_loop_use_select = false;
} else {
pc_loop_fd_list.push_back(self_pipe.first);
}
}
return NETHOGS_STATUS_OK;
}
static void nethogsmonitor_handle_update(NethogsMonitorCallback cb) {
refreshconninode();
refreshcount++;
ProcList *curproc = processes;
ProcList *previousproc = NULL;
int nproc = processes->size();
while (curproc != NULL) {
// walk though its connections, summing up their data, and
// throwing away connections that haven't received a package
// in the last PROCESSTIMEOUT seconds.
assert(curproc != NULL);
assert(curproc->getVal() != NULL);
assert(nproc == processes->size());
/* remove timed-out processes (unless it's one of the the unknown process)
*/
if ((curproc->getVal()->getLastPacket() + PROCESSTIMEOUT <=
curtime.tv_sec) &&
(curproc->getVal() != unknowntcp) &&
(curproc->getVal() != unknownudp) && (curproc->getVal() != unknownip)) {
if (DEBUG)
std::cout << "PROC: Deleting process\n";
NethogsRecordMap::iterator it = monitor_record_map.find(curproc);
if (it != monitor_record_map.end()) {
NethogsMonitorRecord &data = it->second;
(*cb)(NETHOGS_APP_ACTION_REMOVE, &data);
monitor_record_map.erase(curproc);
}
ProcList *todelete = curproc;
Process *p_todelete = curproc->getVal();
if (previousproc) {
previousproc->next = curproc->next;
curproc = curproc->next;
} else {
processes = curproc->getNext();
curproc = processes;
}
delete todelete;
delete p_todelete;
nproc--;
// continue;
} else {
const u_int32_t uid = curproc->getVal()->getUid();
u_int32_t sent_bytes;
u_int32_t recv_bytes;
float sent_kbs;
float recv_kbs;
curproc->getVal()->getkbps(&recv_kbs, &sent_kbs);
curproc->getVal()->gettotal(&recv_bytes, &sent_bytes);
// notify update
bool const new_data =
(monitor_record_map.find(curproc) == monitor_record_map.end());
NethogsMonitorRecord &data = monitor_record_map[curproc];
bool data_change = false;
if (new_data) {
data_change = true;
static int record_id = 0;
++record_id;
memset(&data, 0, sizeof(data));
data.record_id = record_id;
data.name = curproc->getVal()->name;
data.pid = curproc->getVal()->pid;
}
data.device_name = curproc->getVal()->devicename;
#define NHM_UPDATE_ONE_FIELD(TO, FROM) \
if ((TO) != (FROM)) { \
TO = FROM; \
data_change = true; \
}
NHM_UPDATE_ONE_FIELD(data.uid, uid)
NHM_UPDATE_ONE_FIELD(data.sent_bytes, sent_bytes)
NHM_UPDATE_ONE_FIELD(data.recv_bytes, recv_bytes)
NHM_UPDATE_ONE_FIELD(data.sent_kbs, sent_kbs)
NHM_UPDATE_ONE_FIELD(data.recv_kbs, recv_kbs)
#undef NHM_UPDATE_ONE_FIELD
if (data_change) {
(*cb)(NETHOGS_APP_ACTION_SET, &data);
}
// next
previousproc = curproc;
curproc = curproc->next;
}
}
}
static void nethogsmonitor_clean_up() {
// clean up
handle *current_handle = handles;
while (current_handle != NULL) {
pcap_close(current_handle->content->pcap_handle);
current_handle = current_handle->next;
}
// close file descriptors
for (std::vector<int>::const_iterator it = pc_loop_fd_list.begin();
it != pc_loop_fd_list.end(); ++it) {
close(*it);
}
procclean();
}
int nethogsmonitor_loop(NethogsMonitorCallback cb) {
if (monitor_run_flag) {
return NETHOGS_STATUS_FAILURE;
}
int return_value = nethogsmonitor_init();
if (return_value != NETHOGS_STATUS_OK) {
return return_value;
}
monitor_run_flag = true;
struct dpargs *userdata = (dpargs *)malloc(sizeof(struct dpargs));
// Main loop
while (monitor_run_flag) {
bool packets_read = false;
handle *current_handle = handles;
while (current_handle != NULL) {
userdata->device = current_handle->devicename;
userdata->sa_family = AF_UNSPEC;
int retval = dp_dispatch(current_handle->content, -1, (u_char *)userdata,
sizeof(struct dpargs));
if (retval < 0) {
std::cerr << "Error dispatching: " << retval << std::endl;
} else if (retval != 0) {
packets_read = true;
} else {
gettimeofday(&curtime, NULL);
}
current_handle = current_handle->next;
}
time_t const now = ::time(NULL);
if (monitor_last_refresh_time + monitor_refresh_delay <= now) {
monitor_last_refresh_time = now;
nethogsmonitor_handle_update(cb);
}
if (!packets_read) {
if (!wait_for_next_trigger()) {
break;
}
}
}
nethogsmonitor_clean_up();
return NETHOGS_STATUS_OK;
}
void nethogsmonitor_breakloop() {
monitor_run_flag = false;
write(self_pipe.second, "x", 1);
}

72
src/libnethogs.h Normal file
View File

@@ -0,0 +1,72 @@
#ifndef LIBNETHOGS_H_
#define LIBNETHOGS_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
#define NETHOGS_DSO_VISIBLE __attribute__((visibility("default")))
#define NETHOGS_DSO_HIDDEN __attribute__((visibility("hidden")))
#define NETHOGS_APP_ACTION_SET 1
#define NETHOGS_APP_ACTION_REMOVE 2
#define NETHOGS_STATUS_OK 0
#define NETHOGS_STATUS_FAILURE 1
#define NETHOGS_STATUS_NO_DEVICE 2
typedef struct NethogsMonitorRecord {
int record_id;
const char *name;
int pid;
uint32_t uid;
const char *device_name;
uint32_t sent_bytes;
uint32_t recv_bytes;
float sent_kbs;
float recv_kbs;
} NethogsMonitorRecord;
/**
* @brief Defines a callback to handle updates about applications
* @param action NETHOGS_APP_ACTION_SET if data is beeing added or updated,
* NETHOGS_APP_ACTION_REMOVE if data is beeing removed.
* the record_id member is used to uniquely identify the data beeing
* update or removed.
* @param data a pointer to an application usage data. the pointer remains valid
* until
* the callback is called with NETHOGS_APP_ACTION_REMOVE for the same
* pointer.
* the user should not modify the content of the structure pointed by
* data.
*/
typedef void (*NethogsMonitorCallback)(int action,
NethogsMonitorRecord const *data);
/**
* @brief Enter the process monitoring loop and reports updates using the
* callback provided as parameter.
* This call will block until nethogsmonitor_breakloop() is called or a failure
* occurs.
* @param cb A pointer to a callback function following the
* NethogsMonitorCallback definition
*/
NETHOGS_DSO_VISIBLE int nethogsmonitor_loop(NethogsMonitorCallback cb);
/**
* @brief Makes the call to nethogsmonitor_loop return.
*/
NETHOGS_DSO_VISIBLE void nethogsmonitor_breakloop();
#undef NETHOGS_DSO_VISIBLE
#undef NETHOGS_DSO_HIDDEN
#ifdef __cplusplus
}
#endif
#endif // LIBNETHOGS_H_

278
src/main.cpp Normal file
View File

@@ -0,0 +1,278 @@
#include "nethogs.cpp"
#include <fcntl.h>
#include <vector>
// The self_pipe is used to interrupt the select() in the main loop
static std::pair<int, int> self_pipe = std::make_pair(-1, -1);
static time_t last_refresh_time = 0;
// selectable file descriptors for the main loop
static fd_set pc_loop_fd_set;
static std::vector<int> pc_loop_fd_list;
static bool pc_loop_use_select = true;
static void versiondisplay(void) { std::cout << version << "\n"; }
static void help(bool iserror) {
std::ostream &output = (iserror ? std::cerr : std::cout);
// output << "usage: nethogs [-V] [-b] [-d seconds] [-t] [-p] [-f (eth|ppp))]
// [device [device [device ...]]]\n";
output << "usage: nethogs [-V] [-h] [-b] [-d seconds] [-v mode] [-c count] "
"[-t] [-p] [-s] [device [device [device ...]]]\n";
output << " -V : prints version.\n";
output << " -h : prints this help.\n";
output << " -b : bughunt mode - implies tracemode.\n";
output << " -d : delay for update refresh rate in seconds. default "
"is 1.\n";
output << " -v : view mode (0 = KB/s, 1 = total KB, 2 = total B, 3 "
"= total MB). default is 0.\n";
output << " -c : number of updates. default is 0 (unlimited).\n";
output << " -t : tracemode.\n";
// output << " -f : format of packets on interface, default is
// eth.\n";
output << " -p : sniff in promiscious mode (not recommended).\n";
output << " -s : sort output by sent column.\n";
output << " -a : monitor all devices, even loopback/stopped ones.\n";
output << " device : device(s) to monitor. default is all "
"interfaces up and running excluding loopback\n";
output << std::endl;
output << "When nethogs is running, press:\n";
output << " q: quit\n";
output << " s: sort by SENT traffic\n";
output << " r: sort by RECEIVE traffic\n";
output << " m: switch between total (KB, B, MB) and KB/s mode\n";
}
void quit_cb(int /* i */) {
if (self_pipe.second != -1) {
write(self_pipe.second, "x", 1);
} else {
exit(0);
}
}
void forceExit(bool success, const char *msg, ...) {
if ((!tracemode) && (!DEBUG)) {
exit_ui();
}
va_list argp;
va_start(argp, msg);
vfprintf(stderr, msg, argp);
va_end(argp);
std::cerr << std::endl;
if (success)
exit(EXIT_SUCCESS);
else
exit(EXIT_FAILURE);
}
std::pair<int, int> create_self_pipe() {
int pfd[2];
if (pipe(pfd) == -1)
return std::make_pair(-1, -1);
if (fcntl(pfd[0], F_SETFL, fcntl(pfd[0], F_GETFL) | O_NONBLOCK) == -1)
return std::make_pair(-1, -1);
if (fcntl(pfd[1], F_SETFL, fcntl(pfd[1], F_GETFL) | O_NONBLOCK) == -1)
return std::make_pair(-1, -1);
return std::make_pair(pfd[0], pfd[1]);
}
bool wait_for_next_trigger() {
if (pc_loop_use_select) {
FD_ZERO(&pc_loop_fd_set);
int nfds = 0;
for (std::vector<int>::const_iterator it = pc_loop_fd_list.begin();
it != pc_loop_fd_list.end(); ++it) {
int const fd = *it;
nfds = std::max(nfds, *it + 1);
FD_SET(fd, &pc_loop_fd_set);
}
timeval timeout = {refreshdelay, 0};
if (select(nfds, &pc_loop_fd_set, 0, 0, &timeout) != -1) {
if (FD_ISSET(self_pipe.first, &pc_loop_fd_set)) {
return false;
}
}
} else {
// If select() not possible, pause to prevent 100%
usleep(1000);
}
return true;
}
void clean_up() {
// close file descriptors
for (std::vector<int>::const_iterator it = pc_loop_fd_list.begin();
it != pc_loop_fd_list.end(); ++it) {
close(*it);
}
procclean();
if ((!tracemode) && (!DEBUG))
exit_ui();
}
int main(int argc, char **argv) {
process_init();
int promisc = 0;
bool all = false;
int opt;
while ((opt = getopt(argc, argv, "Vahbtpd:v:c:sa")) != -1) {
switch (opt) {
case 'V':
versiondisplay();
exit(0);
case 'h':
help(false);
exit(0);
case 'b':
bughuntmode = true;
tracemode = true;
break;
case 't':
tracemode = true;
break;
case 'p':
promisc = 1;
break;
case 's':
sortRecv = false;
break;
case 'd':
refreshdelay = atoi(optarg);
break;
case 'v':
viewMode = atoi(optarg) % VIEWMODE_COUNT;
break;
case 'c':
refreshlimit = atoi(optarg);
break;
case 'a':
all = true;
break;
default:
help(true);
exit(EXIT_FAILURE);
}
}
device *devices = get_devices(argc - optind, argv + optind, all);
if (devices == NULL)
forceExit(false, "No devices to monitor. Use '-a' to allow monitoring "
"loopback interfaces or devices that are not up/running");
if ((!tracemode) && (!DEBUG)) {
init_ui();
}
if (NEEDROOT && (geteuid() != 0))
forceExit(false, "You need to be root to run NetHogs!");
// use the Self-Pipe trick to interrupt the select() in the main loop
self_pipe = create_self_pipe();
if (self_pipe.first == -1 || self_pipe.second == -1) {
forceExit(false, "Error creating pipe file descriptors\n");
} else {
// add the self-pipe to allow interrupting select()
pc_loop_fd_list.push_back(self_pipe.first);
}
char errbuf[PCAP_ERRBUF_SIZE];
handle *handles = NULL;
device *current_dev = devices;
while (current_dev != NULL) {
if (!getLocal(current_dev->name, tracemode)) {
forceExit(false, "getifaddrs failed while establishing local IP.");
}
dp_handle *newhandle =
dp_open_live(current_dev->name, BUFSIZ, promisc, 100, errbuf);
if (newhandle != NULL) {
dp_addcb(newhandle, dp_packet_ip, process_ip);
dp_addcb(newhandle, dp_packet_ip6, process_ip6);
dp_addcb(newhandle, dp_packet_tcp, process_tcp);
dp_addcb(newhandle, dp_packet_udp, process_udp);
/* The following code solves sf.net bug 1019381, but is only available
* in newer versions (from 0.8 it seems) of libpcap
*
* update: version 0.7.2, which is in debian stable now, should be ok
* also.
*/
if (dp_setnonblock(newhandle, 1, errbuf) == -1) {
fprintf(stderr, "Error putting libpcap in nonblocking mode\n");
}
handles = new handle(newhandle, current_dev->name, handles);
if (pc_loop_use_select) {
// some devices may not support pcap_get_selectable_fd
int const fd = pcap_get_selectable_fd(newhandle->pcap_handle);
if (fd != -1) {
pc_loop_fd_list.push_back(fd);
} else {
pc_loop_use_select = false;
pc_loop_fd_list.clear();
fprintf(stderr, "failed to get selectable_fd for %s\n",
current_dev->name);
}
}
} else {
fprintf(stderr, "Error opening handler for device %s\n",
current_dev->name);
}
current_dev = current_dev->next;
}
signal(SIGINT, &quit_cb);
fprintf(
stderr,
"Waiting for first packet to arrive (see sourceforge.net bug 1019381)\n");
struct dpargs *userdata = (dpargs *)malloc(sizeof(struct dpargs));
// Main loop:
while (1) {
bool packets_read = false;
for (handle *current_handle = handles; current_handle != NULL;
current_handle = current_handle->next) {
userdata->device = current_handle->devicename;
userdata->sa_family = AF_UNSPEC;
int retval = dp_dispatch(current_handle->content, -1, (u_char *)userdata,
sizeof(struct dpargs));
if (retval < 0)
std::cerr << "Error dispatching: " << retval << std::endl;
else if (retval != 0)
packets_read = true;
}
time_t const now = ::time(NULL);
if (last_refresh_time + refreshdelay <= now) {
last_refresh_time = now;
if ((!DEBUG) && (!tracemode)) {
// handle user input
ui_tick();
}
do_refresh();
}
// if not packets, do a select() until next packet
if (!packets_read)
if (!wait_for_next_trigger())
// Shutdown requested - exit the loop
break;
}
clean_up();
}

248
src/nethogs.cpp Normal file
View File

@@ -0,0 +1,248 @@
/*
* `nethogs.cpp`
*
* Copyright (c) 2004-2006,2008,2011 Arnout Engelen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
*USA.
*
*/
#include "nethogs.h"
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <unistd.h>
#include <csignal>
#include <string>
#include <cstring>
#include <getopt.h>
#include <cstdarg>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include "cui.h"
extern "C" {
#include "decpcap.h"
}
#include "packet.h"
#include "connection.h"
#include "process.h"
#include "devices.h"
extern Process *unknownudp;
unsigned refreshdelay = 1;
unsigned refreshlimit = 0;
unsigned refreshcount = 0;
unsigned processlimit = 0;
bool tracemode = false;
bool bughuntmode = false;
// sort on sent or received?
bool sortRecv = true;
// viewMode: kb/s or total
int viewMode = VIEWMODE_KBPS;
const char version[] = " version " VERSION "." SUBVERSION "." MINORVERSION;
timeval curtime;
bool local_addr::contains(const in_addr_t &n_addr) {
if ((sa_family == AF_INET) && (n_addr == addr))
return true;
if (next == NULL)
return false;
return next->contains(n_addr);
}
bool local_addr::contains(const struct in6_addr &n_addr) {
if (sa_family == AF_INET6) {
/*
if (DEBUG) {
char addy [50];
std::cerr << "Comparing: ";
inet_ntop (AF_INET6, &n_addr, addy, 49);
std::cerr << addy << " and ";
inet_ntop (AF_INET6, &addr6, addy, 49);
std::cerr << addy << std::endl;
}
*/
// if (addr6.s6_addr == n_addr.s6_addr)
if (memcmp(&addr6, &n_addr, sizeof(struct in6_addr)) == 0) {
if (DEBUG)
std::cerr << "Match!" << std::endl;
return true;
}
}
if (next == NULL)
return false;
return next->contains(n_addr);
}
struct dpargs {
const char *device;
int sa_family;
in_addr ip_src;
in_addr ip_dst;
in6_addr ip6_src;
in6_addr ip6_dst;
};
const char *getVersion() { return version; }
int process_tcp(u_char *userdata, const dp_header *header,
const u_char *m_packet) {
struct dpargs *args = (struct dpargs *)userdata;
struct tcphdr *tcp = (struct tcphdr *)m_packet;
curtime = header->ts;
/* get info from userdata, then call getPacket */
Packet *packet;
switch (args->sa_family) {
case AF_INET:
#ifdef __APPLE__
packet = new Packet(args->ip_src, ntohs(tcp->th_sport), args->ip_dst,
ntohs(tcp->th_dport), header->len, header->ts);
#else
packet = new Packet(args->ip_src, ntohs(tcp->source), args->ip_dst,
ntohs(tcp->dest), header->len, header->ts);
#endif
break;
case AF_INET6:
#ifdef __APPLE__
packet = new Packet(args->ip6_src, ntohs(tcp->th_sport), args->ip6_dst,
ntohs(tcp->th_dport), header->len, header->ts);
#else
packet = new Packet(args->ip6_src, ntohs(tcp->source), args->ip6_dst,
ntohs(tcp->dest), header->len, header->ts);
#endif
break;
default:
std::cerr << "Invalid address family for TCP packet: " << args->sa_family
<< std::endl;
return true;
}
Connection *connection = findConnection(packet);
if (connection != NULL) {
/* add packet to the connection */
connection->add(packet);
} else {
/* else: unknown connection, create new */
connection = new Connection(packet);
getProcess(connection, args->device);
}
delete packet;
/* we're done now. */
return true;
}
int process_udp(u_char *userdata, const dp_header *header,
const u_char *m_packet) {
struct dpargs *args = (struct dpargs *)userdata;
struct udphdr *udp = (struct udphdr *)m_packet;
curtime = header->ts;
Packet *packet;
switch (args->sa_family) {
case AF_INET:
#ifdef __APPLE__
packet = new Packet(args->ip_src, ntohs(udp->uh_sport), args->ip_dst,
ntohs(udp->uh_dport), header->len, header->ts);
#else
packet = new Packet(args->ip_src, ntohs(udp->source), args->ip_dst,
ntohs(udp->dest), header->len, header->ts);
#endif
break;
case AF_INET6:
#ifdef __APPLE__
packet = new Packet(args->ip6_src, ntohs(udp->uh_sport), args->ip6_dst,
ntohs(udp->uh_dport), header->len, header->ts);
#else
packet = new Packet(args->ip6_src, ntohs(udp->source), args->ip6_dst,
ntohs(udp->dest), header->len, header->ts);
#endif
break;
default:
std::cerr << "Invalid address family for UDP packet: " << args->sa_family
<< std::endl;
return true;
}
// if (DEBUG)
// std::cout << "Got packet from " << packet->gethashstring() << std::endl;
Connection *connection = findConnection(packet);
if (connection != NULL) {
/* add packet to the connection */
connection->add(packet);
} else {
/* else: unknown connection, create new */
connection = new Connection(packet);
getProcess(connection, args->device);
}
delete packet;
/* we're done now. */
return true;
}
int process_ip(u_char *userdata, const dp_header * /* header */,
const u_char *m_packet) {
struct dpargs *args = (struct dpargs *)userdata;
struct ip *ip = (struct ip *)m_packet;
args->sa_family = AF_INET;
args->ip_src = ip->ip_src;
args->ip_dst = ip->ip_dst;
/* we're not done yet - also parse tcp :) */
return false;
}
int process_ip6(u_char *userdata, const dp_header * /* header */,
const u_char *m_packet) {
struct dpargs *args = (struct dpargs *)userdata;
const struct ip6_hdr *ip6 = (struct ip6_hdr *)m_packet;
args->sa_family = AF_INET6;
args->ip6_src = ip6->ip6_src;
args->ip6_dst = ip6->ip6_dst;
/* we're not done yet - also parse tcp :) */
return false;
}
class handle {
public:
handle(dp_handle *m_handle, const char *m_devicename = NULL,
handle *m_next = NULL) {
content = m_handle;
next = m_next;
devicename = m_devicename;
}
dp_handle *content;
const char *devicename;
handle *next;
};

115
src/nethogs.h Normal file
View File

@@ -0,0 +1,115 @@
/*
* nethogs.h
*
* Copyright (c) 2004-2006,2008,2010 Arnout Engelen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
*USA.
*
*/
#ifndef __NETHOGS_H
#define __NETHOGS_H
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <cassert>
#include <cstring>
#ifdef __APPLE__
#include <sys/malloc.h>
#else
#include <malloc.h>
#endif
#include <iostream>
#define _BSD_SOURCE 1
/* take the average speed over the last 5 seconds */
#define PERIOD 5
/* the amount of time after the last packet was recieved
* after which a process is removed */
#define PROCESSTIMEOUT 150
/* the amount of time after the last packet was recieved
* after which a connection is removed */
#define CONNTIMEOUT 50
/* Set to '0' when compiling for a system that uses Linux Capabilities,
* like www.adamantix.org: in that case nethogs shouldn't check if it's
* running as root. Take care to give it sufficient privileges though. */
#ifndef NEEDROOT
#define NEEDROOT 1
#endif
#define DEBUG 0
#define REVERSEHACK 0
// 2 times: 32 characters, 7 ':''s, a ':12345'.
// 1 '-'
// -> 2*45+1=91. we make it 92, for the null.
#define HASHKEYSIZE 92
#define PROGNAME_WIDTH 512
// viewMode: how to represent numbers
#define VIEWMODE_KBPS 0
#define VIEWMODE_TOTAL_KB 1
#define VIEWMODE_TOTAL_B 2
#define VIEWMODE_TOTAL_MB 3
#define VIEWMODE_COUNT 4
#define NORETURN __attribute__((__noreturn__))
void forceExit(bool success, const char *msg, ...) NORETURN;
class local_addr {
public:
/* ipv4 constructor takes an in_addr_t */
local_addr(in_addr_t m_addr, local_addr *m_next = NULL) {
addr = m_addr;
next = m_next;
sa_family = AF_INET;
string = (char *)malloc(16);
inet_ntop(AF_INET, &m_addr, string, 15);
}
/* this constructor takes an char address[33] */
local_addr(struct in6_addr *m_addr, local_addr *m_next = NULL) {
addr6 = *m_addr;
next = m_next;
sa_family = AF_INET6;
string = (char *)malloc(64);
inet_ntop(AF_INET6, &m_addr, string, 63);
}
bool contains(const in_addr_t &n_addr);
bool contains(const struct in6_addr &n_addr);
char *string;
local_addr *next;
private:
in_addr_t addr;
struct in6_addr addr6;
short int sa_family;
};
void quit_cb(int i);
const char *getVersion();
#endif

293
src/packet.cpp Normal file
View File

@@ -0,0 +1,293 @@
/*
* packet.cpp
*
* Copyright (c) 2004-2006,2008 Arnout Engelen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
*USA.
*
*/
#include "nethogs.h"
#include <iostream>
#include "packet.h"
#include <netinet/tcp.h>
#include <netinet/in.h>
#ifdef __APPLE__
#include <sys/malloc.h>
#else
#include <malloc.h>
#endif
#include <cassert>
#include <net/if.h>
#include <net/ethernet.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <sys/ioctl.h>
#include <ifaddrs.h>
// #include "inet6.c"
local_addr *local_addrs = NULL;
/*
* getLocal
* device: This should be device explicit (e.g. eth0:1)
*
* uses getifaddrs to get addresses of this device, and adds them to the
* local_addrs-list.
*/
bool getLocal(const char *device, bool tracemode) {
struct ifaddrs *ifaddr, *ifa;
if (getifaddrs(&ifaddr) == -1) {
return false;
}
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL)
continue;
if (strcmp(ifa->ifa_name, device) != 0)
continue;
int family = ifa->ifa_addr->sa_family;
if (family == AF_INET) {
struct sockaddr_in *addr = (struct sockaddr_in *)ifa->ifa_addr;
local_addrs = new local_addr(addr->sin_addr.s_addr, local_addrs);
if (tracemode || DEBUG) {
printf("Adding local address: %s\n", inet_ntoa(addr->sin_addr));
}
} else if (family == AF_INET6) {
struct sockaddr_in6 *addr = (struct sockaddr_in6 *)ifa->ifa_addr;
local_addrs = new local_addr(&addr->sin6_addr, local_addrs);
if (tracemode || DEBUG) {
char host[512];
printf("Adding local address: %s\n",
inet_ntop(AF_INET6, &addr->sin6_addr, host, sizeof(host)));
}
}
}
return true;
}
typedef u_int32_t tcp_seq;
/* ppp header, i hope ;) */
/* glanced from ethereal, it's 16 bytes, and the payload packet type is
* in the last 2 bytes... */
struct ppp_header {
u_int16_t dummy1;
u_int16_t dummy2;
u_int16_t dummy3;
u_int16_t dummy4;
u_int16_t dummy5;
u_int16_t dummy6;
u_int16_t dummy7;
u_int16_t packettype;
};
/* TCP header */
// TODO take from elsewhere.
struct tcp_hdr {
u_short th_sport; /* source port */
u_short th_dport; /* destination port */
tcp_seq th_seq; /* sequence number */
tcp_seq th_ack; /* acknowledgement number */
#if BYTE_ORDER == LITTLE_ENDIAN
u_int th_x2 : 4, /* (unused) */
th_off : 4; /* data offset */
#endif
#if BYTE_ORDER == BIG_ENDIAN
u_int th_off : 4, /* data offset */
th_x2 : 4; /* (unused) */
#endif
u_char th_flags;
#define TH_FIN 0x01
#define TH_SYN 0x02
#define TH_RST 0x04
#define TH_PUSH 0x08
#define TH_ACK 0x10
#define TH_URG 0x20
#define TH_ECE 0x40
#define TH_CWR 0x80
#define TH_FLAGS (TH_FIN | TH_SYN | TH_RST | TH_ACK | TH_URG | TH_ECE | TH_CWR)
u_short th_win; /* window */
u_short th_sum; /* checksum */
u_short th_urp; /* urgent pointer */
};
Packet::Packet(in_addr m_sip, unsigned short m_sport, in_addr m_dip,
unsigned short m_dport, u_int32_t m_len, timeval m_time,
direction m_dir) {
sip = m_sip;
sport = m_sport;
dip = m_dip;
dport = m_dport;
len = m_len;
time = m_time;
dir = m_dir;
sa_family = AF_INET;
hashstring = NULL;
}
Packet::Packet(in6_addr m_sip, unsigned short m_sport, in6_addr m_dip,
unsigned short m_dport, u_int32_t m_len, timeval m_time,
direction m_dir) {
sip6 = m_sip;
sport = m_sport;
dip6 = m_dip;
dport = m_dport;
len = m_len;
time = m_time;
dir = m_dir;
sa_family = AF_INET6;
hashstring = NULL;
}
direction invert(direction dir) {
if (dir == dir_incoming)
return dir_outgoing;
else if (dir == dir_outgoing)
return dir_incoming;
else
return dir_unknown;
}
Packet *Packet::newInverted() {
direction new_direction = invert(dir);
if (sa_family == AF_INET)
return new Packet(dip, dport, sip, sport, len, time, new_direction);
else
return new Packet(dip6, dport, sip6, sport, len, time, new_direction);
}
/* constructs returns a new Packet() structure with the same contents as this
* one */
Packet::Packet(const Packet &old_packet) {
sip = old_packet.sip;
sport = old_packet.sport;
sip6 = old_packet.sip6;
dip6 = old_packet.dip6;
dip = old_packet.dip;
dport = old_packet.dport;
len = old_packet.len;
time = old_packet.time;
sa_family = old_packet.sa_family;
if (old_packet.hashstring == NULL)
hashstring = NULL;
else
hashstring = strdup(old_packet.hashstring);
dir = old_packet.dir;
}
bool sameinaddr(in_addr one, in_addr other) {
return one.s_addr == other.s_addr;
}
bool Packet::isOlderThan(timeval t) {
std::cout << "Comparing " << time.tv_sec << " <= " << t.tv_sec << std::endl;
return (time.tv_sec <= t.tv_sec);
}
bool Packet::Outgoing() {
/* must be initialised with getLocal("eth0:1");) */
assert(local_addrs != NULL);
switch (dir) {
case dir_outgoing:
return true;
case dir_incoming:
return false;
case dir_unknown:
bool islocal;
if (sa_family == AF_INET)
islocal = local_addrs->contains(sip.s_addr);
else
islocal = local_addrs->contains(sip6);
if (islocal) {
dir = dir_outgoing;
return true;
} else {
if (DEBUG) {
if (sa_family == AF_INET)
islocal = local_addrs->contains(dip.s_addr);
else
islocal = local_addrs->contains(dip6);
if (!islocal) {
std::cerr << "Neither dip nor sip are local: ";
char addy[50];
inet_ntop(AF_INET6, &sip6, addy, 49);
std::cerr << addy << std::endl;
inet_ntop(AF_INET6, &dip6, addy, 49);
std::cerr << addy << std::endl;
return false;
}
}
dir = dir_incoming;
return false;
}
}
return false;
}
/* returns the packet in '1.2.3.4:5-1.2.3.4:5'-form, for use in the 'conninode'
* table */
/* '1.2.3.4' should be the local address. */
/* the calling code should take care of deletion of the hash string */
char *Packet::gethashstring() {
if (hashstring != NULL) {
return hashstring;
}
hashstring = (char *)malloc(HASHKEYSIZE * sizeof(char));
char *local_string = (char *)malloc(50);
char *remote_string = (char *)malloc(50);
if (sa_family == AF_INET) {
inet_ntop(sa_family, &sip, local_string, 49);
inet_ntop(sa_family, &dip, remote_string, 49);
} else {
inet_ntop(sa_family, &sip6, local_string, 49);
inet_ntop(sa_family, &dip6, remote_string, 49);
}
if (Outgoing()) {
snprintf(hashstring, HASHKEYSIZE * sizeof(char), "%s:%d-%s:%d",
local_string, sport, remote_string, dport);
} else {
snprintf(hashstring, HASHKEYSIZE * sizeof(char), "%s:%d-%s:%d",
remote_string, dport, local_string, sport);
}
free(local_string);
free(remote_string);
// if (DEBUG)
// std::cout << "Returning newly created hash string: " << hashstring <<
// std::endl;
return hashstring;
}
/* 2 packets match if they have the same
* source and destination ports and IP's. */
bool Packet::match(Packet *other) {
return (sport == other->sport) && (dport == other->dport) &&
(sameinaddr(sip, other->sip)) && (sameinaddr(dip, other->dip));
}
bool Packet::matchSource(Packet *other) {
return (sport == other->sport) && (sameinaddr(sip, other->sip));
}

84
src/packet.h Normal file
View File

@@ -0,0 +1,84 @@
/*
* packet.h
*
* Copyright (c) 2004,2006 Arnout Engelen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
*USA.
*
*/
#ifndef __PACKET_H
#define __PACKET_H
#define _BSD_SOURCE 1
#include <net/ethernet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "nethogs.h"
enum direction { dir_unknown, dir_incoming, dir_outgoing };
/* To initialise this module, call getLocal with the currently
* monitored device (e.g. "eth0:1") */
bool getLocal(const char *device, bool tracemode);
class Packet {
public:
in6_addr sip6;
in6_addr dip6;
in_addr sip;
in_addr dip;
unsigned short sport;
unsigned short dport;
u_int32_t len;
timeval time;
Packet(in_addr m_sip, unsigned short m_sport, in_addr m_dip,
unsigned short m_dport, u_int32_t m_len, timeval m_time,
direction dir = dir_unknown);
Packet(in6_addr m_sip, unsigned short m_sport, in6_addr m_dip,
unsigned short m_dport, u_int32_t m_len, timeval m_time,
direction dir = dir_unknown);
/* copy constructor */
Packet(const Packet &old);
~Packet() {
if (hashstring != NULL) {
free(hashstring);
hashstring = NULL;
}
}
/* Packet (const Packet &old_packet); */
/* copy constructor that turns the packet around */
Packet *newInverted();
bool isOlderThan(timeval t);
/* is this packet coming from the local host? */
bool Outgoing();
bool match(Packet *other);
bool matchSource(Packet *other);
/* returns '1.2.3.4:5-1.2.3.4:6'-style string */
char *gethashstring();
private:
direction dir;
short int sa_family;
char *hashstring;
};
#endif

353
src/process.cpp Normal file
View File

@@ -0,0 +1,353 @@
/*
* process.cpp
*
* Copyright (c) 2004,2005,2008,2011 Arnout Engelen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
*USA.
*
*/
#include <iostream>
#include <strings.h>
#include <string>
#include <ncurses.h>
#ifndef __APPLE__
#include <asm/types.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <pwd.h>
#include <map>
#include "process.h"
#include "nethogs.h"
#include "inode2prog.h"
#include "conninode.h"
extern timeval curtime;
/*
* connection-inode table. takes information from /proc/net/tcp.
* key contains source ip, source port, destination ip, destination
* port in format: '1.2.3.4:5-1.2.3.4:5'
*/
extern std::map<std::string, unsigned long> conninode;
/* this file includes:
* - calls to inodeproc to get the pid that belongs to that inode
*/
/*
* Initialise the global process-list with some special processes:
* * unknown TCP traffic
* * UDP traffic
* * unknown IP traffic
* We must take care these never get removed from the list.
*/
Process *unknowntcp;
Process *unknownudp;
Process *unknownip;
ProcList *processes;
/* We're migrating to having several `unknown' processes that are added as
* normal processes, instead of hard-wired unknown processes.
* This mapping maps from unknown processes descriptions to processes */
std::map<std::string, Process *> unknownprocs;
float tomb(u_int32_t bytes) { return ((double)bytes) / 1024 / 1024; }
float tokb(u_int32_t bytes) { return ((double)bytes) / 1024; }
float tokbps(u_int32_t bytes) { return (((double)bytes) / PERIOD) / 1024; }
void process_init() {
unknowntcp = new Process(0, "", "unknown TCP");
// unknownudp = new Process (0, "", "unknown UDP");
// unknownip = new Process (0, "", "unknown IP");
processes = new ProcList(unknowntcp, NULL);
// processes = new ProcList (unknownudp, processes);
// processes = new ProcList (unknownip, processes);
}
int Process::getLastPacket() {
int lastpacket = 0;
ConnList *curconn = connections;
while (curconn != NULL) {
assert(curconn != NULL);
assert(curconn->getVal() != NULL);
if (curconn->getVal()->getLastPacket() > lastpacket)
lastpacket = curconn->getVal()->getLastPacket();
curconn = curconn->getNext();
}
return lastpacket;
}
/** Get the kb/s values for this process */
void Process::getkbps(float *recvd, float *sent) {
u_int32_t sum_sent = 0, sum_recv = 0;
/* walk though all this process's connections, and sum
* them up */
ConnList *curconn = this->connections;
ConnList *previous = NULL;
while (curconn != NULL) {
if (curconn->getVal()->getLastPacket() <= curtime.tv_sec - CONNTIMEOUT) {
/* stalled connection, remove. */
ConnList *todelete = curconn;
Connection *conn_todelete = curconn->getVal();
curconn = curconn->getNext();
if (todelete == this->connections)
this->connections = curconn;
if (previous != NULL)
previous->setNext(curconn);
delete (todelete);
delete (conn_todelete);
} else {
u_int32_t sent = 0, recv = 0;
curconn->getVal()->sumanddel(curtime, &recv, &sent);
sum_sent += sent;
sum_recv += recv;
previous = curconn;
curconn = curconn->getNext();
}
}
*recvd = tokbps(sum_recv);
*sent = tokbps(sum_sent);
}
/** get total values for this process */
void Process::gettotal(u_int32_t *recvd, u_int32_t *sent) {
u_int32_t sum_sent = 0, sum_recv = 0;
ConnList *curconn = this->connections;
while (curconn != NULL) {
Connection *conn = curconn->getVal();
sum_sent += conn->sumSent;
sum_recv += conn->sumRecv;
curconn = curconn->getNext();
}
// std::cout << "Sum sent: " << sum_sent << std::endl;
// std::cout << "Sum recv: " << sum_recv << std::endl;
*recvd = sum_recv;
*sent = sum_sent;
}
void Process::gettotalmb(float *recvd, float *sent) {
u_int32_t sum_sent = 0, sum_recv = 0;
gettotal(&sum_recv, &sum_sent);
*recvd = tomb(sum_recv);
*sent = tomb(sum_sent);
}
/** get total values for this process */
void Process::gettotalkb(float *recvd, float *sent) {
u_int32_t sum_sent = 0, sum_recv = 0;
gettotal(&sum_recv, &sum_sent);
*recvd = tokb(sum_recv);
*sent = tokb(sum_sent);
}
void Process::gettotalb(float *recvd, float *sent) {
u_int32_t sum_sent = 0, sum_recv = 0;
gettotal(&sum_recv, &sum_sent);
// std::cout << "Total sent: " << sum_sent << std::endl;
*sent = sum_sent;
*recvd = sum_recv;
}
Process *findProcess(struct prg_node *node) {
ProcList *current = processes;
while (current != NULL) {
Process *currentproc = current->getVal();
assert(currentproc != NULL);
if (node->pid == currentproc->pid)
return current->getVal();
current = current->next;
}
return NULL;
}
/* finds process based on inode, if any */
/* should be done quickly after arrival of the packet,
* otherwise findPID will be outdated */
Process *findProcess(unsigned long inode) {
struct prg_node *node = findPID(inode);
if (node == NULL)
return NULL;
return findProcess(node);
}
int ProcList::size() {
int i = 1;
if (next != NULL)
i += next->size();
return i;
}
void check_all_procs() {
ProcList *curproc = processes;
while (curproc != NULL) {
curproc->getVal()->check();
curproc = curproc->getNext();
}
}
/*
* returns the process from proclist with matching pid
* if the inode is not associated with any PID, return NULL
* if the process is not yet in the proclist, add it
*/
Process *getProcess(unsigned long inode, const char *devicename) {
struct prg_node *node = findPID(inode);
if (node == NULL) {
if (DEBUG || bughuntmode)
std::cout << "No PID information for inode " << inode << std::endl;
return NULL;
}
Process *proc = findProcess(node);
if (proc != NULL)
return proc;
Process *newproc = new Process(inode, devicename, node->name.c_str());
newproc->pid = node->pid;
char procdir[100];
sprintf(procdir, "/proc/%d", node->pid);
struct stat stats;
int retval = stat(procdir, &stats);
/* 0 seems a proper default.
* used in case the PID disappeared while nethogs was running
* TODO we can store node->uid this while info on the inodes,
* right? */
/*
if (!ROBUST && (retval != 0))
{
std::cerr << "Couldn't stat " << procdir << std::endl;
assert (false);
}
*/
if (retval != 0)
newproc->setUid(0);
else
newproc->setUid(stats.st_uid);
/*if (getpwuid(stats.st_uid) == NULL) {
std::stderr << "uid for inode
if (!ROBUST)
assert(false);
}*/
processes = new ProcList(newproc, processes);
return newproc;
}
/*
* Used when a new connection is encountered. Finds corresponding
* process and adds the connection. If the connection doesn't belong
* to any known process, the process list is updated and a new process
* is made. If no process can be found even then, it's added to the
* 'unknown' process.
*/
Process *getProcess(Connection *connection, const char *devicename) {
unsigned long inode = conninode[connection->refpacket->gethashstring()];
if (inode == 0) {
// no? refresh and check conn/inode table
if (bughuntmode) {
std::cout << "? new connection not in connection-to-inode table before "
"refresh, hash " << connection->refpacket->gethashstring()
<< std::endl;
}
// refresh the inode->pid table first. Presumably processing the renewed
// connection->inode table
// is slow, making this worthwhile.
// We take the fact for granted that we might already know the inode->pid
// (unlikely anyway if we
// haven't seen the connection->inode yet though).
#ifndef __APPLE__
reread_mapping();
#endif
refreshconninode();
inode = conninode[connection->refpacket->gethashstring()];
if (bughuntmode) {
if (inode == 0) {
std::cout << ":( inode for connection not found after refresh.\n";
} else {
std::cout << ":) inode for connection found after refresh.\n";
}
}
#if REVERSEHACK
if (inode == 0) {
/* HACK: the following is a hack for cases where the
* 'local' addresses aren't properly recognised, as is
* currently the case for IPv6 */
/* we reverse the direction of the stream if
* successful. */
Packet *reversepacket = connection->refpacket->newInverted();
inode = conninode[reversepacket->gethashstring()];
if (inode == 0) {
delete reversepacket;
if (bughuntmode || DEBUG)
std::cout << "LOC: " << connection->refpacket->gethashstring()
<< " STILL not in connection-to-inode table - adding to "
"the unknown process\n";
unknowntcp->connections =
new ConnList(connection, unknowntcp->connections);
return unknowntcp;
}
delete connection->refpacket;
connection->refpacket = reversepacket;
}
#endif
} else if (bughuntmode) {
std::cout
<< ";) new connection in connection-to-inode table before refresh.\n";
}
if (bughuntmode) {
std::cout << " inode # " << inode << std::endl;
}
Process *proc = NULL;
if (inode != 0)
proc = getProcess(inode, devicename);
if (proc == NULL) {
proc = new Process(inode, "", connection->refpacket->gethashstring());
processes = new ProcList(proc, processes);
}
proc->connections = new ConnList(connection, proc->connections);
return proc;
}
void procclean() {
// delete conninode;
prg_cache_clear();
}

132
src/process.h Normal file
View File

@@ -0,0 +1,132 @@
/*
* process.h
*
* Copyright (c) 2004-2006,2008,2011 Arnout Engelen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
*USA.
*
*/
#ifndef __PROCESS_H
#define __PROCESS_H
#include <cassert>
#include "nethogs.h"
#include "connection.h"
extern bool tracemode;
extern bool bughuntmode;
void check_all_procs();
class ConnList {
public:
ConnList(Connection *m_val, ConnList *m_next) {
assert(m_val != NULL);
val = m_val;
next = m_next;
}
~ConnList() {
/* does not delete its value, to allow a connection to
* remove itself from the global connlist in its destructor */
}
Connection *getVal() { return val; }
void setNext(ConnList *m_next) { next = m_next; }
ConnList *getNext() { return next; }
private:
Connection *val;
ConnList *next;
};
class Process {
public:
/* the process makes a copy of the name. the device name needs to be stable.
*/
Process(const unsigned long m_inode, const char *m_devicename,
const char *m_name = NULL)
: inode(m_inode) {
// std::cout << "ARN: Process created with dev " << m_devicename <<
// std::endl;
if (DEBUG)
std::cout << "PROC: Process created at " << this << std::endl;
if (m_name == NULL)
name = NULL;
else
name = strdup(m_name);
devicename = m_devicename;
connections = NULL;
pid = 0;
uid = 0;
}
void check() { assert(pid >= 0); }
~Process() {
free(name);
if (DEBUG)
std::cout << "PROC: Process deleted at " << this << std::endl;
}
int getLastPacket();
void gettotal(u_int32_t *recvd, u_int32_t *sent);
void getkbps(float *recvd, float *sent);
void gettotalmb(float *recvd, float *sent);
void gettotalkb(float *recvd, float *sent);
void gettotalb(float *recvd, float *sent);
char *name;
const char *devicename;
int pid;
ConnList *connections;
uid_t getUid() { return uid; }
void setUid(uid_t m_uid) { uid = m_uid; }
unsigned long getInode() { return inode; }
private:
const unsigned long inode;
uid_t uid;
};
class ProcList {
public:
ProcList(Process *m_val, ProcList *m_next) {
assert(m_val != NULL);
val = m_val;
next = m_next;
}
int size();
Process *getVal() { return val; }
ProcList *getNext() { return next; }
ProcList *next;
private:
Process *val;
};
Process *getProcess(Connection *connection, const char *devicename = NULL);
void process_init();
void refreshconninode();
void procclean();
#endif

View File

@@ -0,0 +1,25 @@
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
0: 00000000:0050 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 19316 1 ffff88041a179800 100 0 0 10 0
1: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 4086615 1 ffff8800461b4800 100 0 0 10 0
2: 0100007F:1B58 00000000:0000 0A 00000000:00000000 00:00000000 00000000 115 0 26988 1 ffff8800c6263040 100 0 0 10 0
3: 0100007F:1538 00000000:0000 0A 00000000:00000000 00:00000000 00000000 131 0 13278 1 ffff880036a60800 100 0 0 10 0
4: 0100007F:0019 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 28687 1 ffff88041b65f800 100 0 0 10 0
5: 0100007F:9B7A 00000000:0000 0A 00000000:00000000 00:00000000 00000000 115 0 28750 1 ffff88041a6c9040 100 0 0 10 0
6: 00000000:01BB 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 19318 1 ffff88041a179040 100 0 0 10 0
7: 0100007F:1C1F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 115 0 28749 1 ffff88041b65f040 100 0 0 10 0
8: 0100007F:14E1 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 24760 1 ffff880036a60040 100 0 0 10 0
9: 00000000:14E9 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 20847 1 ffff8800c6263800 100 0 0 10 0
10: 0100007F:0CEA 00000000:0000 0A 00000000:00000000 00:00000000 00000000 118 0 23055 1 ffff88041b566800 100 0 0 10 0
11: 16B2A8C0:EB00 7AA34D36:01BB 01 00000000:00000000 00:00000000 00000000 0 0 3625687 1 ffff8802f81f0800 23 4 28 10 -1
12: 16B2A8C0:C2FA 1A5097C2:01BB 06 00000000:00000000 03:00001184 00000000 0 0 0 3 ffff8801288f0ef0
13: 16B2A8C0:C2FC 1A5097C2:01BB 01 00000000:00000000 02:000001E6 00000000 1000 0 4391829 2 ffff88011f795800 22 4 26 10 7
14: 16B2A8C0:C162 E8675436:01BB 01 00000000:00000000 02:0000040D 00000000 1000 0 3730430 2 ffff8802debaa040 32 4 30 10 7
15: 16B2A8C0:BB92 5AFC1EC0:01BB 01 00000000:00000000 02:00001040 00000000 1000 0 4362368 2 ffff8801cff91040 29 4 29 10 -1
16: 16B2A8C0:D916 463A4834:01BB 01 00000000:00000000 02:0000085A 00000000 1000 0 4258238 2 ffff88025ccf5040 32 4 30 10 -1
17: 16B2A8C0:C150 E8675436:01BB 01 00000000:00000000 02:0000078D 00000000 1000 0 3689096 2 ffff8804195f7800 32 4 30 10 7
18: 16B2A8C0:A9BA 58FC1EC0:01BB 01 00000000:00000000 02:00001040 00000000 1000 0 4372829 2 ffff8801e1715800 30 4 29 10 -1
19: 16B2A8C0:B600 5BFC1EC0:01BB 01 00000000:00000000 02:00001040 00000000 1000 0 4152135 2 ffff88007c33c040 30 4 29 10 -1
20: 16B2A8C0:BCC8 5AFC1EC0:01BB 01 00000000:00000000 02:00001040 00000000 1000 0 4396886 2 ffff88011f795040 30 4 25 10 -1
21: 16B2A8C0:8C86 2EB2A8C0:1F49 01 00000000:00000000 02:000006A6 00000000 1000 0 4271722 2 ffff8804195f7040 21 4 22 10 -1
22: 16B2A8C0:8106 19CEFCC6:01BB 01 00000000:00000000 02:0000048D 00000000 1000 0 4400836 2 ffff8802504c1040 30 4 21 10 -1
23: 16B2A8C0:C026 3C6D5A34:01BB 01 00000000:00000000 02:00000B73 00000000 1000 0 3411654 2 ffff880220c79800 29 4 30 10 -1

48025
src/testfiles/proc_net_tcp_big Normal file

File diff suppressed because it is too large Load Diff