blob: 247edacad40b02f81e3ed4edc550541dfa35aca2 [file] [log] [blame]
// Copyright 2023 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 "network.h"
#include <numeric>
#include <efi/protocol/managed-network.h>
#include <gigaboot/cpp/utils.h>
namespace gigaboot {
namespace {
// Given an efi_mac_addr, return a 6 byte octet "real" MAC address.
MacAddr MacAddrFromEfiMacAddr(efi_mac_addr const& addr) {
MacAddr eth;
static_assert(sizeof(addr.addr) >= eth.size(), "Error: source MAC is too small");
std::copy(std::cbegin(addr.addr), std::cbegin(addr.addr) + eth.size(), std::begin(eth));
return eth;
}
// Given a "real" MAC address, return an efi_mac_addr.
efi_mac_addr EfiMacAddrFromMacAddr(MacAddr addr) {
efi_mac_addr eth = {};
static_assert(addr.size() <= sizeof(eth.addr), "Error: source MAC is too large");
std::copy(addr.cbegin(), addr.cend(), std::begin(eth.addr));
return eth;
}
// Returns the first functional network interface found, configured to support unicast receive,
// or nullptr if there is no such interface.
EfiProtocolPtr<efi_managed_network_protocol> FindNetworkIntf() {
auto managed_handles = EfiLocateHandleBufferByProtocol<efi_managed_network_protocol>();
if (managed_handles.is_error()) {
return nullptr;
}
for (auto handle : managed_handles->AsSpan()) {
auto managed_proto = EfiOpenProtocol<efi_managed_network_protocol>(handle);
if (managed_proto.is_error()) {
continue;
}
efi_managed_network_config_data mnp_config_data;
efi_simple_network_mode snp_mode_data;
if (managed_proto->GetModeData(managed_proto.value().get(), &mnp_config_data, &snp_mode_data) !=
EFI_SUCCESS) {
continue;
}
if (!snp_mode_data.MediaPresent) {
continue;
}
// Note: this managed network instance may already have been started by the TCP6 stack.
// If that's the case, it should already have unicast receive enabled, and we don't
// want to stop traffic to enable it.
if (!mnp_config_data.EnableUnicastReceive) {
if (managed_proto->Configure(managed_proto.value().get(), nullptr) != EFI_SUCCESS) {
continue;
}
mnp_config_data.EnableUnicastReceive = true;
if (managed_proto->Configure(managed_proto.value().get(), &mnp_config_data) != EFI_SUCCESS) {
continue;
}
}
return std::move(managed_proto.value());
}
return nullptr;
}
} // namespace
Ip6Header::Ip6Header(uint16_t len, uint8_t next_hdr, const Ip6Addr& src, const Ip6Addr& dst)
: vtcf(htonl(6 << 28)),
length(htons(len)),
next_header(next_hdr),
hop_limit(0xFF),
source(src),
dest(dst) {}
uint16_t CalculateChecksum(cpp20::span<const uint8_t> data, uint64_t start) {
cpp20::span<const uint16_t> tmp(reinterpret_cast<const uint16_t*>(data.data()),
data.size() / sizeof(uint16_t));
// The start and result are wider than a u16 to properly hold the overflow bits
// from 1s complement addition.
uint64_t sum = std::reduce(tmp.begin(), tmp.end(), start);
if (data.size() % 2 == 1) {
sum += data.back();
}
// 1s complement addition works by wrapping overflow bits and adding them.
while (sum > 0xFFFF) {
sum = (sum & 0xFFFF) + (sum >> 16);
}
return static_cast<uint16_t>(sum);
}
fit::result<efi_status, EthernetAgent> EthernetAgent::Create() {
EfiProtocolPtr<efi_managed_network_protocol> mnp = FindNetworkIntf();
if (!mnp) {
return fit::error(EFI_NO_MEDIA);
}
efi_managed_network_config_data mnp_data;
efi_simple_network_mode snp_data;
if (efi_status res = mnp->GetModeData(mnp.get(), &mnp_data, &snp_data); res != EFI_SUCCESS) {
return fit::error(res);
}
MacAddr mac_addr = MacAddrFromEfiMacAddr(snp_data.CurrentAddress);
return fit::ok(EthernetAgent(std::move(mnp), mac_addr));
}
fit::result<efi_status> EthernetAgent::SendV6LocalFrame(
MacAddr dst, cpp20::span<const uint8_t> data, efi_event callback,
efi_managed_network_sync_completion_token* token) {
if (!token) {
return fit::error(EFI_INVALID_PARAMETER);
}
// Note: the efi_mac_addr struct is a 32 byte array, while real MAC addresses are 6 octets.
// There is no obvious explanation for this discrepancy in the UEFI docs.
// It's much more convenient to deal with normal sized MAC addresses, though,
// so we convert to efi_mac_addr only when necessary.
efi_mac_addr dest = EfiMacAddrFromMacAddr(dst);
efi_mac_addr src = EfiMacAddrFromMacAddr(mac_addr_);
efi_managed_network_transmit_data tx_data = {
.DestinationAddress = &dest,
.SourceAddress = &src,
.ProtocolType = 0x86DD, // IPv6 EtherType
.DataLength = static_cast<uint32_t>(data.size()),
.HeaderLength = 0,
.FragmentCount = 1,
.FragmentTable =
{
{
.FragmentLength = static_cast<uint32_t>(data.size()),
.FragmentBuffer = const_cast<uint8_t*>(data.data()),
},
},
};
*token = {.Event = callback, .Packet = {.TxData = &tx_data}};
if (efi_status res = net_proto_->Transmit(net_proto_.get(), token); res != EFI_SUCCESS) {
return fit::error(res);
}
return fit::ok();
}
} // namespace gigaboot