Skip to content

Commit

Permalink
Added UT for netlink layer
Browse files Browse the repository at this point in the history
  • Loading branch information
pawsten committed Oct 3, 2023
1 parent 6a1b063 commit 9c8b70b
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 71 deletions.
2 changes: 1 addition & 1 deletion libebpfdiscovery/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ if(BUILD_TESTS)
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()
15 changes: 13 additions & 2 deletions libebpfdiscovery/headers/ebpfdiscovery/IpAddressChecker.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
#include <initializer_list>
#include <vector>


struct sockaddr_nl;

namespace ebpfdiscovery {

using IPv4 = uint32_t;
Expand All @@ -16,9 +19,17 @@ struct IpIfce {
bool isLocalBridge;
};

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;
};

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

bool readAllIpAddrs();
bool markLocalBridges();
Expand All @@ -28,8 +39,8 @@ class IpAddressChecker {
protected:
void moveBridges();
public:
IpAddressChecker() = default;
IpAddressChecker(std::initializer_list<IpIfce> config);
IpAddressChecker();
IpAddressChecker(std::initializer_list<IpIfce> config, const NetlinkCalls &calls);
bool isAddressExternalLocal(IPv4 addr);
bool readNetworks();
};
Expand Down
121 changes: 64 additions & 57 deletions libebpfdiscovery/src/IpAddressChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,59 @@ static void logErrorFromErrno(std::string_view prefix) {
std::cout << prefix << ": " << strerror(errno) << "\n";
}

static int sendIpAddrRequest(int fd, sockaddr_nl* dst, int domain) {

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


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 {

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

nlmsghdr* nl;
Expand All @@ -49,20 +101,7 @@ static int sendIpAddrRequest(int fd, sockaddr_nl* dst, int domain) {
return sendmsg(fd, &msg, 0);
}

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

static int sendBridgesRequest(int fd, sockaddr_nl* dst, int domain) {
int NetlinkCalls::sendBridgesRequest(int fd, sockaddr_nl* dst, int domain) const{
struct {
struct nlmsghdr n;
struct ifinfomsg i;
Expand Down Expand Up @@ -90,7 +129,7 @@ static int sendBridgesRequest(int fd, sockaddr_nl* dst, int domain) {
}


static int receive(int fd, sockaddr_nl* dst, void* buf, size_t len) {
int NetlinkCalls::receive(int fd, sockaddr_nl* dst, void* buf, size_t len) const {
iovec iov;
msghdr msg {};
iov.iov_base = buf;
Expand All @@ -104,45 +143,11 @@ static int receive(int fd, sockaddr_nl* dst, void* buf, size_t len) {
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) {
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;
IpAddressChecker::IpAddressChecker(std::initializer_list<IpIfce> config, const NetlinkCalls &calls) :netlink(calls) {
interfaces.insert(interfaces.end(), config.begin(), config.end());
}

namespace ebpfdiscovery {

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

bool IpAddressChecker::readNetworks() {
Expand Down Expand Up @@ -178,8 +183,8 @@ static uint32_t parseNlMsg(void* buf, size_t len, P parse) {
return nl->nlmsg_type;
}

template <typename S, typename P>
static bool handleNetlink(S send, P parse, int domain) {
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");
Expand Down Expand Up @@ -213,13 +218,15 @@ static bool handleNetlink(S send, P parse, int domain) {

bool IpAddressChecker::readAllIpAddrs() {
return handleNetlink(
[](int fd, sockaddr_nl* sa, int domain) { return sendIpAddrRequest(fd, sa, AF_INET); },
[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(
[](int fd, sockaddr_nl* sa, int domain) { return sendBridgesRequest(fd, sa, AF_INET); },
[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);
}

Expand Down
59 changes: 48 additions & 11 deletions libebpfdiscovery/test/IpAddressCheckerTest.cpp
Original file line number Diff line number Diff line change
@@ -1,51 +1,88 @@
// SPDX-License-Identifier: Apache-2.0
#include "ebpfdiscovery/IpAddressChecker.h"
#include <arpa/inet.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>

using namespace ebpfdiscovery;
using namespace ::testing;

class NetlinkCallsMock : public NetlinkCalls {
public:
MOCK_METHOD(int, sendIpAddrRequest, (int fd, sockaddr_nl* dst, int domain), (const, override));
MOCK_METHOD(int, sendBridgesRequest, (int fd, sockaddr_nl* dst, int domain), (const, override));
MOCK_METHOD(int, receive, (int fd, sockaddr_nl* dst, void* buf, size_t len), (const, override));
};

class IpAddressCheckerTest : public IpAddressChecker {
protected:
public:
using IpAddressChecker::IpAddressChecker;
IpAddressCheckerTest(std::initializer_list<IpIfce> config): IpAddressChecker(config) {
IpAddressCheckerTest(std::initializer_list<IpIfce> config, const NetlinkCallsMock& mc) : IpAddressChecker(config, mc) {
moveBridges();
}
};

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

TEST(IpUtils, NOTLocalBridgeIp) {
IpAddressCheckerTest u({{{inet_addr("10.2.6.5")}, {}, 0x0000ffff, 0, true}});
TEST(IpAddressChecker, NoReadIfSendFailed) {
const NetlinkCallsMock netlinkMock;
EXPECT_CALL(netlinkMock, sendIpAddrRequest).WillOnce(Return(-1));
EXPECT_CALL(netlinkMock, sendBridgesRequest).WillOnce(Return(-1));
IpAddressCheckerTest u({}, netlinkMock);
u.readNetworks();
}

TEST(IpAddressChecker, ReadAfterSuccessfulSend) {
const NetlinkCallsMock netlinkMock;
EXPECT_CALL(netlinkMock, sendIpAddrRequest).WillOnce(Return(1));
EXPECT_CALL(netlinkMock, sendBridgesRequest).WillOnce(Return(1));
EXPECT_CALL(netlinkMock, receive).Times(2).WillRepeatedly(Return(0));
IpAddressCheckerTest u({}, netlinkMock);
u.readNetworks();
}

TEST(IpAddressChecker, ReadUntilGreaterThan0) {
const NetlinkCallsMock netlinkMock;
EXPECT_CALL(netlinkMock, sendIpAddrRequest).WillOnce(Return(1));
EXPECT_CALL(netlinkMock, sendBridgesRequest).WillOnce(Return(1));
EXPECT_CALL(netlinkMock, receive).WillOnce(Return(1)).WillOnce(Return(0)).WillOnce(Return(1)).WillOnce(Return(0));
IpAddressCheckerTest u({}, netlinkMock);
u.readNetworks();
}

TEST(IpAddressChecker, NOTLocalBridgeIp) {
const NetlinkCallsMock netlinkMock;
IpAddressCheckerTest u({{{inet_addr("10.2.6.5")}, {}, 0x0000ffff, 0, true}}, netlinkMock);
EXPECT_TRUE(u.isAddressExternalLocal(inet_addr("10.3.34.2")));
}

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

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

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

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


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

0 comments on commit 9c8b70b

Please sign in to comment.