blob: bebf9ab5062cd00648018eb703c8a162751e4b99 [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 <netinet/in.h>
#include <string>
#include <unordered_map>
#include <variant>
#include "src/lib/fxl/strings/string_printf.h"
namespace net::interfaces {
namespace {
bool VerifyCompleteProperties(const fuchsia::net::interfaces::Properties& properties) {
if (!(properties.has_id() && properties.has_name() && properties.has_addresses() &&
properties.has_online() && properties.has_device_class() &&
properties.has_has_default_ipv4_route() && properties.has_has_default_ipv6_route())) {
return false;
}
const auto& addresses = properties.addresses();
return std::all_of(addresses.cbegin(), addresses.cend(),
[](const auto& address) { return address.has_addr(); });
}
} // namespace
std::optional<Properties> Properties::VerifyAndCreate(
fuchsia::net::interfaces::Properties properties) {
if (!VerifyCompleteProperties(properties)) {
return std::nullopt;
}
return std::make_optional(Properties(std::move(properties)));
}
Properties::Properties(fuchsia::net::interfaces::Properties properties)
: properties_(std::move(properties)) {}
Properties::Properties(Properties&& interface) noexcept = default;
Properties& Properties::operator=(Properties&& interface) noexcept = default;
Properties::~Properties() = default;
bool Properties::operator==(const Properties& rhs) const {
return fidl::Equals(properties_, rhs.properties_);
}
bool Properties::is_loopback() const {
return device_class().Which() == fuchsia::net::interfaces::DeviceClass::kLoopback;
}
bool Properties::Update(fuchsia::net::interfaces::Properties* properties) {
if (!properties->has_id() || properties_.id() != properties->id()) {
return false;
}
if (properties->has_addresses()) {
const auto& addresses = properties->addresses();
if (!std::all_of(addresses.cbegin(), addresses.cend(),
[](const auto& address) { return address.has_addr(); })) {
return false;
}
if (!fidl::Equals(properties->addresses(), properties_.addresses())) {
std::swap(*properties_.mutable_addresses(), *properties->mutable_addresses());
} else {
properties->clear_addresses();
}
}
if (properties->has_online()) {
if (properties->online() != properties_.online()) {
std::swap(*properties_.mutable_online(), *properties->mutable_online());
} else {
properties->clear_online();
}
}
if (properties->has_has_default_ipv4_route()) {
if (properties->has_default_ipv4_route() != properties_.has_default_ipv4_route()) {
std::swap(*properties_.mutable_has_default_ipv4_route(),
*properties->mutable_has_default_ipv4_route());
} else {
properties->clear_has_default_ipv4_route();
}
}
if (properties->has_has_default_ipv6_route()) {
if (properties->has_default_ipv6_route() != properties_.has_default_ipv6_route()) {
std::swap(*properties_.mutable_has_default_ipv6_route(),
*properties->mutable_has_default_ipv6_route());
} else {
properties->clear_has_default_ipv6_route();
}
}
properties->clear_id();
return true;
}
bool Properties::IsGloballyRoutable() const {
if (is_loopback() || !online()) {
return false;
}
for (const auto& address : addresses()) {
const auto& addr = address.addr().addr;
switch (addr.Which()) {
case fuchsia::net::IpAddress::Tag::kIpv4: {
if (has_default_ipv4_route()) {
return true;
}
break;
}
case fuchsia::net::IpAddress::Tag::kIpv6: {
if (has_default_ipv6_route() && !IN6_IS_ADDR_LINKLOCAL(addr.ipv6().addr.data())) {
return true;
}
break;
}
case fuchsia::net::IpAddress::Tag::Invalid: {
break;
}
}
}
return false;
}
PropertiesMap::PropertiesMap() noexcept = default;
PropertiesMap::PropertiesMap(PropertiesMap&& interface) noexcept = default;
PropertiesMap& PropertiesMap::operator=(PropertiesMap&& interface) noexcept = default;
PropertiesMap::~PropertiesMap() = default;
// static
std::string PropertiesMap::update_error_get_string(UpdateErrorVariant variant) {
UpdateErrorVisitor visitor{
[](UpdateError err) {
switch (err) {
case UpdateError::kInvalidExisting:
return std::string("invalid properties in Existing event");
case UpdateError::kInvalidAdded:
return std::string("invalid properties in Added event");
case UpdateError::kMissingId:
return std::string("missing interface ID in Changed event");
case UpdateError::kInvalidChanged:
return std::string("invalid Changed event");
case UpdateError::kInvalidEvent:
return std::string("invalid event type");
}
},
[](UpdateErrorWithId<UpdateErrorWithIdKind::kDuplicateExisting> err) {
return fxl::StringPrintf("duplicate interface (id=%lu) in Existing event", err.id);
},
[](UpdateErrorWithId<UpdateErrorWithIdKind::kDuplicateAdded> err) {
return fxl::StringPrintf("duplicate interface (id=%lu) in Added event", err.id);
},
[](UpdateErrorWithId<UpdateErrorWithIdKind::kUnknownChanged> err) {
return fxl::StringPrintf("unknown interface (id=%lu) in Changed event", err.id);
},
[](UpdateErrorWithId<UpdateErrorWithIdKind::kUnknownRemoved> err) {
return fxl::StringPrintf("unknown interface (id=%lu) in Removed event", err.id);
},
};
return std::visit(visitor, variant);
}
fpromise::result<void, PropertiesMap::UpdateErrorVariant> PropertiesMap::Update(
fuchsia::net::interfaces::Event event) {
switch (event.Which()) {
case fuchsia::net::interfaces::Event::kExisting: {
auto validated_properties = Properties::VerifyAndCreate(std::move(event.existing()));
if (!validated_properties.has_value()) {
return fpromise::error(
PropertiesMap::UpdateErrorVariant(PropertiesMap::UpdateError::kInvalidExisting));
}
const auto& [iter, inserted] =
properties_map_.emplace(validated_properties->id(), std::move(*validated_properties));
if (!inserted) {
return fpromise::error(PropertiesMap::UpdateErrorVariant(
PropertiesMap::UpdateErrorWithId<
PropertiesMap::UpdateErrorWithIdKind::kDuplicateExisting>{
.id = validated_properties->id()}));
}
return fpromise::ok();
}
case fuchsia::net::interfaces::Event::kAdded: {
auto validated_properties = Properties::VerifyAndCreate(std::move(event.added()));
if (!validated_properties.has_value()) {
return fpromise::error(
PropertiesMap::UpdateErrorVariant(PropertiesMap::UpdateError::kInvalidAdded));
}
const auto& [iter, inserted] =
properties_map_.emplace(validated_properties->id(), std::move(*validated_properties));
if (!inserted) {
return fpromise::error(PropertiesMap::UpdateErrorVariant(
PropertiesMap::UpdateErrorWithId<PropertiesMap::UpdateErrorWithIdKind::kDuplicateAdded>{
.id = validated_properties->id()}));
}
return fpromise::ok();
}
case fuchsia::net::interfaces::Event::kChanged: {
fuchsia::net::interfaces::Properties& change = event.changed();
if (!change.has_id()) {
return fpromise::error(
PropertiesMap::UpdateErrorVariant(PropertiesMap::UpdateError::kMissingId));
}
auto it = properties_map_.find(change.id());
if (it == properties_map_.end()) {
return fpromise::error(PropertiesMap::UpdateErrorVariant(
PropertiesMap::UpdateErrorWithId<PropertiesMap::UpdateErrorWithIdKind::kUnknownChanged>{
.id = change.id()}));
}
auto& properties = it->second;
if (!properties.Update(&change)) {
return fpromise::error(
PropertiesMap::UpdateErrorVariant(PropertiesMap::UpdateError::kInvalidChanged));
}
return fpromise::ok();
}
case fuchsia::net::interfaces::Event::kRemoved: {
const auto nh = properties_map_.extract(event.removed());
if (nh.empty()) {
return fpromise::error(PropertiesMap::UpdateErrorVariant(
PropertiesMap::UpdateErrorWithId<PropertiesMap::UpdateErrorWithIdKind::kUnknownRemoved>{
.id = event.removed()}));
}
return fpromise::ok();
}
case fuchsia::net::interfaces::Event::kIdle: {
return fpromise::ok();
}
case fuchsia::net::interfaces::Event::Invalid: {
return fpromise::error(
PropertiesMap::UpdateErrorVariant(PropertiesMap::UpdateError::kInvalidEvent));
}
}
}
} // namespace net::interfaces