blob: dcf3b9967493595c55a4fb26ed4479ea38b17a51 [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/proxy_interface.h"
#include <errno.h>
#include <ifaddrs.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <netinet/icmp6.h>
#include <netinet/ip6.h>
#include <algorithm>
#include <utility>
#include <base/logging.h>
#include <base/strings/stringprintf.h>
#include <base/stl_util.h>
#include <base/posix/safe_strerror.h>
#include "portier/ipv6_util.h"
#include "portier/nd_bpf.h"
namespace portier {
using std::string;
using std::unique_ptr;
using std::vector;
using shill::ByteString;
using shill::IPAddress;
using Code = Status::Code;
using State = ProxyInterface::State;
namespace {
// Required hop-limit of all outgoing ND Proxy packets.
constexpr uint8_t kProxiedHopLimit = 255;
} // namespace
// static.
string ProxyInterface::GetStateName(State state) {
switch (state) {
case State::kInvalid:
return "Invalid";
case State::kUninitialized:
return "Uninitialized";
case State::kProxyEnabled:
return "Enabled";
case State::kProxyDisabled:
return "Disabled";
case State::kDeinitialized:
return "Deinitialized";
}
return base::StringPrintf("Unknown (%d)", static_cast<int>(state));
}
// static.
unique_ptr<ProxyInterface> ProxyInterface::Create(const string& if_name) {
unique_ptr<ProxyInterface> proxy_if(new ProxyInterface(if_name));
const Status init_status = proxy_if->Init();
if (!init_status) {
proxy_if.reset();
}
return proxy_if;
}
// private.
ProxyInterface::ProxyInterface(const std::string& if_name)
: state_(State::kUninitialized), name_(if_name), mtu_(0) {}
ProxyInterface::~ProxyInterface() {
if (State::kInvalid == state_ || State::kUninitialized == state_ ||
State::kDeinitialized == state_) {
// Nothing to do.
return;
}
state_ = State::kDeinitialized;
}
// private.
Status ProxyInterface::Init() {
DCHECK_NE(state(), State::kInvalid);
if (name().empty()) {
MarkInvalid();
return Status(Code::INVALID_ARGUMENT,
"Empty string is not a valid interface name");
}
// Open ND ethernet socket.
auto nd_sock = EtherSocket::Create(name());
if (!nd_sock) {
MarkInvalid();
return Status(Code::UNEXPECTED_FAILURE,
"Failed to initialize the ND ether socket");
}
nd_sock_ = std::move(nd_sock);
Status status = nd_sock_->SetNonBlockingMode(true);
if (!status) {
MarkInvalid();
return status;
}
// Check that the interface is not the loopback interface. Using
// loopback interface as a proxy interface will cause echoed and/or
// duplicate multicast packet proxying.
bool loopback_flag = false;
status = nd_sock_->GetLoopbackFlag(&loopback_flag);
if (!status) {
MarkInvalid();
return status;
}
if (loopback_flag) {
MarkInvalid();
return Status(Code::INVALID_ARGUMENT)
<< "Cannot make a loopback interface (" << name()
<< ") into a proxy interface";
}
status = nd_sock_->SetAllMulticastMode(true);
if (!status) {
MarkInvalid();
return status;
}
status = nd_sock_->AttachFilter(&kNeighborDiscoveryFilter);
// Attacted filter.
if (!status) {
MarkInvalid();
return status;
}
// Open IPv6 socket.
auto ipv6_sock = EtherSocket::Create(name());
if (!ipv6_sock) {
MarkInvalid();
return Status(Code::UNEXPECTED_FAILURE,
"Failed to initialize IPv6 ether socket");
}
ipv6_sock_ = std::move(ipv6_sock);
status = ipv6_sock_->SetNonBlockingMode(true);
if (!status) {
MarkInvalid();
return status;
}
status = ipv6_sock_->SetAllMulticastMode(true);
if (!status) {
MarkInvalid();
return status;
}
status = ipv6_sock_->AttachFilter(&kNonNeighborDiscoveryFilter);
// Attacted filter.
if (!status) {
MarkInvalid();
return status;
}
// Open ICMPv6 socket.
auto icmp_sock = ICMPv6Socket::Create(name());
if (!icmp_sock) {
MarkInvalid();
return Status(Code::UNEXPECTED_FAILURE,
"Failed to initialize ICMPv6 socket");
}
icmp_sock_ = std::move(icmp_sock);
// Set as non-blocking.
status = icmp_sock_->SetNonBlockingMode(true);
if (!status) {
MarkInvalid();
return status;
}
// Get link-layer address.
LLAddress ll_address;
status = icmp_sock_->GetLinkLayerAddress(&ll_address);
if (!status) {
MarkInvalid();
return status;
}
ll_address_ = ll_address;
// Get link-layer MTU.
uint32_t mtu;
status = icmp_sock_->GetLinkMTU(&mtu);
if (!status) {
MarkInvalid();
return status;
}
mtu_ = mtu;
// Attach filter to block all in-coming packets. For now, the ICMP
// socket is to be used only for sending messages.
// Info on ICMP6_FILTER in RFC3542, section 3.2.
struct icmp6_filter icmp6_filter;
ICMP6_FILTER_SETBLOCKALL(&icmp6_filter);
status = icmp_sock_->AttachFilter(&icmp6_filter);
if (!status) {
MarkInvalid();
return status;
}
// Set hop limits. See Linux manual ipv6(7).
// Multicast.
status = icmp_sock_->SetMulticastHopLimit(kProxiedHopLimit);
if (!status) {
MarkInvalid();
return status;
}
// Unicast.
status = icmp_sock_->SetMulticastHopLimit(kProxiedHopLimit);
if (!status) {
MarkInvalid();
return status;
}
if (!InternalRefreshIPv6AddressList()) {
MarkInvalid();
return Status(Code::UNEXPECTED_FAILURE)
<< "Failed to refresh IP address list on interface " << name();
}
// Done. Mark as disabled.
state_ = State::kProxyDisabled;
return Status();
}
// private.
void ProxyInterface::MarkInvalid() {
state_ = State::kInvalid;
CloseOpenedFds();
mtu_ = 0;
}
void ProxyInterface::CloseOpenedFds() {
if (nd_sock_ && nd_sock_->IsReady()) {
nd_sock_->Close();
}
if (ipv6_sock_ && ipv6_sock_->IsReady()) {
ipv6_sock_->Close();
}
if (icmp_sock_ && icmp_sock_->IsReady()) {
icmp_sock_->Close();
}
}
bool ProxyInterface::IsValid() const {
return (State::kInvalid != state_);
}
int ProxyInterface::GetInterfaceIndex() const {
if (IsInitialized()) {
return nd_sock_->index();
}
return -1;
}
const string& ProxyInterface::name() const {
return name_;
}
int ProxyInterface::GetNDFd() const {
return nd_sock_ ? nd_sock_->fd() : -1;
}
int ProxyInterface::GetIPv6Fd() const {
return ipv6_sock_ ? ipv6_sock_->fd() : -1;
}
int ProxyInterface::GetICMPFd() const {
return icmp_sock_ ? icmp_sock_->fd() : -1;
}
// L3 Information.
bool ProxyInterface::RefreshIPv6AddressList() {
if (!IsInitialized()) {
return false;
}
return InternalRefreshIPv6AddressList();
}
// private.
bool ProxyInterface::InternalRefreshIPv6AddressList() {
DCHECK(!name().empty());
struct ifaddrs* if_addr_head;
if (getifaddrs(&if_addr_head) < 0) {
const int saved_errno = errno;
LOG(ERROR) << "Failed to get if addresses: getifaddrs(): "
<< base::safe_strerror(saved_errno);
return false;
}
ip_addresses_.clear();
// Need to loop through all address across all interfaces. Skipping
// non-IPv6 address and addresses that are unrelated to this
// interface.
struct ifaddrs* if_addr_node = if_addr_head;
while (if_addr_node) {
if (if_addr_node->ifa_addr != nullptr &&
if_addr_node->ifa_addr->sa_family == AF_INET6 &&
name() == if_addr_node->ifa_name) {
// Must use sizeof(struct sockaddr_in6) instead of
// sizeof(if_addr_node->ifa_addr). We know from the if statement
// that the `ifa_addr' is an IPv6 socket address.
IPAddress address(if_addr_node->ifa_addr, sizeof(struct sockaddr_in6));
if (address.IsValid() && address.family() == IPAddress::kFamilyIPv6) {
ip_addresses_.push_back(address);
}
}
if_addr_node = if_addr_node->ifa_next;
}
freeifaddrs(if_addr_head);
return true;
}
bool ProxyInterface::HasIPv6Address(const shill::IPAddress& address) const {
return base::ContainsValue(ip_addresses_, address);
}
// Proxy State.
bool ProxyInterface::IsInitialized() const {
return (State::kProxyEnabled == state_ || State::kProxyDisabled == state_);
}
bool ProxyInterface::IsEnabled() const {
return (State::kProxyEnabled == state_);
}
bool ProxyInterface::EnableProxy() {
if (!IsInitialized()) {
LOG(WARNING) << "Cannot enable an uninitialized interface: " << name_;
return false;
}
if (IsEnabled()) {
return true;
}
// Add any code required to enable the interface here.
state_ = State::kProxyEnabled;
return true;
}
bool ProxyInterface::DisableProxy() {
if (!IsInitialized()) {
LOG(WARNING) << "Cannot disable an uninitialized interface: " << name_;
return false;
}
if (!IsEnabled()) {
return true;
}
// Add any code required to disable the interface here.
state_ = State::kProxyDisabled;
return true;
}
// Callbacks.
// protected.
void ProxyInterface::PostJoinGroup() {}
void ProxyInterface::PostLeaveGroup() {}
bool ProxyInterface::Deinitialize() {
if (!IsInitialized()) {
LOG(WARNING) << "Cannot deinitialize an uninitialized interface: "
<< name();
return false;
}
CloseOpenedFds();
state_ = State::kDeinitialized;
return true;
}
// Sending ND Messages.
Status ProxyInterface::ProxyNeighborDiscoveryMessage(
IPv6EtherHeader header_fields,
const LLAddress& destination_ll_address,
NeighborDiscoveryMessage nd_message) {
if (!IsInitialized()) {
return Status(Code::BAD_INTERNAL_STATE)
<< "Cannot proxy on an uninitialized interface: " << name();
}
// Header validation.
DCHECK(destination_ll_address.IsValid())
<< "Destination link-layer address is invalid";
DCHECK_EQ(IPAddress::kFamilyIPv6, header_fields.source_address.family())
<< "Source address must be IPv6";
DCHECK_EQ(IPAddress::kFamilyIPv6, header_fields.destination_address.family())
<< "Destination address must be IPv6";
if (IPv6AddressIsUnspecified(header_fields.destination_address)) {
return Status(Code::INVALID_ARGUMENT,
"Cannot proxy to an unspecified destination address");
}
if (header_fields.next_header != IPPROTO_ICMPV6) {
return Status(Code::INVALID_ARGUMENT,
"Cannot proxy a non ICMPv6 packet on the ND socket");
}
// ND Message validation.
DCHECK(nd_message.IsValid()) << "ND message must be valid";
const NeighborDiscoveryMessage::Type nd_type = nd_message.type();
// If router advertisement, set the proxy bit.
if (nd_type == NeighborDiscoveryMessage::kTypeRouterAdvert) {
nd_message.SetProxyFlag(true);
}
header_fields.hop_limit = kProxiedHopLimit;
// Link-layer modifications.
header_fields.source_ll_address = ll_address();
header_fields.destination_ll_address = destination_ll_address;
if (nd_message.HasSourceLinkLayerAddress()) {
LLAddress source_ll_address;
nd_message.GetSourceLinkLayerAddress(0, &source_ll_address);
if (!source_ll_address.IsMulticast()) {
nd_message.SetSourceLinkLayerAddress(0, ll_address());
}
}
if (nd_message.HasTargetLinkLayerAddress()) {
LLAddress target_ll_address;
nd_message.GetTargetLinkLayerAddress(0, &target_ll_address);
if (!target_ll_address.IsMulticast()) {
nd_message.SetTargetLinkLayerAddress(0, ll_address());
}
}
// To calculate the checksum, the current value must be zero.
nd_message.SetChecksum(0);
uint16_t checksum = 0;
const Status checksum_status = IPv6UpperLayerChecksum16(
header_fields.source_address, header_fields.destination_address,
IPPROTO_ICMPV6, nd_message.message(), &checksum);
if (checksum_status) {
nd_message.SetChecksum(~checksum);
} else {
LOG(WARNING) << checksum_status;
// Setting the checksum to 0 indicates that the checksum is not set.
nd_message.SetChecksum(0);
}
DCHECK(nd_sock_);
Status send_status =
nd_sock_->SendIPv6Packet(header_fields, nd_message.message());
PORTIER_RETURN_ON_FAILURE(send_status)
<< "Failed to proxy ND message on interface " << name();
return Status();
}
// Receiving ND Messages.
Status ProxyInterface::ReceiveNeighborDiscoveryMessage(
IPv6EtherHeader* header_fields, NeighborDiscoveryMessage* nd_message) {
DCHECK(header_fields)
<< "Must provide an ND Message output parameter to receive "
<< "ND messages on interface " << name_;
if (!IsInitialized()) {
return Status(Code::BAD_INTERNAL_STATE)
<< "Cannot receive from an uninitialized interface " << name();
}
DCHECK(nd_sock_);
ByteString payload;
Status receive_status = nd_sock_->ReceiveIPv6Packet(header_fields, &payload);
PORTIER_RETURN_ON_FAILURE(receive_status)
<< "Failed to receive ND message on if " << name();
if (header_fields->hop_limit != kProxiedHopLimit) {
// RFC 4861: A node MUST silently discard any received Router
// Advertisement (section 6.1.2), Neighbor Solicitation (section 7.1.1),
// Neighbor Advertisement (section 7.1.2) if the IP Hop Limit field does
// not have a value of 255.
return Status(Code::RESULT_UNAVAILABLE);
}
// This should have been caught by BPF filter.
DCHECK_EQ(IPPROTO_ICMPV6, header_fields->next_header)
<< "Next header is not ICMPv6";
if (payload.GetLength() < sizeof(struct icmp6_hdr)) {
return Status(Code::MALFORMED_PACKET)
<< "Received ICMPv6 packet is smaller than ICMPv6 header";
}
const struct icmp6_hdr* icmp6_hdr =
reinterpret_cast<const struct icmp6_hdr*>(payload.GetConstData());
// Ensure that the ICMPv6 packet contains a proxyable ND message. These
// should have been filtered out by the BPF filter.
DCHECK(
NeighborDiscoveryMessage::kTypeRouterAdvert == icmp6_hdr->icmp6_type ||
NeighborDiscoveryMessage::kTypeNeighborSolicit == icmp6_hdr->icmp6_type ||
NeighborDiscoveryMessage::kTypeNeighborAdvert == icmp6_hdr->icmp6_type ||
NeighborDiscoveryMessage::kTypeRedirect == icmp6_hdr->icmp6_type);
DCHECK_EQ(icmp6_hdr->icmp6_code, 0);
// Extract ND Message.
*nd_message = NeighborDiscoveryMessage(payload);
if (!nd_message->IsValid()) {
return Status(Code::MALFORMED_PACKET)
<< "Failed to parse ND message packet";
}
return Status();
}
Status ProxyInterface::DiscardNeighborDiscoveryInput() {
if (!IsInitialized()) {
return Status(Code::BAD_INTERNAL_STATE)
<< "Cannot discard from an uninitialized interface " << name_;
}
DCHECK(nd_sock_);
return nd_sock_->DiscardPacket();
}
Status ProxyInterface::SendIPv6Packet(IPv6EtherHeader header_fields,
const LLAddress& destination_ll_address,
const ByteString& payload) {
if (!IsInitialized()) {
return Status(Code::BAD_INTERNAL_STATE)
<< "Cannot proxy on an uninitialized interface: " << name();
}
// Header validation.
DCHECK(destination_ll_address.IsValid())
<< "Destination link-layer address is invalid";
DCHECK_EQ(IPAddress::kFamilyIPv6, header_fields.source_address.family())
<< "Source address must be IPv6";
DCHECK_EQ(IPAddress::kFamilyIPv6, header_fields.destination_address.family())
<< "Destination address must be IPv6";
if (IPv6AddressIsUnspecified(header_fields.destination_address)) {
return Status(Code::INVALID_ARGUMENT)
<< "Cannot proxy to an unspecified destination address: " << name();
}
// Link-layer modification.
header_fields.source_ll_address = ll_address();
header_fields.destination_ll_address = destination_ll_address;
DCHECK(ipv6_sock_);
Status send_status = ipv6_sock_->SendIPv6Packet(header_fields, payload);
PORTIER_RETURN_ON_FAILURE(send_status)
<< "Failed to proxy ND message on interface " << name();
return Status();
}
Status ProxyInterface::ReceiveIPv6Packet(IPv6EtherHeader* header_fields,
ByteString* payload) {
if (!IsInitialized()) {
return Status(Code::BAD_INTERNAL_STATE)
<< "Cannot receive from an uninitialized interface " << name_;
}
DCHECK(ipv6_sock_);
return ipv6_sock_->ReceiveIPv6Packet(header_fields, payload);
}
Status ProxyInterface::DiscardIPv6Input() {
if (!IsInitialized()) {
return Status(Code::BAD_INTERNAL_STATE)
<< "Cannot discard from an uninitialized interface " << name_;
}
DCHECK(ipv6_sock_);
return ipv6_sock_->DiscardPacket();
}
Status ProxyInterface::SendPacketTooBigMessage(
const IPAddress& destination_address,
uint32_t mtu,
const IPv6EtherHeader& original_header,
const ByteString& original_body) {
if (!IsInitialized()) {
return Status(Code::BAD_INTERNAL_STATE)
<< "Cannot send ICMPv6 Packet Too Big on an uninitialized interface "
<< name_;
}
DCHECK(icmp_sock_);
return icmp_sock_->SendPacketTooBigMessage(destination_address, mtu,
original_header, original_body);
}
} // namespace portier