blob: a42d682585b7da3e6ed85aa3fad9b0cd49988fd4 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/fit/defer.h>
#include <net/if.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <pcap.h>
#include <zircon/compiler.h>
#include <array>
#include <thread>
#include <fbl/unique_fd.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <netpacket/packet.h>
#include <pcap/sll.h>
namespace {
using ::testing::IsNull;
using ::testing::NotNull;
using ::testing::Values;
constexpr char kLoopbackDeviceName[] = "lo";
constexpr char kAnyDeviceName[] = "any";
TEST(LibpcapFindAllDevicesTest, FindAllDevices) {
pcap_if_t *devlist;
char ebuf[PCAP_ERRBUF_SIZE];
ASSERT_GE(pcap_findalldevs(&devlist, ebuf), 0) << std::string(ebuf);
auto d = fit::defer([&] {
pcap_freealldevs(devlist);
devlist = nullptr;
});
bool has_loopback = false, has_any = false;
for (pcap_if_t *dev = devlist; dev != nullptr; dev = dev->next) {
if (strcmp(dev->name, kLoopbackDeviceName) == 0) {
constexpr bpf_u_int32 kFlagsMask = PCAP_IF_UP | PCAP_IF_RUNNING | PCAP_IF_LOOPBACK |
PCAP_IF_CONNECTION_STATUS_NOT_APPLICABLE;
EXPECT_EQ(dev->flags & kFlagsMask, kFlagsMask);
EXPECT_EQ(dev->flags & ~kFlagsMask, 0u);
ASSERT_FALSE(has_loopback);
has_loopback = true;
} else if (strcmp(dev->name, kAnyDeviceName) == 0) {
constexpr bpf_u_int32 kFlagsMask =
PCAP_IF_UP | PCAP_IF_RUNNING | PCAP_IF_CONNECTION_STATUS_NOT_APPLICABLE;
EXPECT_EQ(dev->flags & kFlagsMask, kFlagsMask);
EXPECT_EQ(dev->flags & ~kFlagsMask, 0u);
ASSERT_FALSE(has_any);
has_any = true;
} else {
EXPECT_FALSE(true) << "got unexpected device with name = " << dev->name;
}
}
ASSERT_TRUE(has_loopback);
ASSERT_TRUE(has_any);
}
class LibpcapTest : public ::testing::Test {
protected:
void SetUp() override {
ASSERT_THAT(p_ = pcap_create(nullptr, ebuf()), NotNull()) << "ebuf: " << ebuf();
}
void TearDown() override {
if (p_) {
pcap_close(p_);
p_ = nullptr;
}
}
pcap_t *pcap_handle() { return p_; }
char *ebuf() { return ebuf_.data(); }
private:
std::array<char, PCAP_ERRBUF_SIZE> ebuf_ = {};
pcap_t *p_;
};
TEST_F(LibpcapTest, Breakloop) {
int res;
ASSERT_EQ(res = pcap_set_timeout(pcap_handle(), -1 /* infinite timeout */), 0)
<< pcap_statustostr(res) << "; pcap error: " << pcap_geterr(pcap_handle());
ASSERT_EQ(res = pcap_activate(pcap_handle()), 0)
<< pcap_statustostr(res) << "; pcap error: " << pcap_geterr(pcap_handle());
std::thread breaker([this]() {
// Give some time for the main test thraed to block on `pcap_dispatch`.
std::this_thread::sleep_for(std::chrono::milliseconds(10));
pcap_breakloop(pcap_handle());
});
auto d = fit::defer([&] { breaker.join(); });
res = pcap_dispatch(
pcap_handle(), 1 /* max packets */,
[](u_char *user, const pcap_pkthdr *hdr, const u_char *data) {
ADD_FAILURE() << "unexpectedly called dispatch callback";
},
nullptr /* user */);
ASSERT_GE(res, PCAP_ERROR_BREAK)
<< pcap_statustostr(res) << "; pcap error: " << pcap_geterr(pcap_handle());
}
void TestSetNonblockAndActivate(pcap_t *p, int timeout_ms, char *ebuf) {
int res;
ASSERT_EQ(res = pcap_set_timeout(p, timeout_ms), 0)
<< pcap_statustostr(res) << "; pcap error: " << pcap_geterr(p);
ASSERT_EQ(res = pcap_setnonblock(p, 1 /* nonblock */, ebuf), 0)
<< pcap_statustostr(res) << "; ebuf: " << ebuf;
ASSERT_EQ(res = pcap_activate(p), 0)
<< pcap_statustostr(res) << "; pcap error: " << pcap_geterr(p);
res = pcap_getnonblock(p, ebuf);
ASSERT_GE(res, 0) << pcap_statustostr(res) << "; ebuf: " << ebuf;
ASSERT_EQ(res, 1);
res = pcap_dispatch(
p, 1 /* max packets */,
[](u_char *user, const pcap_pkthdr *hdr, const u_char *data) {
ADD_FAILURE() << "unexpectedly called dispatch callback";
},
nullptr /* user */);
ASSERT_GE(res, 0) << pcap_statustostr(res) << "; pcap error: " << pcap_geterr(p);
EXPECT_EQ(res, 0 /* expected packets */);
}
TEST_F(LibpcapTest, NonblockWithInfiniteTimeout) {
ASSERT_NO_FATAL_FAILURE(
TestSetNonblockAndActivate(pcap_handle(), -1 /* infinite timeout */, ebuf()));
}
class LibpcapPacketTest : public LibpcapTest {
protected:
void SetUp() override {
ASSERT_NO_FATAL_FAILURE(LibpcapTest::SetUp());
udp_addr_ = {
.sin_family = AF_INET,
.sin_addr =
{
.s_addr = htonl(INADDR_LOOPBACK),
},
};
ASSERT_TRUE(udp_ = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
ASSERT_EQ(
bind(udp_.get(), reinterpret_cast<const struct sockaddr *>(&udp_addr_), sizeof(udp_addr_)),
0)
<< strerror(errno);
socklen_t addrlen = sizeof(udp_addr_);
ASSERT_EQ(getsockname(udp_.get(), reinterpret_cast<struct sockaddr *>(&udp_addr_), &addrlen), 0)
<< strerror(errno);
ASSERT_EQ(addrlen, sizeof(udp_addr_));
}
void TearDown() override {
EXPECT_EQ(udp_.reset(), 0) << strerror(errno);
LibpcapTest::TearDown();
}
void Send(uint16_t to_port = 0) {
sockaddr_in addr = udp_addr_;
if (to_port != 0) {
addr.sin_port = htons(to_port);
}
ASSERT_EQ(
sendto(udp_.get(), nullptr, 0, 0, reinterpret_cast<const sockaddr *>(&addr), sizeof(addr)),
0)
<< strerror(errno);
}
void PcapDispatch(uint8_t pkttype, int max_packets, int expected_packets,
uint16_t expected_dst_port = 0) {
uint16_t bound_udp_port = ntohs(udp_addr_.sin_port);
packet_context ctx = {
.src_port = bound_udp_port,
.dst_port = bound_udp_port,
.pkttype = pkttype,
};
if (expected_dst_port != 0) {
ctx.dst_port = expected_dst_port;
}
int res =
pcap_dispatch(pcap_handle(), max_packets, HandlePacket, reinterpret_cast<u_char *>(&ctx));
ASSERT_GE(res, 0) << pcap_statustostr(res) << "; pcap error: " << pcap_geterr(pcap_handle());
ASSERT_EQ(res, expected_packets);
}
private:
struct packet_context {
uint16_t src_port, dst_port;
uint8_t pkttype;
};
static int GetLoopbackIndex() {
const int loopback_ifindex = if_nametoindex(kLoopbackDeviceName);
EXPECT_GE(loopback_ifindex, 0) << strerror(errno);
return loopback_ifindex;
}
static void HandlePacket(u_char *user, const pcap_pkthdr *hdr, const u_char *data) {
ASSERT_THAT(user, NotNull());
ASSERT_THAT(hdr, NotNull());
ASSERT_THAT(data, NotNull());
packet_context *ctx = reinterpret_cast<packet_context *>(user);
// The timeval should not be empty.
timeval not_expected = {};
ASSERT_NE(memcmp(&hdr->ts, &not_expected, sizeof(not_expected)), 0);
struct {
sll2_header sll;
iphdr ip;
udphdr udp;
} __PACKED packet;
ASSERT_EQ(hdr->caplen, sizeof(packet));
ASSERT_EQ(hdr->len, sizeof(packet));
memcpy(&packet, data, sizeof(packet));
EXPECT_EQ(ntohs(packet.sll.sll2_protocol), ETH_P_IP);
EXPECT_EQ(packet.sll.sll2_reserved_mbz, 0);
EXPECT_EQ(ntohl(packet.sll.sll2_if_index), static_cast<unsigned int>(GetLoopbackIndex()));
EXPECT_EQ(packet.sll.sll2_hatype, ARPHRD_ETHER);
EXPECT_EQ(packet.sll.sll2_pkttype, ctx->pkttype);
EXPECT_EQ(packet.sll.sll2_halen, ETH_ALEN);
// Packet was sent through the loopback interface which has the all zeroes
// address.
for (int i = 0; i < packet.sll.sll2_halen; ++i) {
EXPECT_EQ(packet.sll.sll2_addr[i], 0) << "sll2_addr byte mismatch @ idx = " << i;
}
// IHL hold the size of the header in 4 byte units.
EXPECT_EQ(packet.ip.ihl, sizeof(iphdr) / 4);
EXPECT_EQ(packet.ip.version, static_cast<u_int>(IPVERSION));
EXPECT_EQ(ntohs(packet.ip.tot_len), sizeof(iphdr) + sizeof(udphdr));
EXPECT_EQ(packet.ip.protocol, IPPROTO_UDP);
EXPECT_EQ(ntohl(packet.ip.daddr), INADDR_LOOPBACK);
EXPECT_EQ(ntohl(packet.ip.saddr), INADDR_LOOPBACK);
EXPECT_EQ(ntohs(packet.udp.source), ctx->src_port);
EXPECT_EQ(ntohs(packet.udp.dest), ctx->dst_port);
EXPECT_EQ(ntohs(packet.udp.len), sizeof(udphdr));
}
sockaddr_in udp_addr_;
fbl::unique_fd udp_;
};
TEST_F(LibpcapPacketTest, BlockingModes) {
constexpr int kTimeoutMs = 1000;
ASSERT_NO_FATAL_FAILURE(TestSetNonblockAndActivate(pcap_handle(), kTimeoutMs, ebuf()));
auto send_and_dispatch_checks = [&]() {
ASSERT_NO_FATAL_FAILURE(Send());
ASSERT_NO_FATAL_FAILURE(
PcapDispatch(PACKET_HOST, 1 /* max_packets */, 1 /* expected_packets */));
ASSERT_NO_FATAL_FAILURE(Send());
ASSERT_NO_FATAL_FAILURE(
PcapDispatch(PACKET_HOST, 2 /* max_packets */, 1 /* expected_packets */));
};
ASSERT_NO_FATAL_FAILURE(send_and_dispatch_checks());
// Block until packets are ready.
int res;
ASSERT_EQ(res = pcap_setnonblock(pcap_handle(), 0 /* nonblock */, ebuf()), 0)
<< pcap_statustostr(res) << "; ebuf: " << ebuf();
res = pcap_getnonblock(pcap_handle(), ebuf());
ASSERT_GE(res, 0) << pcap_statustostr(res) << "; ebuf: " << ebuf();
ASSERT_EQ(res, 0);
// Make sure that when no packets are ready, we block for at least the
// specified timeout.
std::chrono::time_point start = std::chrono::steady_clock::now();
ASSERT_NO_FATAL_FAILURE(PcapDispatch(PACKET_HOST, 1 /* max_packets */, 0 /* expected_packets */));
EXPECT_GE(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() -
start)
.count(),
kTimeoutMs);
ASSERT_NO_FATAL_FAILURE(send_and_dispatch_checks());
}
TEST_F(LibpcapPacketTest, Filter) {
int res;
ASSERT_EQ(res = pcap_setnonblock(pcap_handle(), 1 /* nonblock */, ebuf()), 0)
<< pcap_statustostr(res) << "; ebuf: " << ebuf();
ASSERT_EQ(res = pcap_activate(pcap_handle()), 0)
<< pcap_statustostr(res) << "; pcap error: " << pcap_geterr(pcap_handle());
constexpr uint16_t kFilteredDstPort = 1234;
constexpr char filter[] = "udp dst port 1234";
bpf_program bpf;
ASSERT_EQ(res = pcap_compile(pcap_handle(), &bpf, filter, 0 /* optimize */, PCAP_NETMASK_UNKNOWN),
0)
<< pcap_statustostr(res) << "; pcap error: " << pcap_geterr(pcap_handle());
res = pcap_setfilter(pcap_handle(), &bpf);
pcap_freecode(&bpf);
ASSERT_EQ(res, 0) << "pcap_setfilter" << pcap_statustostr(res)
<< "; pcap error: " << pcap_geterr(pcap_handle());
// Send a packet to some other port and expect not to receive the packet.
const uint16_t kOtherPort = kFilteredDstPort + 1;
ASSERT_NO_FATAL_FAILURE(Send(kOtherPort));
ASSERT_NO_FATAL_FAILURE(PcapDispatch(PACKET_HOST, 1 /* max_packets */, 0 /* expected_packets */,
kOtherPort /* expected_dst_port */));
// Send a packet to the filtered port and expect to receive the packet.
ASSERT_NO_FATAL_FAILURE(Send(kFilteredDstPort));
ASSERT_NO_FATAL_FAILURE(PcapDispatch(PACKET_HOST, 2 /* max_packets */, 1 /* expected_packets */,
kFilteredDstPort /* expected_dst_port */));
}
class LibpcapPacketDirectionTest
: public LibpcapPacketTest,
public ::testing::WithParamInterface<std::tuple<pcap_direction_t, uint8_t>> {};
TEST_P(LibpcapPacketDirectionTest, FilterTest) {
auto [direction, pkttype] = GetParam();
int res;
ASSERT_EQ(res = pcap_activate(pcap_handle()), 0)
<< pcap_statustostr(res) << "; pcap error: " << pcap_geterr(pcap_handle());
ASSERT_EQ(res = pcap_setdirection(pcap_handle(), direction), 0)
<< pcap_statustostr(res) << "; pcap error: " << pcap_geterr(pcap_handle());
// We only wrote one packet so we should only read one packet.
ASSERT_NO_FATAL_FAILURE(Send());
ASSERT_NO_FATAL_FAILURE(PcapDispatch(pkttype, 2 /* max_packets */, 1 /* expected_packets */));
}
INSTANTIATE_TEST_SUITE_P(LibpcapTests, LibpcapPacketDirectionTest,
Values(std::make_tuple(PCAP_D_INOUT, PACKET_HOST),
std::make_tuple(PCAP_D_IN, PACKET_HOST),
std::make_tuple(PCAP_D_OUT, PACKET_OUTGOING)));
} // namespace