Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add utility to determine if ip is local external #8

Merged
merged 27 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3d2dfed
Added Utility to termine whether ipv4 is external local
pawsten Sep 18, 2023
9022757
Review fixes
pawsten Sep 19, 2023
91a0fac
CMakeLists cleanup
pawsten Sep 21, 2023
46c8893
Added gsl to conanfile
pawsten Sep 21, 2023
3214d30
Cleanup
pawsten Sep 21, 2023
b375869
Added recognizing ipv4 link local addresses
pawsten Sep 21, 2023
2a6619c
Review cleanup, changed naming
pawsten Sep 21, 2023
7bdb67d
Merge branch 'main' into Add-utility-to-determine-if-ip-is-local-exte…
hparzych Sep 21, 2023
c299514
Naming changed
pawsten Sep 22, 2023
fbe0d55
Merge branch 'Add-utility-to-determine-if-ip-is-local-external' of ht…
pawsten Sep 22, 2023
49c5dcc
Function split
pawsten Sep 22, 2023
fcdab86
Conanfile gsl changed name originated from conan.io, File rename
pawsten Sep 25, 2023
605f528
GSL package name fix
pawsten Sep 25, 2023
e6e7354
Cleanup
pawsten Sep 25, 2023
8cb216c
Cleanup
pawsten Sep 25, 2023
82308b4
Review fix
pawsten Sep 25, 2023
6517126
Warning fix
pawsten Sep 25, 2023
745292a
Merge branch 'main' into Add-utility-to-determine-if-ip-is-local-exte…
hparzych Sep 26, 2023
ed32e3a
Review fixes
pawsten Sep 27, 2023
ae4f0fd
Merge branch 'Add-utility-to-determine-if-ip-is-local-external' of ht…
pawsten Sep 28, 2023
6a1b063
Review fix
pawsten Sep 28, 2023
898c2aa
Added UT for netlink layer
pawsten Oct 3, 2023
791fe2f
c-style cast removed
pawsten Oct 3, 2023
be6dee2
Build fix
pawsten Oct 3, 2023
b43731b
File NetlinkCalls added
pawsten Oct 4, 2023
aa968b8
Merge branch 'main' into Add-utility-to-determine-if-ip-is-local-exte…
pawsten Oct 5, 2023
cc94656
Review fixes
pawsten Oct 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
hparzych marked this conversation as resolved.
Show resolved Hide resolved
const NetlinkCalls& netlink;

bool readAllIpAddrs();
bool markLocalBridges();
bool isLoopback(const IpIfce&);
void addIpIfce(IpIfce&& ifce);
void markBridge(int idx);
protected:
void moveBridges();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we somehow make this private?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's protected because it's called in UT what is required.

public:
IpAddressChecker(const NetlinkCalls &calls);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GMock cookbook recommends using interfaces instead of concrete class: http://google.github.io/googletest/gmock_cook_book.html#alternative-to-mocking-concrete-classes
I'm curious, what is your opinion?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is small project and most work is one-time. I don't see value in additional boilerplate.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @pawsten

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.*.*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that both uint32_t and IPv4 are used across the files. I think that deciding upon only one of these for IPv4 addresses and masks would make the code cleaner

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) {
pawsten marked this conversation as resolved.
Show resolved Hide resolved
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 {

pawsten marked this conversation as resolved.
Show resolved Hide resolved
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);
pawsten marked this conversation as resolved.
Show resolved Hide resolved
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);
pawsten marked this conversation as resolved.
Show resolved Hide resolved
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); });
pawsten marked this conversation as resolved.
Show resolved Hide resolved
});

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