blob: 45c1b2f8b2aea561383a1f10319c0a6d791eebd7 [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 "net_interfaces.h"
#include <fuchsia/net/interfaces/cpp/fidl.h>
#include "src/lib/testing/predicates/status.h"
#include "test_common.h"
namespace net::interfaces::test {
constexpr uint64_t kLoopbackID = 1;
constexpr uint64_t kEthernetID = 2;
constexpr uint64_t kWrongID = 0xffffffff;
namespace {
net::interfaces::Properties Verify(fuchsia::net::interfaces::Properties properties) {
auto verified = Properties::VerifyAndCreate(std::move(properties));
EXPECT_TRUE(verified.has_value());
return std::move(verified.value());
}
fuchsia::net::interfaces::Properties LoopbackProperties(
bool online, bool has_default_ipv4_route, bool has_default_ipv6_route,
std::vector<fuchsia::net::interfaces::Address> addresses) {
fuchsia::net::interfaces::Properties properties;
properties.set_id(kLoopbackID);
properties.set_name(kName);
properties.set_device_class(
fuchsia::net::interfaces::DeviceClass::WithLoopback(fuchsia::net::interfaces::Empty()));
SetMutableProperties(properties, online, has_default_ipv4_route, has_default_ipv6_route,
std::move(addresses));
return properties;
}
fuchsia::net::interfaces::Properties EthernetProperties(
bool online, bool has_default_ipv4_route, bool has_default_ipv6_route,
std::vector<fuchsia::net::interfaces::Address> addresses) {
fuchsia::net::interfaces::Properties properties;
properties.set_id(kEthernetID);
properties.set_name(kName);
properties.set_device_class(fuchsia::net::interfaces::DeviceClass::WithDevice(
fuchsia::hardware::network::DeviceClass::ETHERNET));
SetMutableProperties(properties, online, has_default_ipv4_route, has_default_ipv6_route,
std::move(addresses));
return properties;
}
fuchsia::net::interfaces::Properties FullProperties() {
return LoopbackProperties(true, true, true, Addresses(kIPv4Address1, kIPv6Address1));
}
void AddInterface(PropertiesMap& properties_map) {}
template <typename T, typename... Ts>
void AddInterface(PropertiesMap& properties_map, T interface, Ts... interfaces) {
auto result =
properties_map.Update(fuchsia::net::interfaces::Event::WithExisting(std::move(interface)));
EXPECT_TRUE(result.is_ok()) << "Update error: "
<< PropertiesMap::update_error_get_string(result.error());
AddInterface(properties_map, interfaces...);
}
template <typename... Ts>
PropertiesMap NewPropertiesMap(Ts... interfaces) {
PropertiesMap properties_map;
AddInterface(properties_map, std::move(interfaces...));
return properties_map;
}
} // namespace
TEST(Verify, Success) {
auto properties = FullProperties();
auto validated_properties = Verify(FullProperties());
EXPECT_EQ(validated_properties.id(), properties.id());
EXPECT_EQ(validated_properties.name(), properties.name());
EXPECT_EQ(validated_properties.online(), properties.online());
EXPECT_EQ(validated_properties.has_default_ipv4_route(), properties.has_default_ipv4_route());
EXPECT_EQ(validated_properties.has_default_ipv6_route(), properties.has_default_ipv6_route());
EXPECT_TRUE(fidl::Equals(validated_properties.device_class(), properties.device_class()));
EXPECT_TRUE(fidl::Equals(validated_properties.addresses(), properties.addresses()));
}
TEST(Verify, MissingID) {
fuchsia::net::interfaces::Properties properties = FullProperties();
properties.clear_id();
ASSERT_EQ(Properties::VerifyAndCreate(std::move(properties)), std::nullopt);
}
TEST(Verify, MissingAddresses) {
fuchsia::net::interfaces::Properties properties = FullProperties();
properties.clear_addresses();
ASSERT_EQ(Properties::VerifyAndCreate(std::move(properties)), std::nullopt);
}
TEST(Verify, InvalidAddress) {
fuchsia::net::interfaces::Properties properties = FullProperties();
// Add an address that is not initialized.
properties.mutable_addresses()->emplace_back();
ASSERT_EQ(Properties::VerifyAndCreate(std::move(properties)), std::nullopt);
}
TEST(Update, Success) {
Properties properties =
Verify(LoopbackProperties(false, false, false, Addresses(kIPv4Address1, kIPv6Address1)));
fuchsia::net::interfaces::Properties change;
change.set_id(kLoopbackID);
SetMutableProperties(change, true, true, true, Addresses(kIPv4Address2, kIPv6Address2));
ASSERT_TRUE(properties.Update(&change));
ASSERT_EQ(properties,
Verify(LoopbackProperties(true, true, true, Addresses(kIPv4Address2, kIPv6Address2))));
fuchsia::net::interfaces::Properties before_change;
SetMutableProperties(before_change, false, false, false, Addresses(kIPv4Address1, kIPv6Address1));
EXPECT_TRUE(fidl::Equals(change, before_change));
}
TEST(Update, MissingID) {
fuchsia::net::interfaces::Properties change;
change.set_online(true);
ASSERT_FALSE(Verify(LoopbackProperties(false, false, false, {})).Update(&change));
}
TEST(Update, MismatchedID) {
fuchsia::net::interfaces::Properties change;
change.set_id(kWrongID);
change.set_online(true);
ASSERT_FALSE(Verify(LoopbackProperties(false, false, false, {})).Update(&change));
}
TEST(Update, InvalidAddress) {
fuchsia::net::interfaces::Properties change;
change.set_id(kLoopbackID);
change.mutable_addresses()->emplace_back();
ASSERT_FALSE(Verify(LoopbackProperties(false, false, false, {})).Update(&change));
}
TEST(IsGloballyRoutable, Loopback) {
EXPECT_FALSE(Verify(LoopbackProperties(true, true, true, Addresses(kIPv4Address1, kIPv6Address1)))
.IsGloballyRoutable());
}
TEST(IsGloballyRoutable, Offline) {
EXPECT_FALSE(
Verify(EthernetProperties(false, true, true, Addresses(kIPv4Address1, kIPv6Address1)))
.IsGloballyRoutable());
}
TEST(IsGloballyRoutable, NoDefaultRoutes) {
EXPECT_FALSE(
Verify(EthernetProperties(true, false, false, Addresses(kIPv4Address1, kIPv6Address1)))
.IsGloballyRoutable());
}
TEST(IsGloballyRoutable, NoAddresses) {
EXPECT_FALSE(Verify(EthernetProperties(true, true, true, {})).IsGloballyRoutable());
}
TEST(IsGloballyRoutable, IPv6LinkLocal) {
EXPECT_FALSE(Verify(EthernetProperties(true, false, true, Addresses(kIPv6LLAddress)))
.IsGloballyRoutable());
}
TEST(IsGloballyRoutable, IPv4AddressAndDefaultRoute) {
EXPECT_TRUE(
Verify(EthernetProperties(true, true, false, Addresses(kIPv4Address1))).IsGloballyRoutable());
}
TEST(IsGloballyRoutable, IPv6GlobalAddressAndDefaultRoute) {
EXPECT_TRUE(
Verify(EthernetProperties(true, false, true, Addresses(kIPv6Address1))).IsGloballyRoutable());
}
TEST(PropertiesMap, Success) {
PropertiesMap properties_map;
std::unordered_map<uint64_t, Properties> want;
auto result =
properties_map.Update(fuchsia::net::interfaces::Event::WithExisting(FullProperties()));
ASSERT_TRUE(result.is_ok()) << "Update error: "
<< PropertiesMap::update_error_get_string(result.error());
want.emplace(kLoopbackID, Verify(FullProperties()));
ASSERT_EQ(properties_map.properties_map(), want);
fuchsia::net::interfaces::Properties want_properties,
ethernet_properties =
EthernetProperties(false, false, false, Addresses(kIPv4Address1, kIPv6Address1));
ASSERT_OK(ethernet_properties.Clone(&want_properties));
result = properties_map.Update(
fuchsia::net::interfaces::Event::WithAdded(std::move(ethernet_properties)));
ASSERT_TRUE(result.is_ok()) << "Update error: "
<< PropertiesMap::update_error_get_string(result.error());
want.emplace(kEthernetID, Verify(std::move(want_properties)));
ASSERT_EQ(properties_map.properties_map(), want);
fuchsia::net::interfaces::Properties change;
change.set_id(kEthernetID);
SetMutableProperties(change, true, true, true, Addresses(kIPv4Address2, kIPv6Address2));
result = properties_map.Update(fuchsia::net::interfaces::Event::WithChanged(std::move(change)));
ASSERT_TRUE(result.is_ok()) << "Update error: "
<< PropertiesMap::update_error_get_string(result.error());
auto it = want.find(kEthernetID);
ASSERT_NE(it, want.end());
it->second =
Verify(EthernetProperties(true, true, true, Addresses(kIPv4Address2, kIPv6Address2)));
ASSERT_EQ(properties_map.properties_map(), want);
uint64_t id = kLoopbackID;
result = properties_map.Update(fuchsia::net::interfaces::Event::WithRemoved(std::move(id)));
ASSERT_TRUE(result.is_ok()) << "Update error: "
<< PropertiesMap::update_error_get_string(result.error());
want.erase(kLoopbackID);
ASSERT_EQ(properties_map.properties_map(), want);
}
TEST(PropertiesMap, InvalidExisting) {
PropertiesMap properties_map;
auto properties = FullProperties();
properties.clear_id();
auto result =
properties_map.Update(fuchsia::net::interfaces::Event::WithExisting(std::move(properties)));
ASSERT_TRUE(result.is_error());
ASSERT_EQ(result.error(), PropertiesMap::UpdateErrorVariant(PropertiesMap::UpdateError(
PropertiesMap::UpdateError::kInvalidExisting)));
}
TEST(PropertiesMap, InvalidAdded) {
auto properties = FullProperties();
properties.clear_id();
auto result =
PropertiesMap().Update(fuchsia::net::interfaces::Event::WithAdded(std::move(properties)));
ASSERT_TRUE(result.is_error());
ASSERT_EQ(result.error(), PropertiesMap::UpdateErrorVariant(PropertiesMap::UpdateError(
PropertiesMap::UpdateError::kInvalidAdded)));
}
TEST(PropertiesMap, MissingId) {
fuchsia::net::interfaces::Properties change_missing_id;
change_missing_id.set_online(true);
auto result =
NewPropertiesMap(LoopbackProperties(false, false, false, {}))
.Update(fuchsia::net::interfaces::Event::WithChanged(std::move(change_missing_id)));
ASSERT_TRUE(result.is_error());
ASSERT_EQ(result.error(), PropertiesMap::UpdateErrorVariant(PropertiesMap::UpdateError(
PropertiesMap::UpdateError::kMissingId)));
}
TEST(PropertiesMap, InvalidChanged) {
fuchsia::net::interfaces::Properties change_invalid_address;
change_invalid_address.set_id(kLoopbackID);
change_invalid_address.mutable_addresses()->emplace_back();
auto result =
NewPropertiesMap(LoopbackProperties(false, false, false, {}))
.Update(fuchsia::net::interfaces::Event::WithChanged(std::move(change_invalid_address)));
ASSERT_TRUE(result.is_error());
ASSERT_EQ(result.error(), PropertiesMap::UpdateErrorVariant(PropertiesMap::UpdateError(
PropertiesMap::UpdateError::kInvalidChanged)));
}
TEST(PropertiesMap, InvalidEvent) {
auto result = PropertiesMap().Update(fuchsia::net::interfaces::Event());
ASSERT_TRUE(result.is_error());
ASSERT_EQ(result.error(), PropertiesMap::UpdateErrorVariant(PropertiesMap::UpdateError(
PropertiesMap::UpdateError::kInvalidEvent)));
}
TEST(PropertiesMap, DuplicateExisting) {
auto result = NewPropertiesMap(FullProperties())
.Update(fuchsia::net::interfaces::Event::WithExisting(FullProperties()));
ASSERT_TRUE(result.is_error());
ASSERT_EQ(result.error(),
PropertiesMap::UpdateErrorVariant(
PropertiesMap::UpdateErrorWithId<
PropertiesMap::UpdateErrorWithIdKind::kDuplicateExisting>{.id = kLoopbackID}));
}
TEST(PropertiesMap, DuplicateAdded) {
auto result = NewPropertiesMap(FullProperties())
.Update(fuchsia::net::interfaces::Event::WithAdded(FullProperties()));
ASSERT_TRUE(result.is_error());
ASSERT_EQ(
result.error(),
PropertiesMap::UpdateErrorVariant(
PropertiesMap::UpdateErrorWithId<PropertiesMap::UpdateErrorWithIdKind::kDuplicateAdded>{
.id = kLoopbackID}));
}
TEST(PropertiesMap, UnknownChanged) {
fuchsia::net::interfaces::Properties change_unknown;
change_unknown.set_id(kWrongID);
change_unknown.set_online(true);
auto result = PropertiesMap().Update(
fuchsia::net::interfaces::Event::WithChanged(std::move(change_unknown)));
ASSERT_TRUE(result.is_error());
ASSERT_EQ(
result.error(),
PropertiesMap::UpdateErrorVariant(
PropertiesMap::UpdateErrorWithId<PropertiesMap::UpdateErrorWithIdKind::kUnknownChanged>{
.id = kWrongID}));
}
TEST(PropertiesMap, UnknownRemoved) {
uint64_t id = kWrongID;
auto result = PropertiesMap().Update(fuchsia::net::interfaces::Event::WithRemoved(std::move(id)));
ASSERT_TRUE(result.is_error());
ASSERT_EQ(
result.error(),
PropertiesMap::UpdateErrorVariant(
PropertiesMap::UpdateErrorWithId<PropertiesMap::UpdateErrorWithIdKind::kUnknownRemoved>{
.id = kWrongID}));
}
class ReachabilityWatcherTest : public gtest::TestLoopFixture {
public:
ReachabilityWatcherTest() : ReachabilityWatcherTest(fuchsia::net::interfaces::WatcherPtr()) {}
void AssertReachable(bool want) {
ASSERT_TRUE(reachable_.has_value());
ASSERT_TRUE(reachable_.value().is_ok());
ASSERT_EQ(reachable_.value().value(), want);
reachable_.reset();
}
protected:
std::optional<fpromise::result<bool, ReachabilityWatcher::ErrorVariant>> reachable_;
FakeWatcherImpl fake_watcher_impl_;
std::unique_ptr<fidl::Binding<fuchsia::net::interfaces::Watcher>> watcher_binding_;
ReachabilityWatcher reachability_watcher_;
private:
ReachabilityWatcherTest(fuchsia::net::interfaces::WatcherPtr watcher)
: watcher_binding_(std::make_unique<fidl::Binding<fuchsia::net::interfaces::Watcher>>(
&fake_watcher_impl_, watcher.NewRequest())),
reachability_watcher_(std::move(watcher),
[this](auto reachable) { reachable_ = reachable; }) {}
};
TEST_F(ReachabilityWatcherTest, Basic) {
constexpr uint64_t kID1 = 1;
constexpr uint64_t kID2 = 2;
RunLoopUntilIdle();
fake_watcher_impl_.SendExistingEvent(kID1, false);
RunLoopUntilIdle();
ASSERT_NO_FATAL_FAILURE(AssertReachable(false));
fake_watcher_impl_.SendAddedEvent(kID2, true);
RunLoopUntilIdle();
ASSERT_NO_FATAL_FAILURE(AssertReachable(true));
fake_watcher_impl_.SendChangedEvent(kID1, true);
RunLoopUntilIdle();
ASSERT_FALSE(reachable_.has_value())
<< "got: "
<< (reachable_.value().is_ok()
? std::to_string(reachable_.value().value())
: ReachabilityWatcher::error_get_string(reachable_.value().take_error()));
fake_watcher_impl_.SendChangedEvent(kID2, false);
RunLoopUntilIdle();
ASSERT_FALSE(reachable_.has_value())
<< "got: "
<< (reachable_.value().is_ok()
? std::to_string(reachable_.value().value())
: ReachabilityWatcher::error_get_string(reachable_.value().take_error()));
fake_watcher_impl_.SendRemovedEvent(kID1);
RunLoopUntilIdle();
ASSERT_NO_FATAL_FAILURE(AssertReachable(false));
}
TEST_F(ReachabilityWatcherTest, MalformedEvent) {
RunLoopUntilIdle();
fake_watcher_impl_.SendExistingEvent(kEthernetID, true);
RunLoopUntilIdle();
ASSERT_NO_FATAL_FAILURE(AssertReachable(true));
fake_watcher_impl_.SendEvent(
fuchsia::net::interfaces::Event::WithExisting(fuchsia::net::interfaces::Properties()));
RunLoopUntilIdle();
ASSERT_TRUE(reachable_.has_value());
ASSERT_TRUE(reachable_.value().is_error());
ASSERT_EQ(reachable_.value().take_error(),
ReachabilityWatcher::ErrorVariant(PropertiesMap::UpdateErrorVariant(
PropertiesMap::UpdateError(PropertiesMap::UpdateError::kInvalidExisting))));
}
TEST_F(ReachabilityWatcherTest, ChannelClose) {
RunLoopUntilIdle();
fake_watcher_impl_.SendExistingEvent(kEthernetID, true);
RunLoopUntilIdle();
ASSERT_NO_FATAL_FAILURE(AssertReachable(true));
watcher_binding_->Close(ZX_OK);
RunLoopUntilIdle();
ASSERT_TRUE(reachable_.has_value());
ASSERT_TRUE(reachable_.value().is_error());
ASSERT_EQ(reachable_.value().take_error(),
ReachabilityWatcher::ErrorVariant(ReachabilityWatcher::Error::kChannelClosed));
}
} // namespace net::interfaces::test