| // 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 |