The dst is a source route (L4 routing)

A FWD frame carries its own route. There is no forwarding table keyed on destinations, no route discovery and no per-flow entry: a hop matches the leading dst route segments against its own children, and where one of its names matches it strips that name’s whole mount run and prepends to src the mount run of the link the frame arrived on (RFC-0004 §B, ADR-0040, ADR-0061).

dst shrinks by exactly what this hop consumed; src grows by exactly how to get back here. The frame that leaves is a different frame from the one that arrived, and the difference is the hop.

Both mounts in the example are one route segment wide — the trivial case, and the clearest to read. The qualified-mount page is the same rule at full width, and it is the general statement; “one route segment per hop” is a property of this wiring and never of the rule.

What to notice

  • The assertion is byte-exact. The frame the hop emits is compared against the frame a client one hop closer would have built from scratch, so the example says “forwarding produced the canonical bytes” rather than “forwarding produced something that decodes plausibly”.

  • Nothing was stored. handles() reports zero bindings after the forward. A forwarder is stateless because the route left with the frame — and so did the return route (reply home is the other half of that sentence).

  • The match is on whole route segments. A dst whose first route segment merely starts with a child’s name is not that child’s traffic; "bb" is not "b". Path bytes are self-delimiting records precisely so a prefix in bytes cannot be mistaken for a prefix in addresses (RFC-0018).

  • The child name is local. Nothing downstream has to agree that this link is called b. That is what lets a caller compose /b/c/sensor out of two nodes’ private names without any global namespace.

  • The link is a recording stub, not a socket. The subject is the BYTES a hop emits, so the example stays synchronous: no threads, no ports, no rendezvous, nothing to flake. A loopback_channel_t or a real transport is the same code with threads in between — see two nodes over a wire.

  • This target needs the FWD net plane. It is built only when LIBTRACER_NET_PLANE is on (the default). Nothing in it is conditional at run time.

Source

  1/*
  2 * SPDX-License-Identifier: Apache-2.0
  3 * SPDX-FileCopyrightText: Copyright 2026 avatarsd LLC
  4 */
  5
  6/**
  7 * @file
  8 * @brief ONE CONCEPT — a `FWD`'s `dst` is a SOURCE ROUTE, and every hop rewrites the frame.
  9 *
 10 * There is no routing table keyed on destinations here and no per-flow state: the whole route
 11 * rides in the frame. A hop matches the leading `dst` route segments against its own children,
 12 * and where one of its names matches it STRIPS that name's whole MOUNT RUN and PREPENDS to `src`
 13 * the mount run of the link the frame arrived on (RFC-0004 §B, ADR-0040, ADR-0061). `dst`
 14 * shrinks by exactly what this hop consumed; `src` grows by exactly how to get back here. The
 15 * frame that leaves is a different frame from the one that arrived, and the difference IS the
 16 * hop.
 17 *
 18 * Both mount runs here are one route segment wide, which is the trivial case and the clearest
 19 * one to read — route_qualified_mount is the same rule with multi-segment mounts, and it is the
 20 * general statement. "One segment per hop" is a property of this wiring, never of the rule.
 21 *
 22 * That is the whole forwarding rule, and it is why a forwarder is stateless: it never has to
 23 * remember a request in order to route the answer, because the answer's route is being built
 24 * on the way out (see route_reply_home for the other half).
 25 *
 26 * The check below is byte-exact rather than field-by-field. The frame this node emits is
 27 * compared against the frame a client one hop closer would have built from scratch — so the
 28 * assertion is "forwarding produced the canonical bytes", not "forwarding produced something
 29 * that decodes plausibly".
 30 *
 31 * The link is a recording stub rather than a socket or a `loopback_channel_t`: the point is the
 32 * BYTES a hop emits, and a stub makes them synchronous and inspectable with no threads and no
 33 * rendezvous. Runs under ctest as `example_route_dst_is_source_route`; returns non-zero on any
 34 * failed check.
 35 */
 36
 37#include <cstddef>
 38#include <cstdint>
 39#include <cstdio>
 40#include <initializer_list>
 41#include <span>
 42#include <string_view>
 43#include <vector>
 44
 45#include "libtracer/fwd_router.hpp"
 46#include "libtracer/tlv_emit.hpp"
 47#include "libtracer/tracer.hpp"
 48
 49namespace {
 50
 51using tr::graph::fwd_op_t;
 52using tr::graph::graph_t;
 53using tr::wire::opt_t;
 54using tr::wire::type_t;
 55
 56/** @brief Report expectation @p what and record a failure on @p ok. */
 57void check(bool& ok, bool cond, const char* what) {
 58    std::printf("  [%s] %s\n", cond ? "ok" : "FAIL", what);
 59    ok = ok && cond;
 60}
 61
 62/**
 63 * @brief A `transport_t` that keeps every frame handed to it — the "wire", made inspectable.
 64 *
 65 * A real link would put these bytes on a socket. Keeping them lets the example assert what a
 66 * hop EMITS, which is the only externally visible thing forwarding does.
 67 */
 68struct recording_link_t : tr::net::transport_t {
 69    std::vector<std::vector<std::byte>> sent; /**< @brief Frames emitted on this link, in order. */
 70    void send(std::span<const std::byte> frame) override {
 71        sent.emplace_back(frame.begin(), frame.end());
 72    }
 73    void send(std::span<const std::span<const std::byte>> iov) override {
 74        std::vector<std::byte> flat;
 75        for (const auto part : iov) flat.insert(flat.end(), part.begin(), part.end());
 76        sent.push_back(std::move(flat));
 77    }
 78};
 79
 80/** @brief A `PATH` TLV over @p segs — RFC-0018 packed segment records, `opt.PL` clear. */
 81std::vector<std::byte> path_tlv(std::initializer_list<std::string_view> segs) {
 82    std::vector<std::byte> body;
 83    for (std::string_view s : segs) (void)tr::wire::emit_path_segment(body, s);
 84    std::vector<std::byte> out;
 85    tr::wire::emit_tlv(out, type_t::PATH, opt_t{}, body);
 86    return out;
 87}
 88
 89/** @brief A one-byte `VALUE` TLV carrying @p v. */
 90std::vector<std::byte> value_tlv(std::uint8_t v) {
 91    const std::byte b{v};
 92    std::vector<std::byte> out;
 93    tr::wire::emit_tlv(out, type_t::VALUE, opt_t{}, std::span<const std::byte>(&b, 1));
 94    return out;
 95}
 96
 97/** @brief `FWD[.pl]{ VALUE op, PATH dst, PATH src, VALUE payload }` (RFC-0004 §B child order). */
 98std::vector<std::byte> fwd_write(std::initializer_list<std::string_view> dst,
 99                                 std::initializer_list<std::string_view> src) {
100    std::vector<std::byte> body = value_tlv(static_cast<std::uint8_t>(fwd_op_t::WRITE));
101    for (const auto& part : {path_tlv(dst), path_tlv(src), value_tlv(0x2A)}) {
102        body.insert(body.end(), part.begin(), part.end());
103    }
104    std::vector<std::byte> out;
105    tr::wire::emit_tlv(out, type_t::FWD, opt_t{.pl = true}, body);
106    return out;
107}
108
109}  // namespace
110
111int main() {
112    bool ok = true;
113    graph_t g;
114    tr::net::fwd_router_t router(g);
115
116    // This node knows exactly one next hop, under the name "b". That name is LOCAL to this
117    // node: nothing downstream has to agree with it, which is what makes the route composable.
118    recording_link_t to_b;
119    if (!router.add_child("b", to_b)) {
120        std::fprintf(stderr, "route_dst_is_source_route: add_child failed — nothing registered\n");
121        return 1;
122    }
123
124    // A client on the link this node calls "cli" asks to write /b/sensor/temp.
125    router.on_frame("cli", fwd_write({"b", "sensor", "temp"}, {}));
126    check(ok, to_b.sent.size() == 1, "the frame went out on the child whose mount run dst named");
127    if (to_b.sent.size() != 1) return 1;
128
129    // The hop, byte for byte: dst lost "b", src gained "cli", the payload is untouched.
130    const std::vector<std::byte> next_hop_would_build = fwd_write({"sensor", "temp"}, {"cli"});
131    check(ok, to_b.sent[0] == next_hop_would_build,
132          "dst shrank by this child's mount run and src grew by the inbound one — byte-exact");
133
134    // Nothing was remembered. The router holds no per-request state at all: the route left
135    // with the frame, and the return route left with it too.
136    check(ok, router.handles().ingress_count() == 0 && router.handles().egress_count() == 0,
137          "the hop stored NOTHING — a forwarder is stateless by construction");
138
139    // The match is on whole route segments, not on a byte prefix: a `dst` whose first route
140    // segment merely starts with a child's name is not that child's traffic.
141    to_b.sent.clear();
142    router.on_frame("cli", fwd_write({"bb", "sensor"}, {}));
143    check(ok, to_b.sent.empty(),
144          "\"bb\" is not \"b\" — a prefix in bytes is not a prefix in route segments");
145
146    std::printf("in : dst=/b/sensor/temp src=/        (%zu bytes)\n",
147                fwd_write({"b", "sensor", "temp"}, {}).size());
148    std::printf("out: dst=/sensor/temp   src=/cli     (%zu bytes)\n", next_hop_would_build.size());
149    return ok ? 0 : 1;
150}

See also: fwd-router module · addressing · communication flows · terminus or forward · multi-hop.