Skip to content

Commit

Permalink
Add utility to determine if ip is local external (#8)
Browse files Browse the repository at this point in the history
* Added Utility to termine whether ipv4 is external local

* Added gsl to conanfile

* Added recognizing ipv4 link local addresses

* File NetlinkCalls added

Co-authored-by: Hubert Parzych <hubert.parzych@dynatrace.com>
  • Loading branch information
pawsten and hparzych authored Oct 5, 2023
1 parent 4b434a2 commit a6bb15a
Show file tree
Hide file tree
Showing 8 changed files with 445 additions and 2 deletions.
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,14 @@ endif(NOT TARGET fmt)

find_package(Protobuf REQUIRED)


find_package(Microsoft.GSL REQUIRED)
if(Microsoft.GSL_FOUND)
include_directories(${Microsoft.GSL_INCLUDE_DIRS})
endif()

set(INSTALL_GTEST OFF CACHE BOOL "Disable installation of googletest" FORCE)

if(BUILD_TESTS OR BUILD_BPF_TESTS)
include(FetchContent)
FetchContent_Declare(googletest URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip)
Expand Down
1 change: 1 addition & 0 deletions conanfile.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[requires]
protobuf/3.21.9
protobuf-c/1.4.1
ms-gsl/4.0.0
fmt/10.1.1
spdlog/1.12.0
gtest/1.14.0
Expand Down
7 changes: 5 additions & 2 deletions libebpfdiscovery/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ list(
SOURCES
src/Config.cpp
src/Discovery.cpp
src/IpAddressChecker.cpp
src/NetlinkCalls.cpp
src/Session.cpp
src/StringFunctions.cpp
)
Expand All @@ -11,6 +13,7 @@ set(TARGET ebpfdiscovery)
add_library(${TARGET} STATIC ${SOURCES})

target_include_directories(${TARGET} PRIVATE src PUBLIC headers)

target_link_libraries(${TARGET} bpfload)
target_link_libraries(${TARGET} ebpfdiscoveryshared)
target_link_libraries(${TARGET} ebpfdiscoveryskel)
Expand All @@ -19,11 +22,11 @@ target_link_libraries(${TARGET} httpparser)
target_link_libraries(${TARGET} ebpfdiscoveryproto)

if(BUILD_TESTS)
list(APPEND TEST_SOURCES test/StringFunctionsTest.cpp test/LRUCacheTest.cpp)
list(APPEND TEST_SOURCES test/StringFunctionsTest.cpp test/LRUCacheTest.cpp test/IpAddressCheckerTest.cpp)
set(TEST_TARGET test${TARGET})

add_executable(${TEST_TARGET} ${TEST_SOURCES})
target_link_libraries(${TEST_TARGET} GTest::gtest_main ${TARGET})
target_link_libraries(${TEST_TARGET} GTest::gtest_main GTest::gmock_main ${TARGET})
target_include_directories(${TEST_TARGET} PRIVATE src)
gtest_discover_tests(${TEST_TARGET})
endif()
39 changes: 39 additions & 0 deletions libebpfdiscovery/headers/ebpfdiscovery/IpAddressChecker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include "ebpfdiscovery/NetlinkCalls.h"
#include <initializer_list>
#include <vector>


namespace ebpfdiscovery {

using IPv4int = uint32_t;

struct IpIfce {
std::vector<IPv4int> ip;
std::vector<IPv4int> broadcast;
uint32_t mask;
int index;
bool isLocalBridge;
};

class IpAddressChecker {
std::vector<IpIfce> interfaces;
std::vector<IpIfce>::iterator bridgeEnd = interfaces.end();
const NetlinkCalls& netlink;

bool readAllIpAddrs();
bool markLocalBridges();
bool isLoopback(const IpIfce&);
void addIpIfce(IpIfce&& ifce);
void markBridge(int idx);
protected:
void moveBridges();
public:
IpAddressChecker(const NetlinkCalls &calls);
IpAddressChecker(std::initializer_list<IpIfce> config, const NetlinkCalls &calls);
bool isAddressExternalLocal(IPv4int addr);
bool readNetworks();
};
} // namespace ebpfdiscovery

17 changes: 17 additions & 0 deletions libebpfdiscovery/headers/ebpfdiscovery/NetlinkCalls.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <cstdint>
#include <stddef.h>

struct sockaddr_nl;

namespace ebpfdiscovery {

class NetlinkCalls {
public:
virtual int sendIpAddrRequest(int fd, sockaddr_nl* dst, int domain) const;
virtual int sendBridgesRequest(int fd, sockaddr_nl* dst, int domain) const;
virtual int receive(int fd, sockaddr_nl* dst, void* buf, size_t len) const;
};
} // namespace ebpfdiscovery

199 changes: 199 additions & 0 deletions libebpfdiscovery/src/IpAddressChecker.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// SPDX-License-Identifier: Apache-2.0
#include "ebpfdiscovery/IpAddressChecker.h"

#include <algorithm>
#include <arpa/inet.h>
#include <array>
#include <cstring>
#include <errno.h>
#include <gsl/util>
#include <iostream>
#include <linux/rtnetlink.h>
#include <string>
#include <string_view>
#include <unistd.h>

static constexpr uint32_t BUFLEN{4096};
static constexpr uint32_t IP_CLASS_C{0x0000a8c0}; // 192.168.*.*
static constexpr uint32_t MASK_CLASS_C{0x0000ffff};
static constexpr uint32_t IP_CLASS_B{0x000010ac}; // 172.16-31.*.*
static constexpr uint32_t MASK_CLASS_B{0x0000f0ff};
static constexpr uint32_t IP_CLASS_A{0x0000000a}; // 10.*.*.*
static constexpr uint32_t MASK_CLASS_A{0x000000ff};
static constexpr uint32_t IP_LINK_LOCAL{0x0000fea9}; // 169.254.*.*
static constexpr uint32_t MASK_LINK_LOCAL{0x0000ffff};
static constexpr uint32_t IP_LOOPBACK{0x0000007f}; // 127.0.*.*
static constexpr uint32_t MASK_LOOPBACK{0x00ffffff};

static void logErrorFromErrno(std::string_view prefix) {
std::cout << prefix << ": " << strerror(errno) << "\n";
}

static ebpfdiscovery::IpIfce parseIfceIPv4(void* data, size_t len) {
ebpfdiscovery::IpIfce ifce{};
ifaddrmsg* ifa = reinterpret_cast<ifaddrmsg*>(data);
ifce.index = ifa->ifa_index;
ifce.mask = htonl(-1 << (32 - ifa->ifa_prefixlen));
rtattr* rta = reinterpret_cast<rtattr*>(IFA_RTA(data));

for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
in_addr* addr = reinterpret_cast<in_addr*>(RTA_DATA(rta));

if (rta->rta_type == IFA_ADDRESS) {
ifce.ip.push_back(addr->s_addr);
}

if (rta->rta_type == IFA_BROADCAST) {
ifce.broadcast.push_back(addr->s_addr);
}
}

return ifce;
}

static ebpfdiscovery::IpIfce parseIfce(void* data, size_t len) {
if (reinterpret_cast<ifaddrmsg*>(data)->ifa_family != AF_INET) {
return {};
}

return parseIfceIPv4(data, len);
}

static int getIfIndex(void* data) {
ifinfomsg* ifa = reinterpret_cast<ifinfomsg*>(data);
return ifa->ifi_index;
}

namespace ebpfdiscovery {

IpAddressChecker::IpAddressChecker(std::initializer_list<IpIfce> config, const NetlinkCalls &calls) :netlink(calls) {
interfaces.insert(interfaces.end(), config.begin(), config.end());
}

IpAddressChecker::IpAddressChecker(const NetlinkCalls &calls) :netlink(calls) {
}

bool IpAddressChecker::readNetworks() {
const bool ret = readAllIpAddrs();
if (markLocalBridges()) {
moveBridges();
} else {
bridgeEnd = interfaces.end();
}

return ret;
}

void IpAddressChecker::moveBridges() {
bridgeEnd = std::partition(interfaces.begin(), interfaces.end(), [](const auto& it) { return it.isLocalBridge; });
}

template <typename P>
static uint32_t parseNlMsg(void* buf, size_t len, P parse) {
const nlmsghdr* nl = reinterpret_cast<nlmsghdr*>(buf);

for (; NLMSG_OK(nl, len) && nl->nlmsg_type != NLMSG_DONE; nl = NLMSG_NEXT(nl, len)) {
if (nl->nlmsg_type == NLMSG_ERROR) {
return -1;
}

if (nl->nlmsg_type == RTM_NEWADDR || nl->nlmsg_type == RTM_NEWLINK) {
parse(NLMSG_DATA(nl), IFA_PAYLOAD(nl));
continue;
}
}

return nl->nlmsg_type;
}

template <typename S, typename P, typename R>
static bool handleNetlink(S send, R receive, P parse, int domain) {
int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (fd < 0) {
logErrorFromErrno("socket");
return false;
}

auto const _ = gsl::finally([fd]() { ::close(fd); });
sockaddr_nl sa{};
sa.nl_family = AF_NETLINK;

int len = send(fd, &sa, domain);
if (len <= 0) {
logErrorFromErrno("send");
return false;
}

uint32_t nlMsgType;
do {
std::array<char, BUFLEN> buf{};
len = receive(fd, &sa, buf.data(), BUFLEN);
if (len <= 0) {
logErrorFromErrno("receive");
break;
}

nlMsgType = parseNlMsg(buf.data(), len, parse);
} while (nlMsgType != NLMSG_DONE && nlMsgType != NLMSG_ERROR);

return true;
}

bool IpAddressChecker::readAllIpAddrs() {
return handleNetlink(
[this](int fd, sockaddr_nl* sa, int domain) { return netlink.sendIpAddrRequest(fd, sa, AF_INET); },
[this](int fd, sockaddr_nl* dst, void* buf, size_t len) { return netlink.receive(fd, dst, buf, len); },
[this](void* buf, size_t len) { addIpIfce(parseIfce(buf, len)); }, AF_INET);
}

bool IpAddressChecker::markLocalBridges() {
return handleNetlink(
[this](int fd, sockaddr_nl* sa, int domain) { return netlink.sendBridgesRequest(fd, sa, AF_INET); },
[this](int fd, sockaddr_nl* dst, void* buf, size_t len) { return netlink.receive(fd, dst, buf, len); },
[this](void* buf, size_t len) { markBridge(getIfIndex(buf)); }, AF_INET);
}

void IpAddressChecker::addIpIfce(IpIfce&& ifce) {
if(!isLoopback(ifce)){
interfaces.push_back(ifce);
}
}

void IpAddressChecker::markBridge(int idx) {
auto ifc = std::find_if(interfaces.begin(), interfaces.end(), [idx](const auto& it) { return it.index == idx; });
if (ifc == interfaces.end()) {
return;
}

ifc->isLocalBridge = true;
}

bool IpAddressChecker::isLoopback(const IpIfce& ifce) {

return std::all_of(ifce.ip.begin(), ifce.ip.end(), [](const auto& ipv4) {
return ((ipv4 & MASK_LOOPBACK) == IP_LOOPBACK);
});
}

bool IpAddressChecker::isAddressExternalLocal(IPv4int addr) {
const bool isPublic = ((addr & MASK_CLASS_A) != IP_CLASS_A) && ((addr & MASK_CLASS_B) != IP_CLASS_B) && ((addr & MASK_CLASS_C) != IP_CLASS_C);

if (isPublic) {
return false;
}

if ((addr & MASK_LINK_LOCAL) == IP_LINK_LOCAL) {
return false;
}

const bool bridgeRelated = std::any_of(interfaces.begin(), bridgeEnd, [addr](const auto& it) {
return std::any_of(it.ip.begin(), it.ip.end(), [addr, mask = it.mask](const auto& ip) { return (addr & mask) == (ip & mask); });
});

if (bridgeRelated) {
return false;
}

return true;
}
} // namespace ebpfdiscovery
84 changes: 84 additions & 0 deletions libebpfdiscovery/src/NetlinkCalls.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#include "ebpfdiscovery/NetlinkCalls.h"
#include <arpa/inet.h>
#include <array>
#include <linux/rtnetlink.h>
#include <net/if.h>
#include <cstring>

namespace ebpfdiscovery {

static constexpr uint32_t BUFFLEN{4096};

static void addNetlinkMsg(nlmsghdr* nh, int type, const void* data, int dataLen) {
struct rtattr* rta;
int rta_length = RTA_LENGTH(dataLen);

rta = reinterpret_cast<rtattr*>((char*)nh + NLMSG_ALIGN(nh->nlmsg_len));

rta->rta_type = type;
rta->rta_len = rta_length;
nh->nlmsg_len = NLMSG_ALIGN(nh->nlmsg_len) + RTA_ALIGN(rta_length);

memcpy(RTA_DATA(rta), data, dataLen);
}

int NetlinkCalls::sendIpAddrRequest(int fd, sockaddr_nl* dst, int domain) const {
std::array<char, BUFFLEN> buf{};

nlmsghdr* nl;
nl = reinterpret_cast<nlmsghdr*>(buf.data());
nl->nlmsg_len = NLMSG_LENGTH(sizeof(ifaddrmsg));
nl->nlmsg_type = RTM_GETADDR;
nl->nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT;

ifaddrmsg* ifa;
ifa = reinterpret_cast<ifaddrmsg*>(NLMSG_DATA(nl));
ifa->ifa_family = domain; // ipv4 or ipv6

iovec iov = {nl, nl->nlmsg_len};
msghdr msg = {dst, sizeof(*dst), &iov, 1, NULL, 0, 0};

return sendmsg(fd, &msg, 0);
}

int NetlinkCalls::sendBridgesRequest(int fd, sockaddr_nl* dst, int domain) const {
struct {
struct nlmsghdr n;
struct ifinfomsg i;
char _[1024]; // space for rtattr array
} r{};

const char* dev_type = "bridge";

r.n.nlmsg_len = NLMSG_LENGTH(sizeof(ifinfomsg));
r.n.nlmsg_type = RTM_GETLINK;
r.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
r.i.ifi_family = AF_PACKET;
r.n.nlmsg_pid = 0;
r.n.nlmsg_seq = 0;

rtattr* linkinfo = reinterpret_cast<rtattr*>((char*)&r.n + NLMSG_ALIGN(r.n.nlmsg_len));
addNetlinkMsg(&r.n, IFLA_LINKINFO, NULL, 0);
addNetlinkMsg(&r.n, IFLA_INFO_KIND, dev_type, strlen(dev_type) + 1);
linkinfo->rta_len = (int)(reinterpret_cast<char*>(&r.n) + NLMSG_ALIGN(r.n.nlmsg_len) - reinterpret_cast<char*>(linkinfo));

iovec iov = {&r.n, r.n.nlmsg_len};
msghdr msg = {dst, sizeof(*dst), &iov, 1, NULL, 0, 0};

return sendmsg(fd, &msg, 0);
}

int NetlinkCalls::receive(int fd, sockaddr_nl* dst, void* buf, size_t len) const {
iovec iov;
msghdr msg{};
iov.iov_base = buf;
iov.iov_len = len;

msg.msg_name = dst;
msg.msg_namelen = sizeof(*dst);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;

return recvmsg(fd, &msg, 0);
}
} // namespace ebpfdiscovery
Loading

0 comments on commit a6bb15a

Please sign in to comment.