blob: e4b151128afcaf56345d7b4881db27ccc522b278 [file] [log] [blame]
// Copyright 2018 The Chromium OS 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 "portier/nd_proxy.h"
#include <utility>
#include <base/bind.h>
#include <base/logging.h>
#include <base/stl_util.h>
#include "portier/ipv6_util.h"
namespace portier {
using std::shared_ptr;
using std::string;
using brillo::MessageLoop;
using shill::ByteString;
using TaskId = brillo::MessageLoop::TaskId;
using Code = Status::Code;
using FdTaskPair = std::pair<const int32_t, TaskId>;
using NameTaskPair = std::pair<const string, TaskId>;
using NameIfPair = std::pair<const string, shared_ptr<ProxyInterface>>;
using NameNamePair = std::pair<const string, string>;
namespace {
// Specified disable time for proxy interfaces which have been disabled
// for loop prevention purposes. See RFC 4389 section 4.1.3.3.
constexpr base::TimeDelta kInterfaceDisableTime =
base::TimeDelta::FromMinutes(60);
} // namespace
std::unique_ptr<NeighborDiscoveryProxy> NeighborDiscoveryProxy::Create(
bool nested_mode) {
auto ndp_ptr = std::unique_ptr<NeighborDiscoveryProxy>(
new NeighborDiscoveryProxy(nested_mode));
return ndp_ptr;
}
Status NeighborDiscoveryProxy::ManagerInterface(const string& if_name) {
if (IsManagingInterface(if_name)) {
return Status(Code::ALREADY_EXISTS)
<< "The interface " << if_name << " is already being managed";
}
auto uif = ProxyInterface::Create(if_name);
if (!uif) {
return Status(Code::UNEXPECTED_FAILURE)
<< "Failed to create proxy interface " << if_name;
}
shared_ptr<ProxyInterface> proxy_if(std::move(uif));
proxy_ifs_.insert(NameIfPair(if_name, proxy_if));
// Setup event handler.
const int nd_fd = proxy_if->GetNDFd();
auto nd_task = base::FileDescriptorWatcher::WatchReadable(
nd_fd,
base::BindRepeating(&NeighborDiscoveryProxy::HandleNeighborDiscoverPacket,
base::Unretained(this), if_name));
fd_tasks_.emplace(nd_fd, std::move(nd_task));
const int ipv6_fd = proxy_if->GetIPv6Fd();
auto ipv6_task = base::FileDescriptorWatcher::WatchReadable(
ipv6_fd, base::BindRepeating(&NeighborDiscoveryProxy::HandleIPv6Packet,
base::Unretained(this), if_name));
fd_tasks_.emplace(ipv6_fd, std::move(ipv6_task));
LOG(INFO) << "ND Proxy is now managing interface " << if_name;
return Status();
}
Status NeighborDiscoveryProxy::ReleaseInterface(const string& if_name) {
auto it = proxy_ifs_.find(if_name);
if (it == proxy_ifs_.end()) {
return Status(Code::DOES_NOT_EXIST)
<< "The interface " << if_name << " is not being managed";
}
auto proxy_if = it->second;
// Remove from proxy group if member.
if (proxy_if->HasGroup()) {
auto group = proxy_if->GetGroup();
group->RemoveMember(proxy_if);
}
// Cancel all associated tasks.
fd_tasks_.erase(proxy_if->GetNDFd());
fd_tasks_.erase(proxy_if->GetIPv6Fd());
const auto loop_pair = loop_tasks_.find(if_name);
if (loop_pair != loop_tasks_.end()) {
MessageLoop::current()->CancelTask(loop_pair->second);
loop_tasks_.erase(loop_pair);
}
// Remove interface from interface list.
proxy_ifs_.erase(it);
DCHECK_EQ(proxy_if.use_count(), 1);
proxy_if.reset();
LOG(INFO) << "ND Proxy has stopped managing interface " << if_name;
return Status();
}
bool NeighborDiscoveryProxy::IsManagingInterface(const string& if_name) const {
return base::ContainsKey(proxy_ifs_, if_name);
}
Status NeighborDiscoveryProxy::CreateProxyGroup(const string& pg_name) {
const Status create_status = group_manager_.CreateGroup(pg_name);
if (create_status) {
LOG(INFO) << "Created proxy group " << pg_name;
}
return create_status;
}
Status NeighborDiscoveryProxy::ReleaseProxyGroup(const string& pg_name) {
const Status release_status = group_manager_.ReleaseGroup(pg_name);
if (release_status) {
LOG(INFO) << "Released proxy group " << pg_name;
}
return release_status;
}
bool NeighborDiscoveryProxy::HasProxyGroup(const string& pg_name) const {
return group_manager_.HasGroup(pg_name);
}
// Membership.
Status NeighborDiscoveryProxy::AddToGroup(const string& if_name,
const string& pg_name,
bool as_upstream) {
auto proxy_if = GetInterface(if_name);
if (!proxy_if) {
return Status(Code::DOES_NOT_EXIST)
<< "Interface " << if_name << " does not exists";
}
auto group = group_manager_.GetGroup(pg_name);
if (!group) {
return Status(Code::DOES_NOT_EXIST)
<< "Proxy group " << pg_name << " does not exists";
}
if (proxy_if->HasGroup()) {
return Status(Code::ALREADY_EXISTS)
<< "Interface " << if_name << " is already a member of group "
<< proxy_if->GetGroup()->name();
}
if (!group->AddMember(proxy_if)) {
return Status(Code::UNEXPECTED_FAILURE)
<< "Failed to add interface " << if_name << " to proxy group "
<< pg_name;
}
if (as_upstream) {
group->SetUpstream(proxy_if);
}
return Status();
}
Status NeighborDiscoveryProxy::RemoveFromGroup(const string& if_name) {
auto proxy_if = GetInterface(if_name);
if (!proxy_if) {
return Status(Code::DOES_NOT_EXIST)
<< "Interface " << if_name << " does not exists";
}
auto group = proxy_if->GetGroup();
if (!group) {
return Status(Code::DOES_NOT_EXIST)
<< "Interface " << if_name << " is not a member of any group";
}
if (!group->RemoveMember(proxy_if)) {
return Status(Code::UNEXPECTED_FAILURE)
<< "Failed to remove interface " << if_name << " from proxy group "
<< group->name();
}
// Clear any loop issue. Should be done after so that no packet is
// proxied.
const auto loop_pair = loop_tasks_.find(if_name);
if (loop_pair != loop_tasks_.end()) {
MessageLoop::current()->CancelTask(loop_pair->second);
loop_tasks_.erase(loop_pair);
}
return Status();
}
Status NeighborDiscoveryProxy::SetAsUpstream(const std::string& if_name) {
auto proxy_if = GetInterface(if_name);
if (!proxy_if) {
return Status(Code::DOES_NOT_EXIST)
<< "Interface " << if_name << " does not exists";
}
auto group = proxy_if->GetGroup();
if (!group) {
return Status(Code::DOES_NOT_EXIST)
<< "Interface " << if_name << " is not a member of any group";
}
group->SetUpstream(proxy_if);
return Status();
}
// private.
shared_ptr<ProxyInterface> NeighborDiscoveryProxy::GetInterface(
const string& if_name) const {
auto it = proxy_ifs_.find(if_name);
if (it != proxy_ifs_.cend()) {
return it->second;
}
return nullptr;
}
// private.
void NeighborDiscoveryProxy::HandleLoopDetection(
shared_ptr<ProxyInterface> proxy_if) {
DCHECK(proxy_if);
DCHECK(proxy_if->HasGroup());
// Clear old task, might be left over from a previous loop detection.
const auto loop_pair = loop_tasks_.find(proxy_if->name());
if (loop_pair != loop_tasks_.end()) {
MessageLoop::current()->CancelTask(loop_pair->second);
loop_tasks_.erase(loop_pair);
}
proxy_if->MarkLoopDetected();
const TaskId loop_task_id = MessageLoop::current()->PostDelayedTask(
base::Bind(&NeighborDiscoveryProxy::LoopTimeOut, base::Unretained(this),
proxy_if->name(), proxy_if->GetGroup()->name()),
kInterfaceDisableTime);
loop_tasks_.insert(NameTaskPair(proxy_if->name(), loop_task_id));
}
// Packet event handlers.
// private.
void NeighborDiscoveryProxy::HandleNeighborDiscoverPacket(string if_name) {
auto proxy_if = GetInterface(if_name);
if (!proxy_if) {
// Possible that interface was removed while the call back was queued.
return;
}
if (!proxy_if->IsEnabled()) {
proxy_if->DiscardNeighborDiscoveryInput();
}
if (!proxy_if->HasGroup()) {
LOG(WARNING) << "Proxy interface " << proxy_if->name()
<< " was enabled, but not part of a group";
proxy_if->MarkGroupless();
proxy_if->DiscardNeighborDiscoveryInput();
return;
}
IPv6EtherHeader header_fields;
NeighborDiscoveryMessage nd_message;
Status receive_status =
proxy_if->ReceiveNeighborDiscoveryMessage(&header_fields, &nd_message);
if (receive_status.code() == Code::RESULT_UNAVAILABLE) {
// Possible if a zero packet was received.
return;
}
if (!receive_status) {
receive_status << "Failed to receive ND on if " << proxy_if->name();
LOG(ERROR) << receive_status;
return;
}
// Check if locally destined.
if (proxy_if->HasIPv6Address(header_fields.destination_address)) {
// Don't need to handle locally destined packets.
return;
}
ProxyGroup* group = proxy_if->GetGroup();
// We already did a check for proxy_if->HasGroup().
CHECK(group);
// Loop prevention check.
const NeighborDiscoveryMessage::Type nd_type = nd_message.type();
if (nd_type == NeighborDiscoveryMessage::kTypeRouterAdvert) {
if (!proxy_if->IsUpstream()) {
// Router advertisements should not be received on a downstream
// interface.
LOG(WARNING) << "An RA was received on downstream interface "
<< proxy_if->name();
HandleLoopDetection(proxy_if);
return;
}
// else, interface is upstream.
// Check if there is a potential loop.
bool proxy_flag = false;
nd_message.GetProxyFlag(&proxy_flag);
if (proxy_flag && !IsNested()) {
// If the daemon is not running in nested mode, than the proxy bit
// indicates a loop on this interface.
LOG(WARNING) << "A proxied RA set received on interface "
<< proxy_if->name();
HandleLoopDetection(proxy_if);
return;
}
}
// Handle the case of multicast.
if (IPv6AddressIsMulticast(header_fields.destination_address)) {
LLAddress destination_ll_address;
IPv6GetMulticastLinkLayerAddress(header_fields.destination_address,
&destination_ll_address);
LOG(INFO) << "Multicast ND Msg (" << nd_message.type() << ") "
<< proxy_if->name() << " from "
<< header_fields.source_address.ToString();
// Multicast, then forward packet to all interfaces other than
// the one received on.
auto group_members = group->GetMembers();
for (auto& group_if : group_members) {
if (group_if == proxy_if) {
// Don't proxy out the same interface.
continue;
}
if (!group_if->IsEnabled()) {
continue;
}
const Status send_status = group_if->ProxyNeighborDiscoveryMessage(
header_fields, destination_ll_address, nd_message);
if (!send_status) {
LOG(ERROR) << send_status;
}
}
return;
}
NeighborCacheEntry out_entry;
if (!neighbor_cache_.GetEntry(header_fields.destination_address,
group->name(), &out_entry)) {
// TODO(siquit): Queue ND packet and perform neighbor discovery.
LOG(INFO) << "No neighbor for "
<< header_fields.destination_address.ToString();
return;
}
shared_ptr<ProxyInterface> out_if = GetInterface(out_entry.if_name);
if (!out_if || !out_if->IsEnabled()) {
return;
}
if (out_if->name() == proxy_if->name()) {
// Don't proxy out the same interface. Should be silently dropped.
return;
}
LOG(INFO) << "Unicast ND Msg (" << nd_message.type() << ") "
<< proxy_if->name() << " from "
<< header_fields.source_address.ToString() << " out "
<< out_if->name() << " to "
<< header_fields.destination_address.ToString();
Status send_status = out_if->ProxyNeighborDiscoveryMessage(
header_fields, out_entry.ll_address, nd_message);
if (!send_status) {
send_status << "Failed to proxy from " << proxy_if->name() << " to "
<< out_if->name();
LOG(ERROR) << send_status;
}
}
void NeighborDiscoveryProxy::HandleIPv6Packet(string if_name) {
auto proxy_if = GetInterface(if_name);
if (!proxy_if) {
// Possible that interface was removed while the call back was queued.
return;
}
if (!proxy_if->IsEnabled()) {
proxy_if->DiscardIPv6Input();
}
if (!proxy_if->HasGroup()) {
LOG(WARNING) << "Proxy interface " << proxy_if->name()
<< " was enabled, but not part of a group";
proxy_if->MarkGroupless();
proxy_if->DiscardIPv6Input();
return;
}
IPv6EtherHeader header_fields;
ByteString payload;
Status receive_status = proxy_if->ReceiveIPv6Packet(&header_fields, &payload);
if (receive_status.code() == Code::RESULT_UNAVAILABLE) {
// Possible if a zero packet was received.
return;
}
if (!receive_status) {
receive_status << "Failed to receive ND on if " << proxy_if->name();
LOG(ERROR) << receive_status;
return;
}
// Check if locally destined.
if (proxy_if->HasIPv6Address(header_fields.destination_address)) {
// Don't need to handle locally destined packets.
return;
}
ProxyGroup* group = proxy_if->GetGroup();
// We already did a check for proxy_if->HasGroup().
CHECK(group);
// If multicast, then forward packet to all interfaces other than
// the one received on.
if (IPv6AddressIsMulticast(header_fields.destination_address)) {
LLAddress destination_ll_address;
IPv6GetMulticastLinkLayerAddress(header_fields.destination_address,
&destination_ll_address);
LOG(INFO) << "Multicast IPv6 Packet " << proxy_if->name() << " from "
<< header_fields.source_address.ToString();
// Multicast, then forward packet to all interfaces other than
// the one received on.
auto group_members = group->GetMembers();
for (auto& group_if : group_members) {
if (group_if == proxy_if) {
// Don't proxy out the same interface.
continue;
}
if (!group_if->IsEnabled()) {
continue;
}
const Status send_status = group_if->SendIPv6Packet(
header_fields, destination_ll_address, payload);
if (!send_status) {
LOG(ERROR) << send_status;
}
}
return;
}
NeighborCacheEntry out_entry;
if (!neighbor_cache_.GetEntry(header_fields.destination_address,
group->name(), &out_entry)) {
// TODO(sigquit): Queue IP packet and perform neighbor discovery.
return;
}
shared_ptr<ProxyInterface> out_if = GetInterface(out_entry.if_name);
if (!out_if || !out_if->IsEnabled()) {
return;
}
if (out_if->name() == proxy_if->name()) {
// Don't proxy out the same interface. Should be silently dropped.
return;
}
LOG(INFO) << "Unicast IPv6 Packet " << proxy_if->name() << " from "
<< header_fields.source_address.ToString() << " out "
<< out_if->name() << " to "
<< header_fields.destination_address.ToString();
Status send_status =
out_if->SendIPv6Packet(header_fields, out_entry.ll_address, payload);
if (!send_status) {
send_status << "Failed to proxy from " << proxy_if->name() << " to "
<< out_if->name();
LOG(ERROR) << send_status;
}
}
void NeighborDiscoveryProxy::LoopTimeOut(string if_name, string pg_name) {
auto proxy_if = GetInterface(if_name);
if (!proxy_if || !proxy_if->HasGroup() ||
proxy_if->GetGroup()->name() != pg_name ||
!base::ContainsKey(loop_tasks_, if_name)) {
return;
}
proxy_if->ClearLoopDetected();
}
} // namespace portier