# Flow Trace Semantic Conventions

This document proposes semantic conventions for representing network flow data as traces. The existing network conventions are primarily designed for unidirectional, client/server interactions within an instrumented application. This proposal addresses the need to represent a network flow as a complete, bidirectional conversation, typically observed by a third party (like a network device or eBPF agent).

The core concept of this proposal is to represent each network flow record as a single **Span**. This model elevates traces from purely application-level signals to comprehensive flow spans that capture detailed data about network traffic, creating a new standard for network observability within the OpenTelemetry ecosystem.

***

## Core Concepts

Each network flow record is represented as a single OpenTelemetry **Span**. This "flow span" has the following key characteristics:

* **Span Name**: To clearly distinguish flow spans from application spans, the name SHOULD follow the format `flow_<network.type>_<network.transport>`. For example, a typical TCP flow over IPv4 would be named `flow_ipv4_tcp`.
* **Span Kind**: The Span Kind MUST be `CLIENT`, `SERVER`, or `INTERNAL`. Using `CLIENT` or `SERVER` provides crucial directional context that the generic `INTERNAL` kind lacks, eliminating the need for separate attributes like `flow.initiator: source/destination` or `flow.biflow_direction: initiator/reverseInitiator`.
  * `CLIENT`: Represents the perspective of the connection initiator. An agent infers this when observing an outbound connection that originates from an ephemeral (non-listening) port or through protocol-specific logic.
    * **Example (TCP)**: A host sends a packet from an ephemeral source port (e.g., 54211) to a destination service port (e.g., 443).
    * **Example (ICMP)**: A host sends an ICMP "Echo Request" packet.
    * **Example (loopback)**: A host sends a packet *to* one of its own listening ports (egress direction) — the sender is the client even though a local listening port is involved.
  * `SERVER`: Represents the perspective of the connection receiver. An agent infers this when a local process is actively listening on the matched port **and** the flow direction confirms the agent is on the receiving side of the connection.
    * **Example (TCP)**: An inbound packet arrives at a listening port (e.g., 443); or an outbound packet departs *from* a listening port as a server response.
    * **Example (ICMP)**: A host sends an ICMP "Echo Reply" packet.
  * `INTERNAL`: Used as a fallback when the client/server relationship cannot be determined.

### Flow Direction

While Span Kind provides directional context for traces, metrics do not carry Span Kind. To enable consistent direction signaling across all OpenTelemetry signals (traces, metrics, and logs), this convention defines the `flow.direction` attribute.

The values mirror [IPFIX biflow](https://datatracker.ietf.org/doc/html/rfc5103) concepts:

| Value     | Description                                                                                                                   |
| --------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `forward` | The flow record describes traffic in the forward direction — from the connection initiator to the responder (client → server) |
| `reverse` | The flow record describes traffic in the reverse direction — from the responder back to the initiator (server → client)       |
| `unknown` | The direction could not be reliably determined                                                                                |

The `flow.direction` attribute MUST be consistent with the Span Kind when both are present:

| Span Kind  | `flow.direction` |
| ---------- | ---------------- |
| `CLIENT`   | `forward`        |
| `SERVER`   | `reverse`        |
| `INTERNAL` | `unknown`        |

**Why both Span Kind and `flow.direction`?**

Span Kind is the idiomatic way to express direction in OpenTelemetry traces and enables proper trace visualization in backends. However, when the same flow data is exported as metrics (e.g., for dashboards or alerting), Span Kind is not available. The `flow.direction` attribute ensures that direction information is preserved regardless of signal type, enabling:

* Consistent queries across traces and metrics (e.g., "show all forward flows to this service")
* Metric-based dashboards that distinguish inbound vs. outbound traffic
* Correlation between flow traces and flow metrics using the same direction semantics

### Attribute Namespaces

To ensure clarity, this convention uses and defines specific attribute namespaces:

* **`flow.*`**: Describes the network conversation itself, including metrics and metadata that change over the lifetime of the flow (e.g., flow\.bytes.total, flow\.end\_reason).
* **`source.*` / `destination.*`**: Standard OTel namespaces that describe the two endpoints of the flow, including L3/L4 addresses and enriched metadata like Kubernetes pod names.
* **`network.*`**: The existing OTel namespace for protocol-specific attributes that are static for the duration of the flow (e.g., network.transport, network.type).
* **`tunnel.*`**: Describes tunneling protocols and encapsulation metadata (e.g., tunnel.type, tunnel.id). This is always the outer-most tunnel or encapsulation.
* **`process.*` / `container.*`**: Existing OTel namespaces used to identify the host process or container associated with the flow's socket.

> **Note on `client.*` / `server.*`**: This convention intentionally omits the standard OTel `client.*` / `server.*` attributes. Flow telemetry is symmetric and observed by a third party — the source and destination of a packet are well-defined by `source.*` and `destination.*`, and the observer's role is captured by Span Kind. Attempting to resolve which endpoint is the "client" or "server" is opportunistic at best and counter-intuitive to how network flow data (NetFlow, IPFIX, eBPF) is typically consumed. Users should treat `source.*` / `destination.*` as the canonical endpoint identifiers.

The `flow.*` namespace is critical for creating a clear semantic distinction. It separates attributes of a flow — a dynamic conversation between two endpoints over time — from attributes of a network entity, like a physical interface, whose properties are generally static. Overloading the existing network.\* namespace with dynamic flow concepts would create ambiguity.

### Why `source.k8s.*` Instead of `k8s.source.*`?

This convention uses `source.k8s.*` / `destination.k8s.*` (e.g., `source.k8s.pod.name`) rather than `k8s.source.*` / `k8s.destination.*`. This is a deliberate design choice driven by the nature of network flow data:

1. **The entity being described is the flow endpoint, not Kubernetes itself.** In flow telemetry, the primary entity is the connection between two endpoints. Each endpoint has many attributes: an IP address, a port, and potentially Kubernetes metadata (pod name, namespace, etc.). Grouping all attributes of an endpoint under its directional prefix (`source.*` or `destination.*`) keeps related data together semantically. The question "what do I know about the source?" is answered by querying `source.*`, yielding address, port, and all k8s enrichment in one logical group.
2. **Symmetry of bidirectional flows.** Unlike client/server metrics where telemetry is recorded from a single perspective, network flow observability (especially eBPF-based) captures both directions of a conversation symmetrically. There is no privileged "recording side." The `source`/`destination` prefixes establish a consistent frame of reference for the entire flow record, and all enrichment attributes naturally belong under that frame.
3. **Consistency with networking industry conventions.** Traditional flow protocols (NetFlow, IPFIX, sFlow) and eBPF-based tools universally structure directional metadata with the direction first. Using `source.k8s.*` aligns with these patterns, making the schema intuitive for network engineers.
4. **Query ergonomics and grouping.** Placing the directional prefix first enables efficient queries like `source.k8s.*` to retrieve all Kubernetes context for one side of the flow. If the hierarchy were inverted (`k8s.source.*`), querying "all source endpoint attributes" would require combining `source.address`, `source.port`, and `k8s.source.*` — three separate prefix patterns instead of one.
5. **Avoiding OTel resource attribute ambiguity.** Standard OTel `k8s.*` attributes (e.g., `k8s.pod.name`) describe the resource where telemetry originates — typically the observing agent's own pod. For flow data, we need to describe *two* remote endpoints, neither of which is necessarily the agent itself. Using `source.k8s.*` / `destination.k8s.*` clearly distinguishes flow endpoint metadata from resource-level attributes, preventing confusion about which entity is being described.

This pattern intentionally diverges from the OTel `client.*`/`server.*` [guidance](https://opentelemetry.io/docs/specs/semconv/general/naming/#client-and-server-metrics), which assumes telemetry recorded from a single side's perspective. For symmetric, connection-centric observability, direction-first prefixing provides clearer semantics.

A simple litmus test can help:

* If an attribute's value can change during the lifetime of a flow (like a byte count), it belongs in the `flow.\*` namespace (e.g., `flow.bytes.total`).
* If an attribute's value is static for the duration of the flow (like the transport protocol), it belongs in the `network.\*` namespace (e.g., network.transport).

This separation prevents ambiguity. For instance, an attribute like `network.byte_count` could be misinterpreted as the total bytes for an entire network interface, whereas `flow.bytes.total` clearly refers to the byte count for a specific five-tuple flow. This makes the resulting telemetry data more accurate and easier to query.

***

## Requirement Level Legend

The following symbols are used in the "Required" column to indicate [OpenTelemetry attribute requirement levels](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/):

<table><thead><tr><th width="99.93359375">Symbol</th><th width="199.79296875">Requirement Level</th><th>Description</th></tr></thead><tbody><tr><td>✓</td><td>Required</td><td>All instrumentations MUST populate the attribute</td></tr><tr><td>?</td><td>Conditionally Required</td><td>MUST populate when the specified condition is satisfied</td></tr><tr><td>~</td><td>Recommended</td><td>SHOULD add by default if readily available and efficient</td></tr><tr><td>○</td><td>Opt-In</td><td>SHOULD populate only if user configures instrumentation to do so</td></tr></tbody></table>

## General Flow Attributes

> Note on Timestamps: The span's standard `start_time_unix_nano` and `end_time_unix_nano` fields are used to mark the beginning and end of the flow span's observation window. These are analogous to the `flowStart*` and `flowEnd*` fields in IPFIX records and are not duplicated as attributes.

<table data-full-width="true"><thead><tr><th>Proposed Field Name</th><th width="96.08203125">Data Type</th><th>Description</th><th>Notes / Decisions</th><th width="96.21875">Std OTel</th><th width="96.0078125">Required</th></tr></thead><tbody><tr><td><code>flow.community_id</code></td><td><code>string</code></td><td>The Community ID hash of the flow's five-tuple.</td><td>A common way to identify a network flow across different monitoring points.</td><td></td><td>✓</td></tr><tr><td><code>flow.direction</code></td><td><code>string</code></td><td>The inferred direction of the flow from the observer's perspective.</td><td>One of: <code>forward</code>, <code>reverse</code>, or <code>unknown</code>. Mirrors IPFIX biflow concepts. See <a href="https://github.com/elastiflow/mermin/blob/main/docs/.gitbook/includes/semantic-conventions.md#flow-direction">Flow Direction</a> for details.</td><td></td><td>✓</td></tr><tr><td><code>flow.connection.state</code></td><td><code>string</code></td><td>The state of the connection (e.g., TCP state) at the time the flow was generated.</td><td>For TCP, this would be one of the standard states like <code>established</code>, <code>time_wait</code>, etc. Similar to network.connection.state but from a flow perspective.</td><td></td><td>? TCP only</td></tr><tr><td><code>flow.end_reason</code></td><td><code>string</code></td><td>The reason the flow record was exported (e.g., <code>active_timeout</code>, <code>end_of_flow_detected</code>).</td><td>Stored as a human-readable text enum based on <a href="https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-flow-end-reason">ipfix end reason</a>.</td><td></td><td>✓</td></tr></tbody></table>

## L2-L4 Attributes

<table data-full-width="true"><thead><tr><th>Proposed Field Name</th><th width="95.9921875">Data Type</th><th>Description</th><th>Notes / Decisions</th><th width="96.04296875">Std OTel</th><th width="95.78125">Required</th></tr></thead><tbody><tr><td><code>source.address</code></td><td><code>string</code></td><td>Source IP address.</td><td></td><td>✓</td><td>✓</td></tr><tr><td><code>source.port</code></td><td><code>long</code></td><td>Source port number.</td><td></td><td>✓</td><td>✓</td></tr><tr><td><code>destination.address</code></td><td><code>string</code></td><td>Destination IP address.</td><td></td><td>✓</td><td>✓</td></tr><tr><td><code>destination.port</code></td><td><code>long</code></td><td>Destination port number.</td><td></td><td>✓</td><td>✓</td></tr><tr><td><code>network.transport</code></td><td><code>string</code></td><td>The transport protocol of the flow (e.g., <code>tcp</code>, <code>udp</code>).</td><td>Lowercase IANA protocol name string.</td><td>✓</td><td>✓</td></tr><tr><td><code>network.type</code></td><td><code>string</code></td><td>The network protocol type (EtherType) of the flow (e.g., <code>ipv4</code>, <code>ipv6</code>).</td><td></td><td>✓</td><td>✓</td></tr><tr><td><code>network.interface.index</code></td><td><code>long</code></td><td>The index value of the network interface where the flow was observed.</td><td></td><td>✓</td><td>~</td></tr><tr><td><code>network.interface.name</code></td><td><code>string</code></td><td>The name of the network interface where the flow was observed.</td><td></td><td>✓</td><td>~</td></tr><tr><td><code>network.interface.mac</code></td><td><code>string</code></td><td>Source MAC address.</td><td>Lowercased, 6 hexadecimal values separated by colons.</td><td></td><td>~</td></tr><tr><td><code>flow.ip.dscp.id</code></td><td><code>long</code></td><td>Differentiated Services Code Point (DSCP) value from the IP header (forward direction).</td><td>First packet per direction per export interval. Reset between exports.</td><td></td><td>~</td></tr><tr><td><code>flow.ip.dscp.name</code></td><td><code>string</code></td><td>Lowercase DSCP standard name (forward direction).</td><td>First packet per direction per export interval. Reset between exports.</td><td></td><td>~</td></tr><tr><td><code>flow.ip.ecn.id</code></td><td><code>long</code></td><td>Explicit Congestion Notification (ECN) value from the IP header (forward direction).</td><td>First packet per direction per export interval. Reset between exports.</td><td></td><td>~</td></tr><tr><td><code>flow.ip.ecn.name</code></td><td><code>string</code></td><td>Lowercase ECN standard name (forward direction).</td><td>First packet per direction per export interval. Reset between exports.</td><td></td><td>~</td></tr><tr><td><code>flow.ip.ttl</code></td><td><code>long</code></td><td>Time to Live (IPv4) or Hop Limit (IPv6) value (forward direction).</td><td>First packet per direction per export interval. Reset between exports.</td><td></td><td>~</td></tr><tr><td><code>flow.ip.flow_label</code></td><td><code>long</code></td><td>Flow Label from the IPv6 header (forward direction).</td><td>First packet per direction per export interval. Reset between exports.</td><td></td><td>~</td></tr><tr><td><code>flow.reverse.ip.dscp.id</code></td><td><code>long</code></td><td>Differentiated Services Code Point (DSCP) value from the IP header (reverse direction).</td><td>First packet per direction per export interval. Reset between exports.</td><td></td><td>~</td></tr><tr><td><code>flow.reverse.ip.dscp.name</code></td><td><code>string</code></td><td>Lowercase DSCP standard name (reverse direction).</td><td>First packet per direction per export interval. Reset between exports.</td><td></td><td>~</td></tr><tr><td><code>flow.reverse.ip.ecn.id</code></td><td><code>long</code></td><td>Explicit Congestion Notification (ECN) value from the IP header (reverse direction).</td><td>First packet per direction per export interval. Reset between exports.</td><td></td><td>~</td></tr><tr><td><code>flow.reverse.ip.ecn.name</code></td><td><code>string</code></td><td>Lowercase ECN standard name (reverse direction).</td><td>First packet per direction per export interval. Reset between exports.</td><td></td><td>~</td></tr><tr><td><code>flow.reverse.ip.ttl</code></td><td><code>long</code></td><td>Time to Live (IPv4) or Hop Limit (IPv6) value (reverse direction).</td><td>First packet per direction per export interval. Reset between exports.</td><td></td><td>~</td></tr><tr><td><code>flow.reverse.ip.flow_label</code></td><td><code>long</code></td><td>Flow Label from the IPv6 header (reverse direction).</td><td>First packet per direction per export interval. Reset between exports.</td><td></td><td>~</td></tr><tr><td><code>flow.icmp.type.id</code></td><td><code>long</code></td><td>ICMP message type id.</td><td>Based on IANA standard names.</td><td></td><td>~</td></tr><tr><td><code>flow.icmp.type.name</code></td><td><code>string</code></td><td>Lowercase ICMP message type name.</td><td>Based on IANA standard names.</td><td></td><td>~</td></tr><tr><td><code>flow.icmp.code.id</code></td><td><code>long</code></td><td>ICMP message code id.</td><td>Based on IANA standard names.</td><td></td><td>~</td></tr><tr><td><code>flow.icmp.code.name</code></td><td><code>string</code></td><td>ICMP message code name.</td><td>Based on IANA standard names.</td><td></td><td>~</td></tr><tr><td><code>flow.reverse.icmp.type.id</code></td><td><code>long</code></td><td>ICMP message type id (reverse direction).</td><td>First packet per direction per export interval. Reset between exports.</td><td></td><td>~</td></tr><tr><td><code>flow.reverse.icmp.type.name</code></td><td><code>string</code></td><td>Lowercase ICMP message type name (reverse direction).</td><td>First packet per direction per export interval. Reset between exports.</td><td></td><td>~</td></tr><tr><td><code>flow.reverse.icmp.code.id</code></td><td><code>long</code></td><td>ICMP message code id (reverse direction).</td><td>First packet per direction per export interval. Reset between exports.</td><td></td><td>~</td></tr><tr><td><code>flow.reverse.icmp.code.name</code></td><td><code>string</code></td><td>ICMP message code name (reverse direction).</td><td>First packet per direction per export interval. Reset between exports.</td><td></td><td>~</td></tr><tr><td><code>flow.tcp.flags.bits</code></td><td><code>long</code></td><td>The integer representation of all TCP flags seen during the observation window.</td><td>Accumulated across entire flow lifetime (never reset).</td><td></td><td>~</td></tr><tr><td><code>flow.tcp.flags.tags</code></td><td><code>string[]</code></td><td>An array of TCP flag names (e.g., <code>["SYN", "ACK"]</code>) for all flags set.</td><td>Accumulated across entire flow lifetime (never reset).</td><td></td><td>~</td></tr><tr><td><code>flow.reverse.tcp.flags.bits</code></td><td><code>long</code></td><td>The integer representation of all TCP flags seen in reverse direction.</td><td>Accumulated across entire flow lifetime (never reset).</td><td></td><td>~</td></tr><tr><td><code>flow.reverse.tcp.flags.tags</code></td><td><code>string[]</code></td><td>An array of TCP flag names for reverse direction (e.g., <code>["SYN", "ACK"]</code>).</td><td>Accumulated across entire flow lifetime (never reset).</td><td></td><td>~</td></tr></tbody></table>

## Flow Metrics

<table data-full-width="true"><thead><tr><th>Proposed Field Name</th><th width="95.57421875">Data Type</th><th>Description</th><th>Notes / Decisions</th><th width="95.93359375">Std OTel</th><th width="95.9453125">Required</th></tr></thead><tbody><tr><td><code>flow.bytes.delta</code></td><td><code>long</code></td><td>Number of bytes observed in the last measurement interval for the flow.</td><td></td><td></td><td>✓</td></tr><tr><td><code>flow.bytes.total</code></td><td><code>long</code></td><td>Total number of bytes observed for this flow since its start.</td><td>The term <code>bytes</code> is preferred over <code>octets</code> for clarity.</td><td></td><td>~</td></tr><tr><td><code>flow.packets.delta</code></td><td><code>long</code></td><td>Number of packets observed in the last measurement interval for the flow.</td><td></td><td></td><td>✓</td></tr><tr><td><code>flow.packets.total</code></td><td><code>long</code></td><td>Total number of packets observed for this flow since its start.</td><td></td><td></td><td>~</td></tr><tr><td><code>flow.reverse.bytes.delta</code></td><td><code>long</code></td><td>Delta bytes in the reverse direction of the flow.</td><td></td><td></td><td>✓</td></tr><tr><td><code>flow.reverse.bytes.total</code></td><td><code>long</code></td><td>Total bytes in the reverse direction of the flow since its start.</td><td></td><td></td><td>~</td></tr><tr><td><code>flow.reverse.packets.delta</code></td><td><code>long</code></td><td>Delta packets in the reverse direction of the flow.</td><td></td><td></td><td>✓</td></tr><tr><td><code>flow.reverse.packets.total</code></td><td><code>long</code></td><td>Total packets in the reverse direction of the flow since its start.</td><td></td><td></td><td>~</td></tr></tbody></table>

## Performance Metrics

Time-based metrics calculated for the flow, stored in nanoseconds (`ns`).

<table data-full-width="true"><thead><tr><th>Proposed Field Name</th><th width="95.625">Data Type</th><th>Description</th><th>Notes / Decisions</th><th width="96.4140625">Std OTel</th><th width="95.51953125">Required</th></tr></thead><tbody><tr><td><code>flow.tcp.handshake.latency</code></td><td><code>long</code></td><td>The latency of the first part of the TCP handshake (SYN to SYN/ACK), from the <strong>client's perspective</strong>. (Server network delay)</td><td>Unit: <code>ns</code>.</td><td></td><td>~</td></tr><tr><td><code>flow.tcp.svc.latency</code></td><td><code>long</code></td><td>The application/service processing time, as measured on the <strong>server side</strong>.</td><td>Unit: <code>ns</code>.</td><td></td><td>~</td></tr><tr><td><code>flow.tcp.svc.jitter</code></td><td><code>long</code></td><td>The jitter of the application/service processing time, as measured on the <strong>server side</strong>.</td><td>Unit: <code>ns</code>.</td><td></td><td>~</td></tr><tr><td><code>flow.tcp.rndtrip.latency</code></td><td><code>long</code></td><td>The full round-trip time (client to server + app to client), from the <strong>client's perspective</strong>.</td><td>Unit: <code>ns</code>.</td><td></td><td>~</td></tr><tr><td><code>flow.tcp.rndtrip.jitter</code></td><td><code>long</code></td><td>The jitter of the full round-trip time, from the <strong>client's perspective</strong>.</td><td>Unit: <code>ns</code>.</td><td></td><td>~</td></tr></tbody></table>

## Tunnel & Ip-in-Ip & IPSec Attributes

<table data-full-width="true"><thead><tr><th>Proposed Field Name</th><th width="95.73828125">Data Type</th><th>Description</th><th>Notes / Decisions</th><th width="96.20703125">Std OTel</th><th width="95.76953125">Required</th></tr></thead><tbody><tr><td><code>flow.ipsec.ah.spi</code></td><td><code>long</code></td><td>Security Parameters Index for AH headers.</td><td>SPI from the outermost header (after a tunnel)</td><td></td><td>○</td></tr><tr><td><code>flow.ipsec.esp.spi</code></td><td><code>long</code></td><td>Security Parameters Index for ESP headers.</td><td>SPI from the outermost header (after a tunnel)</td><td></td><td>○</td></tr><tr><td><code>flow.ipsec.sender_index</code></td><td><code>long</code></td><td>The sender index from a WireGuard header.</td><td></td><td></td><td>○</td></tr><tr><td><code>flow.ipsec.receiver_index</code></td><td><code>long</code></td><td>The receiver index from a WireGuard header.</td><td></td><td></td><td>○</td></tr><tr><td><code>ipip.network.type</code></td><td><code>string</code></td><td>The network protocol type (EtherType) of the flow (e.g., <code>ipv4</code>, <code>ipv6</code>).</td><td></td><td></td><td>○</td></tr><tr><td><code>ipip.network.transport</code></td><td><code>string</code></td><td>The transport protocol of the encapsulated flow (e.g., <code>tcp</code>, <code>udp</code>).</td><td></td><td></td><td>○</td></tr><tr><td><code>ipip.source.address</code></td><td><code>string</code></td><td>The source IP address of the tunnel's outer header.</td><td>Ip-in-Ip is always the outermost header.</td><td></td><td>○</td></tr><tr><td><code>ipip.destination.address</code></td><td><code>string</code></td><td>The destination IP address of the tunnel's outer header.</td><td></td><td></td><td>○</td></tr><tr><td><code>ipip.bytes.delta</code></td><td><code>long</code></td><td>Number of outer header bytes observed in the last measurement interval.</td><td></td><td></td><td>? IP-in-IP present</td></tr><tr><td><code>ipip.bytes.total</code></td><td><code>long</code></td><td>Total number of outer header bytes observed since flow start.</td><td>The term <code>bytes</code> is preferred over <code>octets</code> for clarity.</td><td></td><td>~</td></tr><tr><td><code>ipip.reverse.bytes.delta</code></td><td><code>long</code></td><td>Delta outer header bytes in the reverse direction.</td><td></td><td></td><td>? IP-in-IP present</td></tr><tr><td><code>ipip.reverse.bytes.total</code></td><td><code>long</code></td><td>Total outer header bytes in the reverse direction since flow start.</td><td></td><td></td><td>~</td></tr><tr><td><code>tunnel.type</code></td><td><code>string</code></td><td>The type of tunnel protocol (e.g., <code>vxlan</code>, <code>geneve</code>, <code>gre</code>).</td><td>Tunnel is always the outermost header.</td><td></td><td>○</td></tr><tr><td><code>tunnel.network.interface.mac</code></td><td><code>string</code></td><td>Source MAC address of tunnel.</td><td>Lowercased, 6 hexadecimal values separated by colons.</td><td></td><td>~</td></tr><tr><td><code>tunnel.network.type</code></td><td><code>string</code></td><td>The network protocol type (EtherType) of the flow (e.g., <code>ipv4</code>, <code>ipv6</code>).</td><td></td><td></td><td>○</td></tr><tr><td><code>tunnel.network.transport</code></td><td><code>string</code></td><td>The transport protocol of the flow (e.g., <code>tcp</code>, <code>udp</code>).</td><td></td><td></td><td>○</td></tr><tr><td><code>tunnel.source.address</code></td><td><code>string</code></td><td>The source IP address of the tunnel's outer header.</td><td></td><td></td><td>○</td></tr><tr><td><code>tunnel.source.port</code></td><td><code>long</code></td><td>The source port of the tunnel's outer header.</td><td></td><td></td><td>○</td></tr><tr><td><code>tunnel.destination.address</code></td><td><code>string</code></td><td>The destination IP address of the tunnel's outer header.</td><td></td><td></td><td>○</td></tr><tr><td><code>tunnel.destination.port</code></td><td><code>long</code></td><td>The destination port of the tunnel's outer header.</td><td></td><td></td><td>○</td></tr><tr><td><code>tunnel.id</code></td><td><code>string</code></td><td>The identifier for the tunnel (e.g., VNI for VXLAN/Geneve, Key ID for GRE).</td><td></td><td></td><td>○</td></tr><tr><td><code>tunnel.ipsec.ah.spi</code></td><td><code>long</code></td><td>Security Parameters Index for AH headers.</td><td>SPI from the outermost header.</td><td></td><td>○</td></tr><tr><td><code>tunnel.ipsec.esp.spi</code></td><td><code>long</code></td><td>Security Parameters Index for ESP headers.</td><td>SPI from the outermost header.</td><td></td><td>○</td></tr><tr><td><code>tunnel.bytes.delta</code></td><td><code>long</code></td><td>Number of tunnel overhead bytes observed in the last measurement interval.</td><td></td><td></td><td>? tunnel present</td></tr><tr><td><code>tunnel.bytes.total</code></td><td><code>long</code></td><td>Total number of tunnel overhead bytes observed since flow start.</td><td>The term <code>bytes</code> is preferred over <code>octets</code> for clarity.</td><td></td><td>~</td></tr><tr><td><code>tunnel.reverse.bytes.delta</code></td><td><code>long</code></td><td>Delta tunnel overhead bytes in the reverse direction.</td><td></td><td></td><td>? tunnel present</td></tr><tr><td><code>tunnel.reverse.bytes.total</code></td><td><code>long</code></td><td>Total tunnel overhead bytes in the reverse direction since flow start.</td><td></td><td></td><td>~</td></tr></tbody></table>

## Kubernetes Attributes

> **Note:** These attributes use `source.k8s.*` / `destination.k8s.*` prefixes rather than standard OTel `k8s.*` attributes. See [Why `source.k8s.*` Instead of `k8s.source.*`?](https://github.com/elastiflow/mermin/blob/main/docs/.gitbook/includes/semantic-conventions.md#why-sourcek8s-instead-of-k8ssource) for the rationale.

<table data-full-width="true"><thead><tr><th>Proposed Field Name</th><th width="96.2421875">Data Type</th><th>Description</th><th>Notes / Decisions</th><th width="96.04296875">Std OTel</th><th width="96.25">Required</th></tr></thead><tbody><tr><td><code>source.k8s.cluster.name</code></td><td><code>string</code></td><td>The name of the Kubernetes cluster for the source.</td><td></td><td>~</td><td>~</td></tr><tr><td><code>source.k8s.cluster.uid</code></td><td><code>string</code></td><td>The UID of the Kubernetes cluster for the source.</td><td></td><td>~</td><td>○</td></tr><tr><td><code>source.k8s.node.name</code></td><td><code>string</code></td><td>The name of the Kubernetes Node for the source.</td><td></td><td>~</td><td>~</td></tr><tr><td><code>source.k8s.node.uid</code></td><td><code>string</code></td><td>The UID of the Kubernetes Node for the source.</td><td></td><td>~</td><td>○</td></tr><tr><td><code>source.k8s.node.annotations.&#x3C;key></code></td><td><code>string</code></td><td>Dynamic annotations from the source Node.</td><td>Flattened map.</td><td>~</td><td>○</td></tr><tr><td><code>source.k8s.namespace.name</code></td><td><code>string</code></td><td>The name of the Kubernetes Namespace for the source.</td><td></td><td>~</td><td>~</td></tr><tr><td><code>source.k8s.pod.name</code></td><td><code>string</code></td><td>The name of the Kubernetes Pod for the source.</td><td></td><td>~</td><td>~</td></tr><tr><td><code>source.k8s.pod.uid</code></td><td><code>string</code></td><td>The UID of the Kubernetes Pod for the source.</td><td></td><td>~</td><td>○</td></tr><tr><td><code>source.k8s.pod.annotations.&#x3C;key></code></td><td><code>string</code></td><td>Dynamic annotations from the source Pod.</td><td>Flattened map.</td><td>~</td><td>○</td></tr><tr><td><code>source.k8s.container.name</code></td><td><code>string</code></td><td>The name of the Container from Pod specification.</td><td></td><td>~</td><td>~</td></tr><tr><td><code>source.k8s.deployment.name</code></td><td><code>string</code></td><td>The name of the Kubernetes Deployment for the source.</td><td></td><td>~</td><td>~</td></tr><tr><td><code>source.k8s.deployment.uid</code></td><td><code>string</code></td><td>The UID of the Kubernetes Deployment for the source.</td><td></td><td>~</td><td>○</td></tr><tr><td><code>source.k8s.deployment.annotations.&#x3C;key></code></td><td><code>string</code></td><td>Dynamic annotations from the source Deployment.</td><td>Flattened map.</td><td>~</td><td>○</td></tr><tr><td><code>source.k8s.replicaset.name</code></td><td><code>string</code></td><td>The name of the Kubernetes ReplicaSet for the source.</td><td></td><td>~</td><td>~</td></tr><tr><td><code>source.k8s.replicaset.uid</code></td><td><code>string</code></td><td>The UID of the Kubernetes ReplicaSet for the source.</td><td></td><td>~</td><td>○</td></tr><tr><td><code>source.k8s.replicaset.annotations.&#x3C;key></code></td><td><code>string</code></td><td>Dynamic annotations from the source ReplicaSet.</td><td>Flattened map.</td><td>~</td><td>○</td></tr><tr><td><code>source.k8s.statefulset.name</code></td><td><code>string</code></td><td>The name of the Kubernetes StatefulSet for the source.</td><td></td><td>~</td><td>~</td></tr><tr><td><code>source.k8s.statefulset.uid</code></td><td><code>string</code></td><td>The UID of the Kubernetes StatefulSet for the source.</td><td></td><td>~</td><td>○</td></tr><tr><td><code>source.k8s.statefulset.annotations.&#x3C;key></code></td><td><code>string</code></td><td>Dynamic annotations from the source StatefulSet.</td><td>Flattened map.</td><td>~</td><td>○</td></tr><tr><td><code>source.k8s.daemonset.name</code></td><td><code>string</code></td><td>The name of the Kubernetes DaemonSet for the source.</td><td></td><td>~</td><td>~</td></tr><tr><td><code>source.k8s.daemonset.uid</code></td><td><code>string</code></td><td>The UID of the Kubernetes DaemonSet for the source.</td><td></td><td>~</td><td>○</td></tr><tr><td><code>source.k8s.daemonset.annotations.&#x3C;key></code></td><td><code>string</code></td><td>Dynamic annotations from the source DaemonSet.</td><td>Flattened map.</td><td>~</td><td>○</td></tr><tr><td><code>source.k8s.job.name</code></td><td><code>string</code></td><td>The name of the Kubernetes Job for the source.</td><td></td><td>~</td><td>~</td></tr><tr><td><code>source.k8s.job.uid</code></td><td><code>string</code></td><td>The UID of the Kubernetes Job for the source.</td><td></td><td>~</td><td>○</td></tr><tr><td><code>source.k8s.job.annotations.&#x3C;key></code></td><td><code>string</code></td><td>Dynamic annotations from the source Job.</td><td>Flattened map.</td><td>~</td><td>○</td></tr><tr><td><code>source.k8s.cronjob.name</code></td><td><code>string</code></td><td>The name of the Kubernetes CronJob for the source.</td><td></td><td>~</td><td>~</td></tr><tr><td><code>source.k8s.cronjob.uid</code></td><td><code>string</code></td><td>The UID of the Kubernetes CronJob for the source.</td><td></td><td>~</td><td>○</td></tr><tr><td><code>source.k8s.cronjob.annotations.&#x3C;key></code></td><td><code>string</code></td><td>Dynamic annotations from the source CronJob.</td><td>Flattened map.</td><td>~</td><td>○</td></tr><tr><td><code>source.k8s.service.name</code></td><td><code>string</code></td><td>The name of the Kubernetes Service for the source.</td><td></td><td>~</td><td>~</td></tr><tr><td><code>source.k8s.service.uid</code></td><td><code>string</code></td><td>The UID of the Kubernetes Service for the source.</td><td></td><td>~</td><td>○</td></tr><tr><td><code>source.k8s.service.annotations.&#x3C;key></code></td><td><code>string</code></td><td>Dynamic annotations from the source Service.</td><td>Flattened map.</td><td>~</td><td>○</td></tr><tr><td><code>destination.k8s.cluster.name</code></td><td><code>string</code></td><td>The name of the Kubernetes cluster for the destination.</td><td></td><td>~</td><td>~</td></tr><tr><td><code>destination.k8s.cluster.uid</code></td><td><code>string</code></td><td>The UID of the Kubernetes cluster for the destination.</td><td></td><td>~</td><td>○</td></tr><tr><td><code>destination.k8s.node.name</code></td><td><code>string</code></td><td>The name of the Kubernetes Node for the destination.</td><td></td><td>~</td><td>~</td></tr><tr><td><code>destination.k8s.node.uid</code></td><td><code>string</code></td><td>The UID of the Kubernetes Node for the destination.</td><td></td><td>~</td><td>○</td></tr><tr><td><code>destination.k8s.node.annotations.&#x3C;key></code></td><td><code>string</code></td><td>Dynamic annotations from the destination Node.</td><td>Flattened map.</td><td>~</td><td>○</td></tr><tr><td><code>destination.k8s.namespace.name</code></td><td><code>string</code></td><td>The name of the Kubernetes Namespace for the destination.</td><td></td><td>~</td><td>~</td></tr><tr><td><code>destination.k8s.pod.name</code></td><td><code>string</code></td><td>The name of the Kubernetes Pod for the destination.</td><td></td><td>~</td><td>~</td></tr><tr><td><code>destination.k8s.pod.uid</code></td><td><code>string</code></td><td>The UID of the Kubernetes Pod for the destination.</td><td></td><td>~</td><td>○</td></tr><tr><td><code>destination.k8s.pod.annotations.&#x3C;key></code></td><td><code>string</code></td><td>Dynamic annotations from the destination Pod.</td><td>Flattened map.</td><td>~</td><td>○</td></tr><tr><td><code>destination.k8s.container.name</code></td><td><code>string</code></td><td>The name of the Container from Pod specification.</td><td></td><td>~</td><td>~</td></tr><tr><td><code>destination.k8s.deployment.name</code></td><td><code>string</code></td><td>The name of the Kubernetes Deployment for the destination.</td><td></td><td>~</td><td>~</td></tr><tr><td><code>destination.k8s.deployment.uid</code></td><td><code>string</code></td><td>The UID of the Kubernetes Deployment for the destination.</td><td></td><td>~</td><td>○</td></tr><tr><td><code>destination.k8s.deployment.annotations.&#x3C;key></code></td><td><code>string</code></td><td>Dynamic annotations from the destination Deployment.</td><td>Flattened map.</td><td>~</td><td>○</td></tr><tr><td><code>destination.k8s.replicaset.name</code></td><td><code>string</code></td><td>The name of the Kubernetes ReplicaSet for the destination.</td><td></td><td>~</td><td>~</td></tr><tr><td><code>destination.k8s.replicaset.uid</code></td><td><code>string</code></td><td>The UID of the Kubernetes ReplicaSet for the destination.</td><td></td><td>~</td><td>○</td></tr><tr><td><code>destination.k8s.replicaset.annotations.&#x3C;key></code></td><td><code>string</code></td><td>Dynamic annotations from the destination ReplicaSet.</td><td>Flattened map.</td><td>~</td><td>○</td></tr><tr><td><code>destination.k8s.statefulset.name</code></td><td><code>string</code></td><td>The name of the Kubernetes StatefulSet for the destination.</td><td></td><td>~</td><td>~</td></tr><tr><td><code>destination.k8s.statefulset.uid</code></td><td><code>string</code></td><td>The UID of the Kubernetes StatefulSet for the destination.</td><td></td><td>~</td><td>○</td></tr><tr><td><code>destination.k8s.statefulset.annotations.&#x3C;key></code></td><td><code>string</code></td><td>Dynamic annotations from the destination StatefulSet.</td><td>Flattened map.</td><td>~</td><td>○</td></tr><tr><td><code>destination.k8s.daemonset.name</code></td><td><code>string</code></td><td>The name of the Kubernetes DaemonSet for the destination.</td><td></td><td>~</td><td>~</td></tr><tr><td><code>destination.k8s.daemonset.uid</code></td><td><code>string</code></td><td>The UID of the Kubernetes DaemonSet for the destination.</td><td></td><td>~</td><td>○</td></tr><tr><td><code>destination.k8s.daemonset.annotations.&#x3C;key></code></td><td><code>string</code></td><td>Dynamic annotations from the destination DaemonSet.</td><td>Flattened map.</td><td>~</td><td>○</td></tr><tr><td><code>destination.k8s.job.name</code></td><td><code>string</code></td><td>The name of the Kubernetes Job for the destination.</td><td></td><td>~</td><td>~</td></tr><tr><td><code>destination.k8s.job.uid</code></td><td><code>string</code></td><td>The UID of the Kubernetes Job for the destination.</td><td></td><td>~</td><td>○</td></tr><tr><td><code>destination.k8s.job.annotations.&#x3C;key></code></td><td><code>string</code></td><td>Dynamic annotations from the destination Job.</td><td>Flattened map.</td><td>~</td><td>○</td></tr><tr><td><code>destination.k8s.cronjob.name</code></td><td><code>string</code></td><td>The name of the Kubernetes CronJob for the destination.</td><td></td><td>~</td><td>~</td></tr><tr><td><code>destination.k8s.cronjob.uid</code></td><td><code>string</code></td><td>The UID of the Kubernetes CronJob for the destination.</td><td></td><td>~</td><td>○</td></tr><tr><td><code>destination.k8s.cronjob.annotations.&#x3C;key></code></td><td><code>string</code></td><td>Dynamic annotations from the destination CronJob.</td><td>Flattened map.</td><td>~</td><td>○</td></tr><tr><td><code>destination.k8s.service.name</code></td><td><code>string</code></td><td>The name of the Kubernetes Service for the destination.</td><td></td><td>~</td><td>~</td></tr><tr><td><code>destination.k8s.service.uid</code></td><td><code>string</code></td><td>The UID of the Kubernetes Service for the destination.</td><td></td><td>~</td><td>○</td></tr><tr><td><code>destination.k8s.service.annotations.&#x3C;key></code></td><td><code>string</code></td><td>Dynamic annotations from the destination Service.</td><td>Flattened map.</td><td>~</td><td>○</td></tr></tbody></table>

## Network Policy Attributes

<table data-full-width="true"><thead><tr><th>Proposed Field Name</th><th width="96.06640625">Data Type</th><th>Description</th><th>Notes / Decisions</th><th width="95.69140625">Std OTel</th><th width="96.390625">Required</th></tr></thead><tbody><tr><td><code>network.policy.ingress</code></td><td><code>string[]</code></td><td>A list of network policy names affecting ingress traffic.</td><td>This could be multiple policies.</td><td></td><td>○</td></tr><tr><td><code>network.policy.egress</code></td><td><code>string[]</code></td><td>A list of network policy names affecting egress traffic.</td><td>This could be multiple policies.</td><td></td><td>○</td></tr></tbody></table>

## Process & Container Attributes

<table data-full-width="true"><thead><tr><th>Proposed Field Name</th><th width="95.671875">Data Type</th><th>Description</th><th>Notes / Decisions</th><th width="96.1015625">Std OTel</th><th width="95.71875">Required</th></tr></thead><tbody><tr><td><code>process.executable.name</code></td><td><code>string</code></td><td>The name of the binary associated with the socket for this flow.</td><td>Provides application-level identification.</td><td>✓</td><td>~</td></tr><tr><td><code>process.pid</code></td><td><code>long</code></td><td>The PID of the process associated with the socket for this flow.</td><td>Provides application-level identification.</td><td>✓</td><td>~</td></tr><tr><td><code>source.container.name</code></td><td><code>string</code></td><td>The container runtime name for the source (e.g., from Docker/containerd).</td><td>Distinct from <code>source.k8s.container.name</code>.</td><td>✓</td><td>~</td></tr><tr><td><code>source.container.image.name</code></td><td><code>string</code></td><td>The image name of the source container (e.g., <code>nginx:1.21</code>).</td><td>From K8s Pod spec container image.</td><td></td><td>~</td></tr><tr><td><code>destination.container.name</code></td><td><code>string</code></td><td>The container runtime name for the destination.</td><td>Distinct from <code>destination.k8s.container.name</code>.</td><td>✓</td><td>~</td></tr><tr><td><code>destination.container.image.name</code></td><td><code>string</code></td><td>The image name of the destination container (e.g., <code>app:v1.0.0</code>).</td><td>From K8s Pod spec container image.</td><td></td><td>~</td></tr></tbody></table>

***

## Example Flow Trace Span (OTLP JSON)

Below is an example of what a flow span might look like in OTLP JSON format.

{% code fullWidth="false" %}

```json
{
  "name": "flow_ipv4_tcp",
  "kind": "SPAN_KIND_CLIENT",
  "startTimeUnixNano": "1727149620000000000",
  "endTimeUnixNano": "1727149680000000000",
  "attributes": [
    { "key": "flow.community_id", "value": { "stringValue": "1:LQU9qZlK+B+2dM2I2n1kI/M5a/g=" } },
    { "key": "flow.direction", "value": { "stringValue": "forward" } },
    { "key": "flow.end_reason", "value": { "stringValue": "active_timeout" } },
    { "key": "flow.bytes.delta", "value": { "intValue": "1024" } },
    { "key": "flow.packets.delta", "value": { "intValue": "10" } },
    { "key": "flow.reverse.bytes.delta", "value": { "intValue": "32768" } },
    { "key": "flow.reverse.packets.delta", "value": { "intValue": "85" } },
    { "key": "source.address", "value": { "stringValue": "10.1.1.5" } },
    { "key": "source.port", "value": { "intValue": "54211" } },
    { "key": "source.k8s.pod.name", "value": { "stringValue": "frontend-abcde" } },
    { "key": "source.k8s.namespace.name", "value": { "stringValue": "production" } },
    { "key": "destination.address", "value": { "stringValue": "10.1.2.10" } },
    { "key": "destination.port", "value": { "intValue": "80" } },
    { "key": "destination.k8s.pod.name", "value": { "stringValue": "backend-xyz" } },
    { "key": "destination.k8s.namespace.name", "value": { "stringValue": "production" } },
    { "key": "network.transport", "value": { "stringValue": "tcp" } },
    { "key": "network.type", "value": { "stringValue": "ipv4" } },
    { "key": "flow.tcp.flags.bits", "value": { "intValue": "18" } },
    { "key": "flow.tcp.flags.tags", "value": { "arrayValue": { "values": [
      { "stringValue": "SYN" },
      { "stringValue": "ACK" }
    ]}}},
    { "key": "flow.reverse.tcp.flags.bits", "value": { "intValue": "18" } },
    { "key": "flow.reverse.tcp.flags.tags", "value": { "arrayValue": { "values": [
      { "stringValue": "SYN" },
      { "stringValue": "ACK" }
    ]}}},
    { "key": "flow.tcp.rndtrip.latency", "value": { "intValue": "2500000" } }
  ]
}
```

{% endcode %}

***

## Next Steps

{% tabs %}
{% tab title="Learn More" %}

1. [**Learn About Flow Traces**](https://docs.mermin.dev/concepts/introduction-to-flow-traces): High-level overview of what Flow Traces represent
2. [**Understand the Architecture**](https://docs.mermin.dev/concepts/agent-architecture): How Mermin generates Flow Traces
   {% endtab %}

{% tab title="Get Started" %}

1. [**Connect to Your Backend**](https://docs.mermin.dev/getting-started/backend-integrations): Send Flow Traces to Grafana, Elastic, or Jaeger
2. [**Deploy Mermin**](https://docs.mermin.dev/getting-started/quickstart-guide): Capture your first Flow Traces
   {% endtab %}
   {% endtabs %}

### Using Flow Traces in Queries

With these semantic conventions, build powerful queries in your observability backend:

* Filter by Kubernetes workload: `k8s.deployment.name = "frontend"`
* Find high-bandwidth flows: `flow.bytes.total > 1000000`
* Identify connection issues: `flow.tcp.flags.rst = true`

### Need Help?

* [**GitHub Discussions**](https://github.com/elastiflow/mermin/discussions): Ask questions about Flow Trace semantics
