Commit 8dce7285 authored by David Redondo's avatar David Redondo 🏎
Browse files

Use sock_diag netlink interface to get sockets information

Saves parsing all these files in /proc/net. For now the old code path is
retained as fallback when there is some error communicating with the kernel.
parent 5936057e
......@@ -74,6 +74,15 @@ set_package_properties(
PURPOSE "libpcap is used for per-application network usage."
)
find_package(NL)
set_package_properties(NL PROPERTIES
TYPE RECOMMENDED
PURPOSE "Used for gathering socket info via the sock_diag netlink subsystem."
)
if(libpcap_FOUND AND NL_FOUND)
set(BUILD_NETWORK_PLUGIN TRUE)
endif()
if (libpcap_FOUND)
find_package(Libcap)
set_package_properties(Libcap PROPERTIES
......
#
# Find the native netlink includes and library
#
# If they exist, differentiate between versions 1, 2 and 3.
# Version 1 does not have netlink/version.h
# Version 2 started separating libraries (libnl{,-genl,-route}).
# Version 3 (>= 3.2) started appending the major version number as suffix to
# library names (libnl-3)
#
# NL_INCLUDE_DIRS - where to find libnl.h, etc.
# NL_LIBRARIES - List of libraries when using libnl.
# NL_FOUND - True if libnl found.
if(NL_LIBRARIES AND NL_INCLUDE_DIRS)
# in cache already
SET(NL_FOUND TRUE)
else()
SET( SEARCHPATHS
/opt/local
/sw
/usr
/usr/local
)
find_package(PkgConfig)
pkg_check_modules(NL3 libnl-3.0 libnl-genl-3.0 libnl-route-3.0)
if(NOT NL3_FOUND)
pkg_search_module(NL2 libnl-2.0)
endif()
# Try to find NL 2.0, 3.0 or 3.1 (/usr/include/netlink/version.h) or
# NL >= 3.2 (/usr/include/libnl3/netlink/version.h)
find_path(NL3_INCLUDE_DIR
PATH_SUFFIXES
include/libnl3
include
NAMES
netlink/version.h
HINTS
"${NL3_libnl-3.0_INCLUDEDIR}"
"${NL2_INCLUDEDIR}"
PATHS
$(SEARCHPATHS)
)
# NL version >= 2
if(NL3_INCLUDE_DIR)
find_library(NL3_LIBRARY
NAMES
nl-3 nl
PATH_SUFFIXES
lib64 lib
HINTS
"${NL3_libnl-3.0_LIBDIR}"
"${NL2_LIBDIR}"
PATHS
$(SEARCHPATHS)
)
#
# If we don't have all of those libraries, we can't use libnl.
#
if(NL3_LIBRARY)
set(NL_LIBRARY ${NL3_LIBRARY})
if(NL3_INCLUDE_DIR)
# NL2 and NL3 are similar and just affect how the version is reported in
# the --version output. In cast of doubt, assume NL3 since a library
# without version number could be any of 2.0, 3.0 or 3.1.
if(NOT NL3_FOUND AND NL2_FOUND)
set(HAVE_LIBNL2 1)
else()
set(HAVE_LIBNL3 1)
endif()
endif()
endif()
set(NL_INCLUDE_DIR ${NL3_INCLUDE_DIR})
endif()
# libnl-2 and libnl-3 not found, try NL version 1
if(NOT (NL_LIBRARY AND NL_INCLUDE_DIR))
pkg_search_module(NL1 libnl-1)
find_path(NL1_INCLUDE_DIR
NAMES
netlink/netlink.h
HINTS
"${NL1_INCLUDEDIR}"
PATHS
$(SEARCHPATHS)
)
find_library(NL1_LIBRARY
NAMES
nl
PATH_SUFFIXES
lib64 lib
HINTS
"${NL1_LIBDIR}"
PATHS
$(SEARCHPATHS)
)
set(NL_LIBRARY ${NL1_LIBRARY})
set(NL_INCLUDE_DIR ${NL1_INCLUDE_DIR})
if(NL1_LIBRARY AND NL1_INCLUDE_DIR)
set(HAVE_LIBNL1 1)
endif()
endif()
endif()
# MESSAGE(STATUS "LIB Found: ${NL_LIBRARY}, Suffix: ${NLSUFFIX}\n 1:${HAVE_LIBNL1}, 2:${HAVE_LIBNL2}, 3:${HAVE_LIBNL3}.")
# handle the QUIETLY and REQUIRED arguments and set NL_FOUND to TRUE if
# all listed variables are TRUE
INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(NL DEFAULT_MSG NL_LIBRARY NL_INCLUDE_DIR)
IF(NL_FOUND)
set(NL_LIBRARIES ${NL_LIBRARY})
set(NL_INCLUDE_DIRS ${NL_INCLUDE_DIR})
set(HAVE_LIBNL 1)
else()
set(NL_LIBRARIES )
set(NL_INCLUDE_DIRS)
endif()
MARK_AS_ADVANCED( NL_LIBRARIES NL_INCLUDE_DIRS )
add_definitions(-DTRANSLATION_DOMAIN=\"ksysguard_plugins_process\")
if (libpcap_FOUND)
if (BUILD_NETWORK_PLUGIN)
add_subdirectory(network)
endif()
add_subdirectory(nvidia)
......@@ -8,8 +8,8 @@ set(ksgrd_network_helper_SRCS
)
add_executable(ksgrd_network_helper ${ksgrd_network_helper_SRCS})
target_include_directories(ksgrd_network_helper PUBLIC ${PCAP_INCLUDE_DIR})
target_link_libraries(ksgrd_network_helper ${PCAP_LIBRARY})
target_include_directories(ksgrd_network_helper PUBLIC ${PCAP_INCLUDE_DIR} ${NL_INCLUDE_DIRS})
target_link_libraries(ksgrd_network_helper ${PCAP_LIBRARY} ${NL_LIBRARIES})
kde_target_enable_exceptions(ksgrd_network_helper PUBLIC)
set_target_properties(ksgrd_network_helper PROPERTIES CXX_STANDARD 14 CXX_STANDARD_REQUIRED TRUE)
......
/*
* This file is part of KSysGuard.
* Copyright 2019 Arjen Hiemstra <ahiemstra@heimr.nl>
* Copyright 2020 David Redondo <kde@david-redondo.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
......@@ -28,6 +29,13 @@
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <linux/inet_diag.h>
#include <linux/sock_diag.h>
#include <netlink/msg.h>
#include <netlink/netlink.h>
using namespace std::string_literals;
// Convert /proc/net/tcp's mangled big-endian notation to a host-endian int32'
......@@ -41,7 +49,34 @@ uint32_t tcpToInt(const std::string &part)
return result;
}
int parseInetDiagMesg(struct nl_msg *msg, void *arg)
{
auto self = static_cast<ConnectionMapping*>(arg);
struct nlmsghdr *nlh = nlmsg_hdr(msg);
auto inetDiagMsg = static_cast<inet_diag_msg*>(nlmsg_data(nlh));
Packet::Address localAddress;
if (inetDiagMsg->idiag_family == AF_INET) {
// I expected to need ntohl calls here and bewlow for src but comparing to values gathered
// by parsing proc they are not needed and even produce wrong results
localAddress.address[3] = inetDiagMsg->id.idiag_src[0];
} else if (inetDiagMsg->id.idiag_src[0] == 0 && inetDiagMsg->id.idiag_src[1] == 0 && inetDiagMsg->id.idiag_src[2] == 0xffff0000) {
// Some applications (like Steam) use ipv6 sockets with ipv4.
// This results in ipv4 addresses that end up in the tcp6 file.
// They seem to start with 0000000000000000FFFF0000, so if we
// detect that, assume it is ipv4-over-ipv6.
localAddress.address[3] = inetDiagMsg->id.idiag_src[3];
} else {
std::memcpy(localAddress.address.data(), inetDiagMsg->id.idiag_src, sizeof(localAddress.address));
}
localAddress.port = ntohs(inetDiagMsg->id.idiag_sport);
self->m_localToINode[localAddress] = inetDiagMsg->idiag_inode;
self->m_inodes.insert(inetDiagMsg->idiag_inode);
return NL_OK;
}
ConnectionMapping::ConnectionMapping()
: m_socket(nl_socket_alloc(), nl_socket_free)
{
m_socketFileMatch =
// Format of /proc/net/tcp is:
......@@ -51,7 +86,9 @@ ConnectionMapping::ConnectionMapping()
// Since we care only about local address, local port and inode we ignore the middle 70 characters.
std::regex("\\s*\\d+: (?:(\\w{8})|(\\w{32})):([A-F0-9]{4}) (.{94}|.{70}) (\\d+) .*", std::regex::ECMAScript | std::regex::optimize);
parseProc();
nl_connect(m_socket.get(), NETLINK_SOCK_DIAG);
nl_socket_modify_cb(m_socket.get(), NL_CB_VALID, NL_CB_CUSTOM, &parseInetDiagMesg, this);
getSocketInfo();
}
ConnectionMapping::PacketResult ConnectionMapping::pidForPacket(const Packet &packet)
......@@ -62,7 +99,7 @@ ConnectionMapping::PacketResult ConnectionMapping::pidForPacket(const Packet &pa
auto destInode = m_localToINode.find(packet.destinationAddress());
if (sourceInode == m_localToINode.end() && destInode == m_localToINode.end()) {
parseProc();
getSocketInfo();
sourceInode = m_localToINode.find(packet.sourceAddress());
destInode = m_localToINode.find(packet.destinationAddress());
......@@ -90,29 +127,56 @@ ConnectionMapping::PacketResult ConnectionMapping::pidForPacket(const Packet &pa
return result;
}
void ConnectionMapping::parseProc()
void ConnectionMapping::getSocketInfo()
{
//TODO: Consider using INET_DIAG netlink protocol for retrieving socket information.
if (parseSockets())
auto oldInodes = m_inodes;
m_inodes.clear();
m_localToINode.clear();
if (!dumpSockets()) {
// There was some error with sock_diag
parseSockets();
}
if (m_inodes != oldInodes) {
parsePid();
}
}
bool ConnectionMapping::parseSockets()
bool ConnectionMapping::dumpSockets()
{
auto oldInodes = m_inodes;
for (auto family : {AF_INET, AF_INET6}) {
for (auto protocol : {IPPROTO_TCP, IPPROTO_UDP}) {
if (!dumpSockets(family, protocol)) {
return false;
}
}
}
return true;
}
m_inodes.clear();
m_localToINode.clear();
bool ConnectionMapping::dumpSockets(int inet_family, int protocol)
{
inet_diag_req_v2 inet_request;
inet_request.id = {};
inet_request.sdiag_family = inet_family;
inet_request.sdiag_protocol = protocol;
inet_request.idiag_states = -1;
if (nl_send_simple(m_socket.get(), SOCK_DIAG_BY_FAMILY, NLM_F_DUMP | NLM_F_REQUEST, &inet_request, sizeof(inet_diag_req_v2)) < 0) {
return false;
}
if (nl_recvmsgs_default(m_socket.get()) != 0) {
return false;
}
return true;
}
void ConnectionMapping::parseSockets()
{
parseSocketFile("/proc/net/tcp");
parseSocketFile("/proc/net/udp");
parseSocketFile("/proc/net/tcp6");
parseSocketFile("/proc/net/udp6");
if (m_inodes == oldInodes) {
return false;
}
return true;
}
void ConnectionMapping::parsePid()
......
......@@ -26,10 +26,12 @@
#include <unordered_set>
#include <regex>
// #include <QRegularExpression>
#include <netlink/socket.h>
#include "Packet.h"
struct nl_msg;
/**
* @todo write docs
*/
......@@ -43,12 +45,13 @@ public:
ConnectionMapping();
void parseProc();
PacketResult pidForPacket(const Packet &packet);
private:
bool parseSockets();
void getSocketInfo();
bool dumpSockets();
bool dumpSockets(int inet_family, int protocol);
void parseSockets();
void parsePid();
void parseSocketFile(const char* fileName);
......@@ -56,9 +59,10 @@ private:
std::unordered_map<int, int> m_inodeToPid;
std::unordered_set<int> m_inodes;
std::unordered_set<int> m_pids;
// QRegularExpression m_socketFileMatch;
std::unique_ptr<nl_sock, decltype(&nl_socket_free)> m_socket;
std::regex m_socketFileMatch;
friend int parseInetDiagMesg(struct nl_msg *msg, void *arg);
};
#endif // CONNECTIONMAPPING_H
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment