blob: 6f5fbaadb81242f3fa2bdd2b063d9f020a204058 [file] [log] [blame]
// Copyright 2019 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 "filter_test.h"
#include <algorithm>
namespace netdump::test {
// Canonical packet data.
static constexpr uint16_t frame_length = 2000; // bytes
static constexpr EthFilter::MacAddress src_mac = {0xde, 0xad, 0xbe, 0xef, 0xd0, 0x0d};
static constexpr EthFilter::MacAddress dst_mac = {0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef};
static uint16_t ip_pkt_length = htons(1842); // bytes
static constexpr uint8_t protocol = 0xab;
static constexpr uint32_t ip4addr_src = 0xc0a80a04;
static constexpr uint32_t ip4addr_dst = 0xfffefdfc;
static constexpr IpFilter::IPv6Address ip6addr_src{0x20, 0x01, 0x48, 0x60, 0x48, 0x60, 0, 0,
0, 0, 0, 0, 0, 0, 0x88, 0x88};
static constexpr IpFilter::IPv6Address ip6addr_dst{0x32, 0x11, 0xAB, 0xCD, 0x12, 0xFF, 0, 0,
0, 0, 0, 0, 0, 0, 0x12, 0x68};
static uint16_t src_port = htons(6587);
static uint16_t dst_port = htons(1234);
// Test packet storage.
static struct ethhdr test_frame;
static struct iphdr test_ipv4;
static struct ip6_hdr test_ipv6;
static struct tcphdr test_tcp;
static struct udphdr test_udp;
// Setup functions.
static Packet SetupEth(uint16_t ethtype) {
Packet packet;
packet.frame_length = frame_length;
test_frame.h_proto = ethtype;
std::copy(src_mac.begin(), src_mac.end(), test_frame.h_source);
std::copy(dst_mac.begin(), dst_mac.end(), test_frame.h_dest);
packet.frame = &test_frame;
return packet;
}
static void SetupIPv4(Packet* packet) {
test_ipv4.version = 4;
test_ipv4.tot_len = ip_pkt_length;
test_ipv4.protocol = protocol;
test_ipv4.saddr = ip4addr_src;
test_ipv4.daddr = ip4addr_dst;
packet->ip = &test_ipv4;
}
static void SetupIPv6(Packet* packet) {
reinterpret_cast<struct iphdr*>(&test_ipv6)->version = 6; // Version is set with iphdr pointer.
test_ipv6.ip6_plen = ip_pkt_length;
test_ipv6.ip6_nxt = protocol;
std::copy(ip6addr_src.begin(), ip6addr_src.end(), test_ipv6.ip6_src.s6_addr);
std::copy(ip6addr_dst.begin(), ip6addr_dst.end(), test_ipv6.ip6_dst.s6_addr);
packet->ipv6 = &test_ipv6;
}
static void SetupTCP(Packet* packet) {
test_tcp.source = src_port;
test_tcp.dest = dst_port;
test_ipv4.protocol = IPPROTO_TCP;
test_ipv6.ip6_nxt = IPPROTO_TCP;
packet->tcp = &test_tcp;
}
static void SetupUDP(Packet* packet) {
test_udp.uh_sport = src_port;
test_udp.uh_dport = dst_port;
test_ipv4.protocol = IPPROTO_UDP;
test_ipv6.ip6_nxt = IPPROTO_UDP;
packet->tcp = &test_tcp;
}
// Implementation of the test battery.
void FrameLengthTest(FrameLengthFn filter_fn) {
Packet packet = SetupEth(htons(ETH_P_IP));
EXPECT_FALSE(filter_fn(htons(1842), LengthComparator::LEQ)->match(packet));
EXPECT_TRUE(filter_fn(htons(1842), LengthComparator::GEQ)->match(packet));
EXPECT_TRUE(filter_fn(htons(2000), LengthComparator::LEQ)->match(packet));
EXPECT_TRUE(filter_fn(htons(2000), LengthComparator::GEQ)->match(packet));
EXPECT_TRUE(filter_fn(htons(5555), LengthComparator::LEQ)->match(packet));
EXPECT_FALSE(filter_fn(htons(5555), LengthComparator::GEQ)->match(packet));
}
void EthtypeTest(EthtypeFn filter_fn) {
Packet null_packet = SetupEth(htons(0x1430));
null_packet.frame = nullptr;
EXPECT_FALSE(filter_fn(htons(0x1430))->match(null_packet));
EXPECT_TRUE(filter_fn(htons(0x1430))->match(SetupEth(htons(0x1430))));
EXPECT_FALSE(filter_fn(htons(0x3014))->match(SetupEth(htons(0x1430))));
EXPECT_FALSE(filter_fn(htons(0xCDAB))->match(SetupEth(htons(0x1430))));
}
void MacTest(MacFn filter_fn) {
Packet packet = SetupEth(htons(0x1430));
FilterPtr matched_src = filter_fn(src_mac, SRC_ADDR);
FilterPtr matched_dst = filter_fn(dst_mac, DST_ADDR);
EthFilter::MacAddress unmatched_mac1{0x0d, 0xd0, 0xef, 0xbe, 0xad, 0xde};
EthFilter::MacAddress unmatched_mac2{0xef, 0xdc, 0xab, 0xef, 0xdc, 0xab};
FilterPtr unmatched_src1 = filter_fn(unmatched_mac1, SRC_ADDR);
FilterPtr unmatched_src2 = filter_fn(unmatched_mac2, SRC_ADDR);
FilterPtr unmatched_dst1 = filter_fn(unmatched_mac1, DST_ADDR);
FilterPtr unmatched_dst2 = filter_fn(unmatched_mac2, DST_ADDR);
EXPECT_TRUE(matched_src->match(packet));
EXPECT_TRUE(matched_dst->match(packet));
EXPECT_FALSE(unmatched_src1->match(packet));
EXPECT_FALSE(unmatched_src2->match(packet));
EXPECT_FALSE(unmatched_dst1->match(packet));
EXPECT_FALSE(unmatched_dst2->match(packet));
}
void VersionTest(VersionFn filter_fn) {
FilterPtr ip4filter_fn = filter_fn(4);
FilterPtr ip6filter_fn = filter_fn(6);
Packet packet = SetupEth(htons(ETH_P_IP));
packet.ip = nullptr;
EXPECT_FALSE(ip4filter_fn->match(packet));
EXPECT_FALSE(ip6filter_fn->match(packet));
packet = SetupEth(htons(ETH_P_IP));
SetupIPv4(&packet);
EXPECT_TRUE(ip4filter_fn->match(packet));
EXPECT_FALSE(ip6filter_fn->match(packet));
packet = SetupEth(htons(ETH_P_IPV6));
SetupIPv6(&packet);
EXPECT_FALSE(ip4filter_fn->match(packet));
EXPECT_TRUE(ip6filter_fn->match(packet));
}
void IPLengthTest(IPLengthFn filter_fn) {
Packet packet = SetupEth(htons(ETH_P_IP));
SetupIPv4(&packet);
EXPECT_FALSE(filter_fn(4, htons(40), LengthComparator::LEQ)->match(packet));
EXPECT_TRUE(filter_fn(4, htons(40), LengthComparator::GEQ)->match(packet));
EXPECT_TRUE(filter_fn(4, htons(1842), LengthComparator::LEQ)->match(packet));
EXPECT_TRUE(filter_fn(4, htons(1842), LengthComparator::GEQ)->match(packet));
EXPECT_TRUE(filter_fn(4, htons(4444), LengthComparator::LEQ)->match(packet));
EXPECT_FALSE(filter_fn(4, htons(4444), LengthComparator::GEQ)->match(packet));
packet = SetupEth(htons(ETH_P_IPV6));
SetupIPv6(&packet);
EXPECT_FALSE(filter_fn(6, htons(60), LengthComparator::LEQ)->match(packet));
EXPECT_TRUE(filter_fn(6, htons(60), LengthComparator::GEQ)->match(packet));
EXPECT_TRUE(filter_fn(6, htons(1842), LengthComparator::LEQ)->match(packet));
EXPECT_TRUE(filter_fn(6, htons(1842), LengthComparator::GEQ)->match(packet));
EXPECT_TRUE(filter_fn(6, htons(6666), LengthComparator::LEQ)->match(packet));
EXPECT_FALSE(filter_fn(6, htons(6666), LengthComparator::GEQ)->match(packet));
}
void ProtocolTest(ProtocolFn filter_fn) {
FilterPtr matched_ip4 = filter_fn(4, 0xab);
FilterPtr matched_ip6 = filter_fn(6, 0xab);
FilterPtr unmatched_ip4 = filter_fn(4, 0xcd);
FilterPtr unmatched_ip6 = filter_fn(6, 0xef);
Packet packet = SetupEth(htons(ETH_P_IP));
SetupIPv4(&packet);
EXPECT_TRUE(matched_ip4->match(packet));
EXPECT_FALSE(unmatched_ip4->match(packet));
packet = SetupEth(htons(ETH_P_IPV6));
SetupIPv6(&packet);
EXPECT_TRUE(matched_ip6->match(packet));
EXPECT_FALSE(unmatched_ip6->match(packet));
}
void IPv4AddrTest(IPv4AddrFn filter_fn) {
Packet packet = SetupEth(htons(ETH_P_IP));
SetupIPv4(&packet);
FilterPtr matched_src = filter_fn(0xc0a80a04, SRC_ADDR);
FilterPtr matched_dst = filter_fn(0xfffefdfc, DST_ADDR);
FilterPtr either_t = filter_fn(0xc0a80a04, EITHER_ADDR);
FilterPtr either_f = filter_fn(0xffffffff, EITHER_ADDR);
FilterPtr unmatched_src = filter_fn(0x040aa8c0, SRC_ADDR);
FilterPtr unmatched_dst = filter_fn(0xfcfdfeff, DST_ADDR);
EXPECT_TRUE(matched_src->match(packet));
EXPECT_TRUE(matched_dst->match(packet));
EXPECT_TRUE(either_t->match(packet));
EXPECT_FALSE(either_f->match(packet));
EXPECT_FALSE(unmatched_src->match(packet));
EXPECT_FALSE(unmatched_dst->match(packet));
}
void IPv6AddrTest(IPv6AddrFn filter_fn) {
IpFilter::IPv6Address ip6addr_other;
ip6addr_other.fill(123);
Packet packet = SetupEth(htons(ETH_P_IPV6));
SetupIPv6(&packet);
IpFilter::IPv6Address ip6addr_src_copy(ip6addr_src); // Copy construction.
IpFilter::IPv6Address ip6addr_dst_copy(ip6addr_dst);
FilterPtr matched_src = filter_fn(ip6addr_src_copy, SRC_ADDR);
FilterPtr matched_dst = filter_fn(ip6addr_dst_copy, DST_ADDR);
FilterPtr wrong_type_src = filter_fn(ip6addr_src, DST_ADDR);
FilterPtr wrong_type_dst = filter_fn(ip6addr_dst, SRC_ADDR);
FilterPtr either_t = filter_fn(ip6addr_dst, EITHER_ADDR);
FilterPtr either_f = filter_fn(ip6addr_other, EITHER_ADDR);
FilterPtr unmatched_src = filter_fn(ip6addr_other, SRC_ADDR);
FilterPtr unmatched_dst = filter_fn(ip6addr_other, DST_ADDR);
EXPECT_TRUE(matched_src->match(packet));
EXPECT_TRUE(matched_dst->match(packet));
EXPECT_FALSE(wrong_type_src->match(packet));
EXPECT_FALSE(wrong_type_dst->match(packet));
EXPECT_TRUE(either_t->match(packet));
EXPECT_FALSE(either_f->match(packet));
EXPECT_FALSE(unmatched_src->match(packet));
EXPECT_FALSE(unmatched_dst->match(packet));
ip6addr_src_copy.fill(0);
ip6addr_dst_copy.fill(0);
// If IpFilter did not make a copy of the given IP6 address on construction then
// the following will fail.
EXPECT_TRUE(matched_src->match(packet));
EXPECT_TRUE(matched_dst->match(packet));
}
static void PortsTest(uint8_t version, PortFn filter_fn) {
Packet packet;
switch (version) {
case 4:
packet = SetupEth(htons(ETH_P_IP));
SetupIPv4(&packet);
break;
case 6:
packet = SetupEth(htons(ETH_P_IPV6));
SetupIPv6(&packet);
break;
default:
ASSERT_TRUE(version == 4 || version == 6); // IP version must be supported.
}
SetupTCP(&packet);
auto src1 = filter_fn(std::vector<PortRange>{}, SRC_PORT);
auto dst1 = filter_fn(std::vector<PortRange>{}, DST_PORT);
auto either1 = filter_fn(std::vector<PortRange>{}, EITHER_PORT);
EXPECT_FALSE(src1->match(packet));
EXPECT_FALSE(dst1->match(packet));
EXPECT_FALSE(either1->match(packet));
auto src2 = filter_fn(std::vector<PortRange>{PortRange(htons(10000), htons(20000))}, SRC_PORT);
auto dst2 = filter_fn(std::vector<PortRange>{PortRange(htons(1), htons(1000))}, DST_PORT);
auto either2 =
filter_fn(std::vector<PortRange>{PortRange(htons(8888), htons(8888))}, EITHER_PORT);
EXPECT_FALSE(src2->match(packet));
EXPECT_FALSE(dst2->match(packet));
EXPECT_FALSE(either2->match(packet));
auto src3 = filter_fn(std::vector<PortRange>{PortRange(htons(10000), htons(20000)),
PortRange(htons(6587), htons(6587))},
SRC_PORT);
auto dst3 = filter_fn(
std::vector<PortRange>{PortRange(htons(1), htons(1000)), PortRange(htons(1234), htons(1234))},
DST_PORT);
auto either3 = filter_fn(std::vector<PortRange>{PortRange(htons(8888), htons(8888)),
PortRange(htons(1000), htons(2000))},
EITHER_PORT);
EXPECT_TRUE(src3->match(packet));
EXPECT_TRUE(dst3->match(packet));
EXPECT_TRUE(either3->match(packet));
SetupUDP(&packet);
EXPECT_TRUE(src3->match(packet));
EXPECT_TRUE(dst3->match(packet));
EXPECT_TRUE(either3->match(packet));
packet.transport = nullptr;
EXPECT_FALSE(src3->match(packet));
EXPECT_FALSE(dst3->match(packet));
EXPECT_FALSE(either3->match(packet));
}
void IPv4PortsTest(PortFn filter_fn) { PortsTest(4, std::move(filter_fn)); }
void IPv6PortsTest(PortFn filter_fn) { PortsTest(6, std::move(filter_fn)); }
void UnsupportedIpVersionAssertTest(VersionFn version_fn, IPLengthFn length_fn,
ProtocolFn protocol_fn) {
if (ZX_DEBUG_ASSERT_IMPLEMENTED) {
ASSERT_DEATH([&version_fn]() { version_fn(3); });
ASSERT_DEATH([&length_fn]() { length_fn(5, 16, LengthComparator::LEQ); });
ASSERT_DEATH([&protocol_fn]() { protocol_fn(7, IPPROTO_TCP); });
}
}
#define NETDUMP_TRUE FilterPtr(new EthFilter(htons(0x1430)))
#define NETDUMP_FALSE FilterPtr(new EthFilter(htons(0x3014)))
void CompositionTest(UnaryFn neg_fn, BinaryFn conj_fn, BinaryFn disj_fn) {
Packet packet = SetupEth(htons(0x1430));
auto neg_t = neg_fn(NETDUMP_TRUE);
auto neg_f = neg_fn(NETDUMP_FALSE);
auto conj_tt = conj_fn(NETDUMP_TRUE, NETDUMP_TRUE);
auto conj_tf = conj_fn(NETDUMP_TRUE, NETDUMP_FALSE);
auto conj_ft = conj_fn(NETDUMP_FALSE, NETDUMP_TRUE);
auto conj_ff = conj_fn(NETDUMP_FALSE, NETDUMP_FALSE);
auto disj_tt = disj_fn(NETDUMP_TRUE, NETDUMP_TRUE);
auto disj_tf = disj_fn(NETDUMP_TRUE, NETDUMP_FALSE);
auto disj_ft = disj_fn(NETDUMP_FALSE, NETDUMP_TRUE);
auto disj_ff = disj_fn(NETDUMP_FALSE, NETDUMP_FALSE);
EXPECT_TRUE(NETDUMP_TRUE->match(packet));
EXPECT_FALSE(NETDUMP_FALSE->match(packet));
EXPECT_FALSE(neg_t->match(packet));
EXPECT_TRUE(neg_f->match(packet));
EXPECT_TRUE(conj_tt->match(packet));
EXPECT_FALSE(conj_tf->match(packet));
EXPECT_FALSE(conj_ft->match(packet));
EXPECT_FALSE(conj_ff->match(packet));
EXPECT_TRUE(disj_tt->match(packet));
EXPECT_TRUE(disj_tf->match(packet));
EXPECT_TRUE(disj_ft->match(packet));
EXPECT_FALSE(disj_ff->match(packet));
}
#undef NETDUMP_TRUE
#undef NETDUMP_FALSE
// Instantiation of the tests for filter tree node constructors.
// Using templates, the right constructors are picked automatically.
// Any callable class is automatically convertible to `std::function` as long as a matching overload
// of `operator()` is present.
template <class Flt>
class CallConstructor;
template <>
class CallConstructor<IpFilter> {
public:
// IpFilter constructors need some help with integer conversions, so forward them explicitly.
inline FilterPtr operator()(uint8_t version) { return FilterPtr(new IpFilter(version)); }
inline FilterPtr operator()(uint8_t version, uint8_t protocol) {
return FilterPtr(new IpFilter(version, protocol));
}
inline FilterPtr operator()(uint8_t version, uint16_t length, LengthComparator comparator) {
return FilterPtr(new IpFilter(version, length, comparator));
}
inline FilterPtr operator()(uint32_t ipv4_addr, AddressFieldType type) {
return FilterPtr(new IpFilter(ipv4_addr, type));
}
inline FilterPtr operator()(const IpFilter::IPv6Address& ipv6_addr, AddressFieldType type) {
return FilterPtr(new IpFilter(ipv6_addr, type));
}
};
// This template handles all the rest.
template <class Flt>
class CallConstructor {
public:
template <typename... Args>
inline FilterPtr operator()(Args&&... args) {
return FilterPtr(new Flt(std::forward<Args>(args)...));
}
};
#define NETDUMP_TEST(test, flt) \
TEST(NetdumpFilterTest, test) { test(CallConstructor<flt>()); }
NETDUMP_TEST(FrameLengthTest, FrameLengthFilter)
NETDUMP_TEST(EthtypeTest, EthFilter)
NETDUMP_TEST(MacTest, EthFilter)
NETDUMP_TEST(VersionTest, IpFilter)
NETDUMP_TEST(IPLengthTest, IpFilter)
NETDUMP_TEST(ProtocolTest, IpFilter)
NETDUMP_TEST(IPv4AddrTest, IpFilter)
NETDUMP_TEST(IPv6AddrTest, IpFilter)
NETDUMP_TEST(IPv4PortsTest, PortFilter)
NETDUMP_TEST(IPv6PortsTest, PortFilter)
#undef NETDUMP_TEST
TEST(NetdumpFilterTest, UnsupportedIpVersionAssertTest) {
UnsupportedIpVersionAssertTest(CallConstructor<IpFilter>(), CallConstructor<IpFilter>(),
CallConstructor<IpFilter>());
}
TEST(NetdumpFilterTest, CompositionTest) {
CompositionTest(CallConstructor<NegFilter>(), CallConstructor<ConjFilter>(),
CallConstructor<DisjFilter>());
}
// Tests for the `populate` method in the `Packet` class that finds the pointers to the headers.
static constexpr uint16_t BUFFER_LENGTH = 256;
static constexpr uint8_t IPV4_IHL = 12; // 32-bit words
inline struct ethhdr* SetupPopulateBuffer(uint16_t ethtype, uint8_t* buffer) {
auto frame = reinterpret_cast<struct ethhdr*>(buffer);
frame->h_proto = ethtype;
return frame;
}
TEST(NetdumpFilterTest, PopulatePacketEthernetTest) {
uint8_t buffer[BUFFER_LENGTH];
Packet packet;
auto frame = SetupPopulateBuffer(0, buffer);
// Unrecognized ethtype.
packet.populate(buffer, BUFFER_LENGTH);
EXPECT_EQ(BUFFER_LENGTH, packet.frame_length);
EXPECT_EQ(frame, packet.frame);
EXPECT_NULL(packet.ip);
EXPECT_NULL(packet.transport);
// Incomplete Ethernet headers.
SetupPopulateBuffer(ntohs(ETH_P_IP), buffer);
packet.populate(buffer, ETH_HLEN - 1);
EXPECT_EQ(ETH_HLEN - 1, packet.frame_length);
EXPECT_NULL(packet.frame);
EXPECT_NULL(packet.ip);
EXPECT_NULL(packet.transport);
// Incomplete L3 headers.
packet.populate(buffer, ETH_HLEN + 1);
EXPECT_EQ(ETH_HLEN + 1, packet.frame_length);
EXPECT_EQ(frame, packet.frame);
EXPECT_NULL(packet.ip);
EXPECT_NULL(packet.transport);
}
void PopulatePacketIPTest(size_t iphdr_len, uint8_t* transport_protocol, uint8_t* buffer) {
Packet packet;
auto frame = reinterpret_cast<struct ethhdr*>(buffer);
auto ip = reinterpret_cast<struct iphdr*>(buffer + ETH_HLEN);
void* transport = buffer + ETH_HLEN + iphdr_len;
// Unrecognized transport protocol.
*transport_protocol = 0;
packet.populate(buffer, BUFFER_LENGTH);
EXPECT_EQ(BUFFER_LENGTH, packet.frame_length);
EXPECT_EQ(frame, packet.frame);
EXPECT_EQ(ip, packet.ip);
EXPECT_NULL(packet.transport);
// UDP headers.
*transport_protocol = IPPROTO_UDP;
packet.populate(buffer, static_cast<uint16_t>(ETH_HLEN + iphdr_len + sizeof(struct udphdr)));
EXPECT_EQ(ETH_HLEN + iphdr_len + sizeof(struct udphdr), packet.frame_length);
EXPECT_EQ(frame, packet.frame);
EXPECT_EQ(ip, packet.ip);
EXPECT_EQ(transport, packet.transport);
// Incomplete UDP headers.
packet.populate(buffer, static_cast<uint16_t>(ETH_HLEN + iphdr_len + 1));
EXPECT_EQ(ETH_HLEN + iphdr_len + 1, packet.frame_length);
EXPECT_EQ(frame, packet.frame);
EXPECT_EQ(ip, packet.ip);
EXPECT_NULL(packet.transport);
// TCP headers.
*transport_protocol = IPPROTO_TCP;
packet.populate(buffer, BUFFER_LENGTH);
EXPECT_EQ(BUFFER_LENGTH, packet.frame_length);
EXPECT_EQ(frame, packet.frame);
EXPECT_EQ(ip, packet.ip);
EXPECT_EQ(transport, packet.transport);
// Incomplete TCP headers, length sufficient for UDP but not TCP.
packet.populate(buffer, static_cast<uint16_t>(ETH_HLEN + iphdr_len + sizeof(struct udphdr)));
EXPECT_EQ(ETH_HLEN + iphdr_len + sizeof(struct udphdr), packet.frame_length);
EXPECT_EQ(frame, packet.frame);
EXPECT_EQ(ip, packet.ip);
EXPECT_NULL(packet.transport);
}
TEST(NetdumpFilterTest, PopulatePacketIPv4Test) {
uint8_t buffer[BUFFER_LENGTH];
SetupPopulateBuffer(ntohs(ETH_P_IP), buffer);
auto ip = reinterpret_cast<struct iphdr*>(buffer + ETH_HLEN);
ip->ihl = IPV4_IHL;
size_t iphdr_len = IPV4_IHL << 2;
PopulatePacketIPTest(iphdr_len, &ip->protocol, buffer);
}
TEST(NetdumpFilterTest, PopulatePacketIPv6Test) {
uint8_t buffer[BUFFER_LENGTH];
SetupPopulateBuffer(ntohs(ETH_P_IPV6), buffer);
auto ipv6 = reinterpret_cast<struct ip6_hdr*>(buffer + ETH_HLEN);
PopulatePacketIPTest(sizeof(struct ip6_hdr), &ipv6->ip6_nxt, buffer);
}
} // namespace netdump::test