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 17 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
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ endif(NOT TARGET fmt)

find_package(Protobuf REQUIRED)

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

if(BUILD_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
[generators]
CMakeDeps
CMakeToolchain
Expand Down
4 changes: 3 additions & 1 deletion libebpfdiscovery/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ list(
SOURCES
src/Config.cpp
src/Discovery.cpp
src/IpAddressChecker.cpp
src/Log.cpp
src/Session.cpp
src/StringFunctions.cpp
Expand All @@ -12,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 @@ -20,7 +22,7 @@ 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})
Expand Down
34 changes: 34 additions & 0 deletions libebpfdiscovery/headers/ebpfdiscovery/IpAddressChecker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <cstdint>
#include <vector>

namespace ebpfdiscovery {

using IPv4 = uint32_t;
pawsten marked this conversation as resolved.
Show resolved Hide resolved

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

class IpAddressChecker {
protected:
pawsten marked this conversation as resolved.
Show resolved Hide resolved
std::vector<IpIfce> localNetsIpv4;
std::vector<IpIfce>::iterator bridgeEnd = localNetsIpv4.end();
bool readAllIpAddrs();
bool markLocalBridges();
std::vector<IpIfce>::iterator moveBridges();

public:
IpAddressChecker() = default;
bool isAddressExternalLocal(IPv4 addr);
void addIpIfce(IpIfce&& ifce);
void markBridge(int idx);
bool readNetworks();
};
} // namespace ebpfdiscovery

258 changes: 258 additions & 0 deletions libebpfdiscovery/src/IpAddressChecker.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
// 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 <net/if.h>
#include <string>
#include <string_view>
#include <unistd.h>

static constexpr uint32_t BUFLEN = 4096;
static constexpr uint32_t ipC = 0x0000a8c0; // 192.168.*.*
pawsten marked this conversation as resolved.
Show resolved Hide resolved
static constexpr uint32_t maskC = 0x0000ffff;
static constexpr uint32_t ipB = 0x000010ac; // 172.16-31.*.*
static constexpr uint32_t maskB = 0x0000f0ff;
static constexpr uint32_t ipA = 0x0000000a; // 10.*.*.*
static constexpr uint32_t maskA = 0x000000ff;
static constexpr uint32_t ipLinkLocal = 0x0000fea9; // 169.254.*.*
static constexpr uint32_t maskLinkLocal = 0x0000ffff;
pawsten marked this conversation as resolved.
Show resolved Hide resolved

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

static int sendIpAddrRequest(int fd, sockaddr_nl* sa, int domain) {
pawsten marked this conversation as resolved.
Show resolved Hide resolved
std::array<char, BUFLEN> buf{};

nlmsghdr* nl;
nl = reinterpret_cast<nlmsghdr*>(buf.data());
nl->nlmsg_len = NLMSG_LENGTH(sizeof(struct 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 = {sa, sizeof(*sa), &iov, 1, NULL, 0, 0};

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

static void addNetlinkMsg(nlmsghdr* nh, int type, const void* data, int raw_data_length) {
pawsten marked this conversation as resolved.
Show resolved Hide resolved
struct rtattr* rta;
int rta_length = RTA_LENGTH(raw_data_length);

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, raw_data_length);
}

static int sendBridgesRequest(int fd, sockaddr_nl* sa, int domain) {
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)((char*)&r.n + NLMSG_ALIGN(r.n.nlmsg_len) - (char*)linkinfo);
pawsten marked this conversation as resolved.
Show resolved Hide resolved

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

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


static int receive(int fd, sockaddr_nl* sa, void* buf, size_t len) {
iovec iov;
msghdr msg {};
iov.iov_base = buf;
iov.iov_len = len;

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

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

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, size_t len) {
pawsten marked this conversation as resolved.
Show resolved Hide resolved
ifinfomsg* ifa = reinterpret_cast<ifinfomsg*>(data);
return ifa->ifi_index;
}

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

return ret;
}

std::vector<IpIfce>::iterator IpAddressChecker::moveBridges() {
return std::partition(localNetsIpv4.begin(), localNetsIpv4.end(), [](const auto& it) { return it.isLocalBridge; });
}

template <typename F>
static uint32_t parseNlMsg(void* buf, size_t len, F 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 F>
static bool handleNetlink(S send, F f, int domain) {
pawsten marked this conversation as resolved.
Show resolved Hide resolved
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 nl_msg_type;
do {
std::array<char, BUFLEN> buf{};
len = receive(fd, &sa, buf.data(), BUFLEN);
if (len <= 0) {
logErrorFromErrno("receive");
break;
}

nl_msg_type = parseNlMsg(buf.data(), len, f);

pawsten marked this conversation as resolved.
Show resolved Hide resolved
} while (nl_msg_type != NLMSG_DONE && nl_msg_type != NLMSG_ERROR);

return true;
}

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

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

void IpAddressChecker::addIpIfce(IpIfce&& ifce) {
char ifname[IF_NAMESIZE];
if_indextoname(ifce.index, ifname);
if (strcmp(ifname, "lo") == 0) {
pawsten marked this conversation as resolved.
Show resolved Hide resolved
return;
}
localNetsIpv4.push_back(ifce);
}

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

ifc->isLocalBridge = true;
}

bool IpAddressChecker::isAddressExternalLocal(IPv4 addr) {
const bool isPublic = ((addr & maskA) != ipA) && ((addr & maskB) != ipB) && ((addr & maskC) != ipC);

if (isPublic) {
return false;
}

if ((addr & maskLinkLocal) == ipLinkLocal) {
return false;
}

const bool bridgeRelated = std::any_of(localNetsIpv4.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
55 changes: 55 additions & 0 deletions libebpfdiscovery/test/IpAddressCheckerTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: Apache-2.0
#include "ebpfdiscovery/IpAddressChecker.h"
#include <arpa/inet.h>
#include <gtest/gtest.h>
#include <initializer_list>

using namespace ebpfdiscovery;

class IpAddressCheckerTest : public IpAddressChecker {
pawsten marked this conversation as resolved.
Show resolved Hide resolved
protected:
public:
using IpAddressChecker::IpAddressChecker;
void addIfceConfig(std::initializer_list<IpIfce> config) {
localNetsIpv4.insert(localNetsIpv4.end(), config.begin(), config.end());
bridgeEnd = moveBridges();
}
};

TEST(IpUtils, LocalBridgeIp) {
IpAddressCheckerTest u;
u.addIfceConfig({{{inet_addr("10.2.4.5")}, {}, 0x0000ffff, 0, true}, {{inet_addr("10.7.4.5")}, {}, 0x0000ffff, 0, false}});
EXPECT_FALSE(u.isAddressExternalLocal(inet_addr("10.2.6.5")));
}

TEST(IpUtils, NOTLocalBridgeIp) {
IpAddressCheckerTest u;
u.addIfceConfig({{{inet_addr("10.2.6.5")}, {}, 0x0000ffff, 0, true}});
EXPECT_TRUE(u.isAddressExternalLocal(inet_addr("10.3.34.2")));
}

TEST(IpUtils, SimpleClassATest) {
IpAddressCheckerTest u;
EXPECT_TRUE(u.isAddressExternalLocal(inet_addr("192.168.1.2")));
}

TEST(IpUtils, SimpleClassBTest) {
IpAddressCheckerTest u;
EXPECT_TRUE(u.isAddressExternalLocal(inet_addr("172.20.21.2")));
}

TEST(IpUtils, SimpleClassCtest) {
IpAddressCheckerTest u;
EXPECT_TRUE(u.isAddressExternalLocal(inet_addr("10.2.4.5")));
}

TEST(IpUtils, SimpleLinkLocal) {
IpAddressCheckerTest u;
EXPECT_FALSE(u.isAddressExternalLocal(inet_addr("169.254.76.6")));
}


TEST(IpUtils, SimplePublicIp) {
IpAddressCheckerTest u;
EXPECT_FALSE(u.isAddressExternalLocal(inet_addr("170.254.76.6")));
}