Move sources to 'src' directory
This commit is contained in:
77
src/MakeApp.mk
Normal file
77
src/MakeApp.mk
Normal 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
112
src/MakeLib.mk
Normal 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
212
src/connection.cpp
Normal 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
106
src/connection.h
Normal 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
198
src/conninode.cpp
Normal 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
23
src/conninode.h
Normal 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
24
src/conninode_test.cpp
Normal 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
442
src/cui.cpp
Normal 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
30
src/cui.h
Normal 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
321
src/decpcap.c
Normal 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
92
src/decpcap.h
Normal 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
48
src/decpcap_test.cpp
Normal 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
90
src/devices.cpp
Normal 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
50
src/devices.h
Normal 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
249
src/inode2prog.cpp
Normal 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
44
src/inode2prog.h
Normal 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
329
src/libnethogs.cpp
Normal 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
72
src/libnethogs.h
Normal 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
278
src/main.cpp
Normal 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
248
src/nethogs.cpp
Normal 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
115
src/nethogs.h
Normal 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
293
src/packet.cpp
Normal 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
84
src/packet.h
Normal 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
353
src/process.cpp
Normal 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
132
src/process.h
Normal 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
|
||||
25
src/testfiles/proc_net_tcp
Normal file
25
src/testfiles/proc_net_tcp
Normal 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
48025
src/testfiles/proc_net_tcp_big
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user