blob: 62a72098035ae3f4bc7d35ba2ca4f55e300f7c82 [file] [log] [blame]
// Copyright 2018 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 <wlan/common/element_splitter.h>
#include <wlan/common/mac_frame.h>
#include <wlan/common/parse_element.h>
#include <wlan/common/write_element.h>
#include <wlan/mlme/mac_header_writer.h>
#include <wlan/mlme/mesh/hwmp.h>
#include <zircon/status.h>
namespace wlan {
static constexpr size_t kInitialTtl = 32;
static constexpr size_t kMaxHwmpFrameSize = 2048;
static constexpr size_t kDot11MeshHWMPactivePathTimeoutTu = 5000;
static constexpr size_t kDot11MeshHWMPmaxPREQretries = 3;
static constexpr size_t kDot11MeshHWMPpreqMinIntervalTu = 100;
static constexpr bool kDot11MeshHWMPtargetOnly = true;
// According to IEEE Std 802.11-2016, 14.10.8.3
bool HwmpSeqnoLessThan(uint32_t a, uint32_t b) {
// Perform subtraction mod 2^32.
//
// If a <= b, then d = abs(a - b).
// If a > b, then d = 2^32 - abs(a - b).
// In either case, 'd' is the distance on the circle that one needs to walk
// if starting from 'a', finishing at 'b' and going in the positive direction.
//
// 'a' precedes 'b' iff this walk distance 'd' is non-zero and is less than
// half the length of the circle. Note there is an edge case where 'a' and 'b'
// are exactly 2^31 apart (i.e., diametrically opposed on the circle). We will
// return false in that case.
uint32_t d = b - a;
// Equivalent to d != 0 && d < (1u << 31);
return static_cast<int32_t>(d) > 0;
}
static uint32_t MaxHwmpSeqno(uint32_t a, uint32_t b) {
return HwmpSeqnoLessThan(a, b) ? b : a;
}
// See IEEE Std 802.11-2016, 14.10.8.4
static bool ShouldUpdatePathToRemoteNode(const MeshPath* path, uint32_t remote_hwmp_seqno,
uint32_t total_metric) {
// No known path: definitely create one
if (!path) { return true; }
// Update if we got a more recent HWMP sequence number
if (path->hwmp_seqno && HwmpSeqnoLessThan(*path->hwmp_seqno, remote_hwmp_seqno)) {
return true;
}
// If the sequence number is unknown or exactly matches what we received,
// update the path if the metric improved
if (!path->hwmp_seqno || *path->hwmp_seqno == remote_hwmp_seqno) {
return path->metric > total_metric;
}
return false;
}
// See IEEE Std 802.11-2016, 14.10.8.4
//
// "Remote" node is either the originator or the target
// (for PREQ and PREP elements, respectively)
//
// Returns the mesh path to the remote node if it was updated and nullptr otherwise.
static const MeshPath* UpdateForwardingInfo(PathTable* path_table, HwmpState* state,
const common::MacAddr& transmitter_addr,
const common::MacAddr& remote_addr,
uint32_t remote_hwmp_seqno, uint32_t metric,
uint32_t last_hop_metric, unsigned hop_count,
uint32_t lifetime_tu) {
zx::time expiration = state->timer_mgr.Now() + WLAN_TU(lifetime_tu);
const MeshPath* ret = nullptr;
// First, update the path information for the originator/destination node
// (see the last two bullet points in 14.10.8.4)
auto path = path_table->GetPath(remote_addr);
if (ShouldUpdatePathToRemoteNode(path, remote_hwmp_seqno, metric + last_hop_metric)) {
zx::time old_expiration = path ? path->expiration_time : zx::time{};
// See Table 14-9, columns titled 'Forwarding information for originator mesh STA'
// and 'Forwarding information for target mesh STA'.
ret = path_table->AddOrUpdatePath(
remote_addr, MeshPath{
.next_hop = transmitter_addr,
.hwmp_seqno = {remote_hwmp_seqno},
.expiration_time = std::max(old_expiration, expiration),
.metric = metric + last_hop_metric,
.hop_count = hop_count + 1,
});
}
// Update the path information for the transmitter if it is different from the
// originator/destination (see the first bullet point in 14.10.8.4).
if (transmitter_addr != remote_addr) {
auto path = path_table->GetPath(transmitter_addr);
if (!path || path->metric > metric + last_hop_metric) {
zx::time old_expiration = path ? path->expiration_time : zx::time{};
auto path_seqno = path ? path->hwmp_seqno : std::optional<uint32_t>{};
path_table->AddOrUpdatePath(transmitter_addr,
MeshPath{
.next_hop = transmitter_addr,
.hwmp_seqno = path_seqno,
.expiration_time = std::max(old_expiration, expiration),
.metric = last_hop_metric,
.hop_count = 1,
});
}
}
return ret;
}
// See IEEE Std 802.11-2016, 14.10.10.3, Case A
static fbl::unique_ptr<Packet> MakeOriginalPrep(const MacHeaderWriter& header_writer,
const common::MacAddr& preq_transmitter_addr,
const common::MacAddr& self_addr,
const common::ParsedPreq& preq,
const MeshPath& path_to_originator,
const HwmpState& state) {
auto packet = GetWlanPacket(kMaxHwmpFrameSize);
if (!packet) { return {}; }
BufferWriter w(*packet);
header_writer.WriteMeshMgmtHeader(&w, kAction, preq_transmitter_addr);
w.Write<ActionFrame>()->category = action::kMesh;
w.Write<MeshActionHeader>()->mesh_action = action::kHwmpMeshPathSelection;
common::WritePrep(
&w,
{
.flags = {},
.hop_count = 0,
.element_ttl = kInitialTtl,
.target_addr = self_addr,
.target_hwmp_seqno = state.our_hwmp_seqno,
},
nullptr, // For now, we don't support external addresses in path selection
{
.lifetime = preq.middle->lifetime,
.metric = 0,
.originator_addr = preq.header->originator_addr,
// Safe because this can only be called after updating the path to the originator
.originator_hwmp_seqno = *path_to_originator.hwmp_seqno,
});
packet->set_len(w.WrittenBytes());
return packet;
}
// See IEEE Std 802.11-2016, 14.10.9.3, Case E
static fbl::unique_ptr<Packet> MakeForwardedPreq(const MacHeaderWriter& mac_header_writer,
const common::ParsedPreq& incoming_preq,
uint32_t last_hop_metric,
Span<const PreqPerTarget> to_forward) {
auto packet = GetWlanPacket(kMaxHwmpFrameSize);
if (!packet) { return {}; }
BufferWriter w(*packet);
mac_header_writer.WriteMeshMgmtHeader(&w, kAction, common::kBcastMac);
w.Write<ActionFrame>()->category = action::kMesh;
w.Write<MeshActionHeader>()->mesh_action = action::kHwmpMeshPathSelection;
common::WritePreq(
&w,
{
.flags = incoming_preq.header->flags,
.hop_count = static_cast<uint8_t>(incoming_preq.header->hop_count + 1),
.element_ttl = static_cast<uint8_t>(incoming_preq.header->element_ttl - 1),
.path_discovery_id = incoming_preq.header->path_discovery_id,
.originator_addr = incoming_preq.header->originator_addr,
.originator_hwmp_seqno = incoming_preq.header->originator_hwmp_seqno,
},
incoming_preq.originator_external_addr,
{
.lifetime = incoming_preq.middle->lifetime,
.metric = incoming_preq.middle->metric + last_hop_metric,
.target_count = static_cast<uint8_t>(to_forward.size()),
},
to_forward);
packet->set_len(w.WrittenBytes());
return packet;
}
// See IEEE Std 802.11-2016, 14.10.9.4.3
static void HandlePreq(const common::MacAddr& preq_transmitter_addr,
const common::MacAddr& self_addr, const common::ParsedPreq& preq,
uint32_t last_hop_metric, const MacHeaderWriter& mac_header_writer,
HwmpState* state, PathTable* path_table, PacketQueue* packets_to_tx) {
// The spec (IEEE Std 802.11-2016, 14.10.9.4.2) suggests that we entirely
// throw out the PREQ if it doesn't meet the "acceptance criteria",
// e.g. if there is no known path to the target. This seems wrong: we could
// still use the information in the PREQ to update the paths to the transmitter
// and the originator. Also, the sentence refers to "THE" target address of the
// PREQ element, whereas in fact a single PREQ element may contain several
// target addresses.
auto path_to_originator =
UpdateForwardingInfo(path_table, state, preq_transmitter_addr, preq.header->originator_addr,
preq.header->originator_hwmp_seqno, preq.middle->metric,
last_hop_metric, preq.header->hop_count, preq.middle->lifetime);
// The spec also suggests another case when we need to handle the PREQ:
// the sequence number in the PREQ matches what we have cached for the target,
// but we haven't seen the (originator_addr, path_discovery_id) pair yet.
// It is unclear yet whether this is really necessary. To implement this,
// we would need to cache (originator_addr, path_discovery_id) pairs, which
// could be avoided otherwise.
if (path_to_originator == nullptr) { return; }
PreqPerTarget to_forward[kPreqMaxTargets];
size_t num_to_forward = 0;
for (auto t : preq.per_target) {
if (t.target_addr == self_addr) {
// TODO(gbonik): Also check if we are a proxy of target_addr
// See IEEE Std 802.11-2016, 14.10.8.3, second bullet point
state->our_hwmp_seqno = MaxHwmpSeqno(state->our_hwmp_seqno, t.target_hwmp_seqno) + 1;
if (auto prep = MakeOriginalPrep(mac_header_writer, preq_transmitter_addr, self_addr,
preq, *path_to_originator, *state)) {
packets_to_tx->Enqueue(std::move(prep));
}
} else {
bool replied = false;
if (!t.flags.target_only()) {
if (auto path_to_target = path_table->GetPath(t.target_addr)) {
// TODO(gbonik): send a PREP on behalf of target (case(c))
replied = true;
}
}
if (preq.header->element_ttl > 1 && num_to_forward < countof(to_forward)) {
to_forward[num_to_forward] = t;
if (replied) {
// See IEEE Std 802.11-2016, 14.10.9.3, case E2: since we already replied
// to the PREQ, we need to set 'Target Only' to true so that other intermediate
// nodes do not reply.
to_forward[num_to_forward].flags.set_target_only(true);
}
num_to_forward += 1;
}
}
// TODO(gbonik): update proxy info if address extension is present (case (a)/(b))
// TODO: Also update precursors if we decide to store them one day (case (d))
// TODO(gobnik): reply to proactive (broadcast) PREQs (case (e))
}
if (num_to_forward > 0) {
if (auto packet = MakeForwardedPreq(mac_header_writer, preq, last_hop_metric,
{to_forward, num_to_forward})) {
packets_to_tx->Enqueue(std::move(packet));
}
}
}
// See IEEE Std 802.11-2016, 14.10.10.3, Case B
static fbl::unique_ptr<Packet> MakeForwardedPrep(const MacHeaderWriter& mac_header_writer,
const common::ParsedPrep& incoming_prep,
uint32_t last_hop_metric,
const MeshPath& path_to_originator) {
auto packet = GetWlanPacket(kMaxHwmpFrameSize);
if (!packet) { return {}; }
BufferWriter w(*packet);
mac_header_writer.WriteMeshMgmtHeader(&w, kAction, path_to_originator.next_hop);
w.Write<ActionFrame>()->category = action::kMesh;
w.Write<MeshActionHeader>()->mesh_action = action::kHwmpMeshPathSelection;
common::WritePrep(
&w,
{
.flags = incoming_prep.header->flags,
.hop_count = static_cast<uint8_t>(incoming_prep.header->hop_count + 1),
.element_ttl = static_cast<uint8_t>(incoming_prep.header->element_ttl - 1),
.target_addr = incoming_prep.header->target_addr,
.target_hwmp_seqno = incoming_prep.header->target_hwmp_seqno,
},
incoming_prep.target_external_addr,
{
.lifetime = incoming_prep.tail->lifetime,
.metric = incoming_prep.tail->metric + last_hop_metric,
.originator_addr = incoming_prep.tail->originator_addr,
.originator_hwmp_seqno = incoming_prep.tail->originator_hwmp_seqno,
});
packet->set_len(w.WrittenBytes());
return packet;
}
// See IEEE Std 802.11-2016, 14.10.10.4.3
static void HandlePrep(const common::MacAddr& prep_transmitter_addr,
const common::MacAddr& self_addr, const common::ParsedPrep& prep,
uint32_t last_hop_metric, const MacHeaderWriter& mac_header_writer,
HwmpState* state, PathTable* path_table, PacketQueue* packets_to_tx) {
auto path_to_target =
UpdateForwardingInfo(path_table, state, prep_transmitter_addr, prep.header->target_addr,
prep.header->target_hwmp_seqno, prep.tail->metric, last_hop_metric,
prep.header->hop_count, prep.tail->lifetime);
if (path_to_target == nullptr) { return; }
auto it = state->state_by_target.find(prep.header->target_addr.ToU64());
if (it != state->state_by_target.end()) {
// Path established successfully: cancel the retry timer
state->timer_mgr.Cancel(it->second.next_attempt);
state->state_by_target.erase(it);
}
if (prep.tail->originator_addr != self_addr && prep.header->element_ttl > 1) {
if (auto path_to_originator = path_table->GetPath(prep.tail->originator_addr)) {
if (auto packet = MakeForwardedPrep(mac_header_writer, prep, last_hop_metric,
*path_to_originator)) {
packets_to_tx->Enqueue(std::move(packet));
}
}
}
// TODO(gbonik): update proxy info if address extension is present (case (b)/(c))
// TODO: Also update precursors if we decide to store them one day (case (d))
}
PacketQueue HandleHwmpAction(Span<const uint8_t> elements,
const common::MacAddr& action_transmitter_addr,
const common::MacAddr& self_addr, uint32_t last_hop_metric,
const MacHeaderWriter& mac_header_writer, HwmpState* state,
PathTable* path_table) {
PacketQueue ret;
for (auto [id, raw_body] : common::ElementSplitter(elements)) {
switch (id) {
case element_id::kPreq:
if (auto preq = common::ParsePreq(raw_body)) {
HandlePreq(action_transmitter_addr, self_addr, *preq, last_hop_metric,
mac_header_writer, state, path_table, &ret);
}
break;
case element_id::kPrep:
if (auto prep = common::ParsePrep(raw_body)) {
HandlePrep(action_transmitter_addr, self_addr, *prep, last_hop_metric,
mac_header_writer, state, path_table, &ret);
}
break;
default:
break;
}
}
return ret;
}
// IEEE Std 802.11-2016, 14.10.9.3, case A, Table 14-10
static fbl::unique_ptr<Packet> MakeOriginalPreq(const common::MacAddr& target_addr,
const common::MacAddr& self_addr,
const MacHeaderWriter& mac_header_writer,
uint32_t our_hwmp_seqno, uint32_t path_discovery_id,
const PathTable& path_table) {
auto packet = GetWlanPacket(kMaxHwmpFrameSize);
if (!packet) { return {}; }
BufferWriter w(*packet);
// IEEE Std 802.11-2016, 14.10.7, case A
mac_header_writer.WriteMeshMgmtHeader(&w, kAction, common::kBcastMac);
w.Write<ActionFrame>()->category = action::kMesh;
w.Write<MeshActionHeader>()->mesh_action = action::kHwmpMeshPathSelection;
const MeshPath* path_to_target = path_table.GetPath(target_addr);
auto target_seqno = path_to_target ? path_to_target->hwmp_seqno : std::optional<uint32_t>();
PreqPerTargetFlags target_flags;
target_flags.set_target_only(kDot11MeshHWMPtargetOnly);
target_flags.set_usn(!target_seqno);
PreqPerTarget per_target = {
.flags = target_flags,
.target_addr = target_addr,
.target_hwmp_seqno = target_seqno.value_or(0),
};
common::WritePreq(&w,
{
.flags = {},
.hop_count = 0,
.element_ttl = kInitialTtl,
.path_discovery_id = path_discovery_id,
.originator_addr = self_addr,
.originator_hwmp_seqno = our_hwmp_seqno,
},
nullptr,
{
.lifetime = kDot11MeshHWMPactivePathTimeoutTu,
.metric = 0,
.target_count = 1,
},
{&per_target, 1});
packet->set_len(w.WrittenBytes());
return packet;
}
zx_status_t EmitOriginalPreq(const common::MacAddr& target_addr,
HwmpState::TargetState* target_state, const common::MacAddr& self_addr,
const MacHeaderWriter& mac_header_writer, HwmpState* state,
const PathTable& path_table, PacketQueue* packets_to_tx) {
zx_status_t status =
state->timer_mgr.Schedule(state->timer_mgr.Now() + WLAN_TU(kDot11MeshHWMPpreqMinIntervalTu),
{target_addr}, &target_state->next_attempt);
if (status != ZX_OK) {
errorf("[hwmp] failed to schedule a timer: %s\n", zx_status_get_string(status));
return ZX_ERR_INTERNAL;
}
uint32_t our_hwmp_seqno = ++state->our_hwmp_seqno;
uint32_t path_discovery_id = ++state->next_path_discovery_id;
target_state->attempts_left -= 1;
if (auto packet = MakeOriginalPreq(target_addr, self_addr, mac_header_writer, our_hwmp_seqno,
path_discovery_id, path_table)) {
packets_to_tx->Enqueue(std::move(packet));
}
return ZX_OK;
}
zx_status_t InitiatePathDiscovery(const common::MacAddr& target_addr,
const common::MacAddr& self_addr,
const MacHeaderWriter& mac_header_writer, HwmpState* state,
const PathTable& path_table, PacketQueue* packets_to_tx) {
auto it = state->state_by_target.find(target_addr.ToU64());
if (it != state->state_by_target.end()) {
it->second.attempts_left = kDot11MeshHWMPmaxPREQretries;
return ZX_OK;
}
it = state->state_by_target
.emplace(target_addr.ToU64(), HwmpState::TargetState{{}, kDot11MeshHWMPmaxPREQretries})
.first;
zx_status_t status = EmitOriginalPreq(target_addr, &it->second, self_addr, mac_header_writer,
state, path_table, packets_to_tx);
if (status != ZX_OK) { state->state_by_target.erase(it); }
return status;
}
zx_status_t HandleHwmpTimeout(const common::MacAddr& self_addr,
const MacHeaderWriter& mac_header_writer, HwmpState* state,
const PathTable& path_table, PacketQueue* packets_to_tx) {
return state->timer_mgr.HandleTimeout([&](auto now, auto event, auto timeout_id) {
auto it = state->state_by_target.find(event.addr.ToU64());
if (it == state->state_by_target.end() || it->second.next_attempt != timeout_id) { return; }
if (it->second.attempts_left == 0) {
// Failed to discover the path after the maximum number of attempts. Clear the state.
state->state_by_target.erase(it);
return;
}
zx_status_t status = EmitOriginalPreq(event.addr, &it->second, self_addr, mac_header_writer,
state, path_table, packets_to_tx);
if (status != ZX_OK) {
errorf("[hwmp] failed to schedule a path request frame transmission: %s\n",
zx_status_get_string(status));
}
});
}
} // namespace wlan