<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Network Observability Archives - Linuxcent</title>
	<atom:link href="https://linuxcent.com/tag/network-observability/feed/" rel="self" type="application/rss+xml" />
	<link>https://linuxcent.com/tag/network-observability/</link>
	<description>Infrastructure security, from the kernel up.</description>
	<lastBuildDate>Mon, 06 Jul 2026 03:36:39 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.4</generator>

<image>
	<url>https://linuxcent.com/wp-content/uploads/2026/04/favicon-512x512-1-150x150.png</url>
	<title>Network Observability Archives - Linuxcent</title>
	<link>https://linuxcent.com/tag/network-observability/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">211632295</site>	<item>
		<title>Network Flow Observability — What Every Connection Reveals</title>
		<link>https://linuxcent.com/ebpf-network-flow-observability/</link>
					<comments>https://linuxcent.com/ebpf-network-flow-observability/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Fri, 29 May 2026 02:00:00 +0000</pubDate>
				<category><![CDATA[eBPF]]></category>
		<category><![CDATA[Cilium]]></category>
		<category><![CDATA[Flow Telemetry]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Network Observability]]></category>
		<category><![CDATA[SRE]]></category>
		<category><![CDATA[TC eBPF]]></category>
		<guid isPermaLink="false">https://linuxcent.com/?p=1838</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 9</span> <span class="rt-label rt-postfix">minutes</span></span>Network flow observability eBPF shows every TCP connection and retransmit across your cluster — telemetry APM tools interpret, not originate.</p>
<p>The post <a href="https://linuxcent.com/ebpf-network-flow-observability/">Network Flow Observability — What Every Connection Reveals</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 9</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<p><em>eBPF: From Kernel to Cloud, Episode 10</em><br />
<a href="/what-is-ebpf-linux-kubernetes/">What Is eBPF?</a> · <a href="/bpf-verifier-kubernetes-safety/">The BPF Verifier</a> · <a href="/ebpf-vs-kernel-modules-kubernetes/">eBPF vs Kernel Modules</a> · <a href="/ebpf-program-types-kubernetes/">eBPF Program Types</a> · <a href="/ebpf-maps-explained/">eBPF Maps</a> · <a href="/ebpf-co-re-libbpf-portable-programs/">CO-RE and libbpf</a> · <a href="/ebpf-xdp-kubernetes-networking/">XDP</a> · <a href="/tc-ebpf-kubernetes-network-policy/">TC eBPF</a> · <a href="/bpftrace-kernel-observability/">bpftrace</a> · <strong>Network Flow Observability</strong> · <a href="/ebpf-dns-observability-kubernetes/">DNS Observability</a></p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>Network flow observability with eBPF attaches persistent programs to TC hooks and records every connection attempt, retransmit, reset, and drop — continuously, with no sampling<br />
  <em>(TC hook = Traffic Control hook: the point in the Linux network stack where eBPF programs intercept packets after ingress or before egress, tied to a specific network interface)</em></li>
<li>APM tools and service mesh telemetry are interpretations of what happened; kernel-level flow data from TC hooks is the raw event stream they all derive from</li>
<li>Retransmit counters at the kernel level reveal congestion, half-open connections, and remote endpoint failures that application logs never surface</li>
<li>Cilium&#8217;s Hubble and similar tools (Pixie, Retina) are eBPF flow exporters — they run TC programs, collect <code class="" data-line="">perf_event</code> or <code class="" data-line="">ringbuf</code> events, and expose them over an API</li>
<li>You can verify what flow data a tool is actually collecting with four <code class="" data-line="">bpftool</code> commands — without reading documentation</li>
<li>Production caution: flow maps grow with the number of active connections; pin and bound your maps, and account for the per-packet overhead on high-throughput interfaces</li>
</ul>
<hr />
<p>EP09 showed bpftrace as an on-demand kernel query tool — compile a question, get an answer, clean up. Network flow observability with eBPF is the persistent version: programs that stay attached to TC hooks across your entire fleet, recording every connection without waiting for you to ask. When a client reports intermittent failures that appear nowhere in application logs, that persistent record is what you query. This episode covers how that layer works and how to read it.</p>
<h2 id="quick-check-what-flow-data-is-your-cluster-already-collecting">Quick Check: What Flow Data Is Your Cluster Already Collecting?</h2>
<p>Before building anything new, check what&#8217;s already running. If you have Cilium, Pixie, or Retina on your cluster, eBPF flow programs are already attached:</p>
<pre><code class="" data-line=""># SSH into a worker node, then:

# What TC programs are attached to cluster interfaces?
bpftool net list

# Expected output on a Cilium node:
# xdp:
#
# tc:
# eth0(2) clsact/ingress prog_id 38 prio 1 handle 0x1 direct-action
# eth0(2) clsact/egress  prog_id 39 prio 1 handle 0x1 direct-action
# lxc12a3(15) clsact/ingress prog_id 41 prio 1 handle 0x1 direct-action
# lxc12a3(15) clsact/egress  prog_id 42 prio 1 handle 0x1 direct-action
</code></pre>
<pre><code class="" data-line=""># What maps are those programs holding state in?
bpftool map list | grep -E &quot;flow|conn|sock|nat&quot;

# Sample output:
# 24: hash  name cilium_ct4_global  flags 0x0
#     key 24B  value 56B  max_entries 65536  memlock 4718592B
# 25: hash  name cilium_ct4_local   flags 0x0
#     key 24B  value 56B  max_entries 8192   memlock 589824B
</code></pre>
<p>Each <code class="" data-line="">lxcXXXX</code> interface is a pod&#8217;s veth pair. The TC programs on those interfaces are what Cilium uses to enforce NetworkPolicy and collect flow telemetry. If you see <code class="" data-line="">prog_id</code> values on pod interfaces, your cluster is already doing kernel-level flow collection.</p>
<blockquote>
<p><strong>Not running Cilium?</strong> On a plain kubeadm or EKS node without a CNI that uses eBPF, <code class="" data-line="">bpftool net list</code> will show no TC programs on pod interfaces — just whatever kube-proxy or the CNI plugin installed. You can still attach your own flow programs with <code class="" data-line="">tc qdisc add dev eth0 clsact</code> — that&#8217;s the starting point this episode covers.</p>
</blockquote>
<hr />
<p>The client opened a ticket on a Tuesday afternoon. &#8220;Intermittent connection failures to the payment gateway. Started around 11 AM. Application logs say timeout. Retry logic is masking it for most users but the error rate is up 0.3%.&#8221;</p>
<p>I looked at the APM dashboard. The service showed elevated latency — p99 at 850ms versus a normal 120ms — but no hard errors at the application layer. The service mesh metrics showed the downstream call succeeding from the mesh&#8217;s perspective. The payment gateway team said their side looked clean.</p>
<p>Three tools. Three different answers. All of them interpreting the network. None of them were the network.</p>
<p>I ran:</p>
<pre><code class="" data-line="">bpftool map dump id 24 | grep -A5 &quot;payment-gateway-ip&quot;
</code></pre>
<p>The connection tracking map showed retransmit count 14 for a specific <code class="" data-line="">(src_ip, dst_ip, src_port, dst_port)</code> tuple — the same 5-tuple, every 30 seconds, for 2 hours. The kernel was retransmitting. The TCP stack was compensating. The application was seeing sporadic success because retransmits eventually got through. The APM dashboard averaged that latency into a p99 and called it &#8220;elevated.&#8221;</p>
<p>The kernel had the truth. Everything above it was rounding.</p>
<hr />
<h2 id="why-application-level-metrics-miss-what-the-kernel-sees">Why Application-Level Metrics Miss What the Kernel Sees</h2>
<p>Application metrics — APM spans, service mesh telemetry, load balancer health checks — operate at Layer 7. They measure round-trip time for complete requests, error codes returned, bytes transferred. They answer &#8220;did this request succeed?&#8221; not &#8220;what did the network do to make it succeed?&#8221;</p>
<p>The TCP stack underneath those requests handles retransmits, congestion window adjustments, RST packets, and half-open connections silently. From an application&#8217;s perspective, a request that required 3 retransmits before the ACK arrived looks identical to one that succeeded on the first attempt — slightly slower, but successful.</p>
<p>This is structural, not a tooling gap. Application-layer observability tools cannot see below their own protocol boundary. The kernel&#8217;s TCP implementation does not report upward when it retransmits. It just retransmits.</p>
<p>eBPF flow observability closes this gap by attaching programs directly to the network path — at the TC hook, which fires on every packet crossing a network interface — and recording what the kernel actually does.</p>
<hr />
<h2 id="how-tc-hook-flow-programs-work">How TC Hook Flow Programs Work</h2>
<p>EP08 covered TC eBPF programs for pod network policy. Flow observability uses the same attachment point with a different purpose: instead of allowing or dropping packets, the program reads packet metadata and writes it to a map or ring buffer.</p>
<pre><code class="" data-line="">Pod sends packet
      ↓
veth interface (lxcXXXX)
      ↓
TC clsact/egress hook fires
      ↓
eBPF program reads:
  - src IP, dst IP
  - src port, dst port
  - protocol
  - packet size
  - TCP flags (SYN, ACK, FIN, RST, retransmit bit)
      ↓
Writes event to ringbuf (or perf_event_array)
      ↓
Userspace consumer reads ringbuf
      ↓
Aggregates to flow record
      ↓
Exports to Hubble/Prometheus/flow store
</code></pre>
<blockquote>
<p><strong><code class="" data-line="">ringbuf</code></strong> — a BPF ring buffer: a lock-free, memory-efficient queue shared between a kernel eBPF program and a userspace consumer. The kernel program writes events; the userspace reader drains them. Used instead of <code class="" data-line="">perf_event_array</code> in kernel 5.8+ because it avoids per-CPU memory waste and supports variable-length records. When you see Hubble exporting flows, it&#8217;s reading from a ringbuf that the TC program writes to.</p>
</blockquote>
<p>The key structural property: the TC hook fires on every packet. Not sampled. Not throttled by default. Every SYN, every ACK, every RST, every retransmit. For flow observability, you typically aggregate at the program level — count packets and bytes per 5-tuple per second, rather than emitting an event per packet — but the raw visibility is there if you need it.</p>
<hr />
<h2 id="what-retransmit-telemetry-actually-reveals">What Retransmit Telemetry Actually Reveals</h2>
<p>Most flow observability implementations track TCP retransmits specifically because they are the clearest signal of network-layer trouble invisible to applications.</p>
<p>A TCP retransmit happens when a sender doesn&#8217;t receive an ACK within the retransmission timeout (RTO). The kernel resends the segment and doubles the timeout (exponential backoff). From the application&#8217;s perspective, the call takes longer. If retransmits keep clearing, the application sees success — just slow success.</p>
<blockquote>
<p><strong><code class="" data-line="">perf_event</code></strong> — a kernel mechanism for collecting performance data. In eBPF, <code class="" data-line="">BPF_MAP_TYPE_PERF_EVENT_ARRAY</code> lets kernel programs push variable-length records to userspace readers via a ring buffer per CPU. Older tools use <code class="" data-line="">perf_event_array</code>; newer ones use <code class="" data-line="">BPF_MAP_TYPE_RINGBUF</code> (single shared ring, more efficient). If you inspect an older version of Cilium&#8217;s flow exporter, you&#8217;ll see <code class="" data-line="">perf_event</code> writes; newer versions use <code class="" data-line="">ringbuf</code>.</p>
</blockquote>
<p>To observe retransmits directly with bpftrace:</p>
<pre><code class="" data-line=""># Count retransmit events per destination IP — run for 60 seconds
bpftrace -e &#039;
kprobe:tcp_retransmit_skb {
    $sk = (struct sock *)arg0;
    $daddr = ntop(AF_INET, $sk-&gt;__sk_common.skc_daddr);
    @retransmits[$daddr] = count();
}
interval:s:60 { print(@retransmits); clear(@retransmits); exit(); }
&#039;
</code></pre>
<p>Sample output:</p>
<pre><code class="" data-line="">Attaching 2 probes...
@retransmits[10.96.0.10]:   2       # DNS service — normal
@retransmits[172.16.4.23]:  847     # payment gateway endpoint ← problem here
@retransmits[10.244.1.5]:   1       # normal pod-to-pod traffic
</code></pre>
<p>847 retransmits to a single endpoint in 60 seconds. That&#8217;s not noise. That&#8217;s a congested or half-open connection being retried 14 times per second by the TCP stack while the application layer averages it into &#8220;elevated latency.&#8221;</p>
<hr />
<h2 id="how-cilium-hubble-collects-flow-data">How Cilium Hubble Collects Flow Data</h2>
<p>Hubble is the flow observability layer built into Cilium. Understanding how it works makes you able to reason about what it can and cannot see — and how to verify what it&#8217;s actually collecting.</p>
<p>Hubble&#8217;s architecture:</p>
<pre><code class="" data-line="">Kernel (per node)
├── TC eBPF programs on all pod veth interfaces
│     write flow events → BPF ringbuf
│
└── Hubble node agent (userspace)
      reads ringbuf
      enriches with pod metadata (Kubernetes API)
      exposes gRPC API

Cluster level
└── Hubble Relay
      aggregates per-node gRPC streams
      exposes single cluster-wide API

User tooling
└── hubble observe  /  Hubble UI  /  Prometheus exporter
</code></pre>
<p>The TC programs are writing raw packet events. The Hubble agent is the consumer that translates those events into Kubernetes-aware flow records — adding pod name, namespace, label, and policy verdict on top of the 5-tuple and TCP metadata the kernel provides.</p>
<p>To see what Hubble&#8217;s TC programs have attached:</p>
<pre><code class="" data-line=""># On any Cilium node
bpftool net list | grep lxc

# lxce4a1(23) clsact/ingress prog_id 61  ← Hubble flow program on pod interface ingress
# lxce4a1(23) clsact/egress  prog_id 62  ← Hubble flow program on pod interface egress
# lxcf7b2(31) clsact/ingress prog_id 63
# lxcf7b2(31) clsact/egress  prog_id 64
</code></pre>
<pre><code class="" data-line=""># Inspect one of those programs to confirm it&#039;s reading flow metadata
bpftool prog show id 61

# Output:
# 61: sched_cls  name tail_handle_nat  tag 3a8e2f1b4c7d9e0a  gpl
#     loaded_at 2026-04-22T09:13:45+0530  uid 0
#     xlated 2144B  jited 1382B  memlock 4096B  map_ids 24,31,38
#     btf_id 142
</code></pre>
<p><code class="" data-line="">sched_cls</code> is the BPF program type for TC — confirming these are TC-attached flow programs. <code class="" data-line="">map_ids 24,31,38</code> — those are the maps this program reads from and writes to. You can dump any of them:</p>
<pre><code class="" data-line="">bpftool map dump id 24 | head -40

# Output (connection tracking entry):
# [{
#     &quot;key&quot;: {
#         &quot;saddr&quot;: &quot;10.244.1.5&quot;,        # ← source pod IP
#         &quot;daddr&quot;: &quot;172.16.4.23&quot;,        # ← destination IP
#         &quot;sport&quot;: 48291,                # ← source port
#         &quot;dport&quot;: 443,                  # ← destination port
#         &quot;nexthdr&quot;: 6,                  # ← protocol: TCP
#         &quot;flags&quot;: 3                     # ← CT_EGRESS | CT_ESTABLISHED
#     },
#     &quot;value&quot;: {
#         &quot;rx_packets&quot;: 14832,           # ← packets received
#         &quot;tx_packets&quot;: 14831,           # ← packets sent
#         &quot;rx_bytes&quot;: 3841024,           # ← bytes received
#         &quot;tx_bytes&quot;: 3756288,           # ← bytes sent
#         &quot;lifetime&quot;: 21600,             # ← seconds until entry expires
#         &quot;rx_closing&quot;: 0,
#         &quot;tx_closing&quot;: 0
#     }
# }]
</code></pre>
<p>That&#8217;s the ground truth. Not an APM span. Not a service mesh metric. The actual per-connection counters the kernel is maintaining for that 5-tuple.</p>
<hr />
<h2 id="writing-a-minimal-flow-observer-with-bpftrace">Writing a Minimal Flow Observer with bpftrace</h2>
<p>You don&#8217;t need Cilium or Hubble to get flow telemetry. bpftrace can produce it directly on any node with BTF:</p>
<pre><code class="" data-line=""># Persistent flow table: connections + packet counts for 2 minutes
bpftrace -e &#039;
kprobe:tcp_sendmsg {
    $sk = (struct sock *)arg0;
    $daddr = ntop(AF_INET, $sk-&gt;__sk_common.skc_daddr);
    $dport = $sk-&gt;__sk_common.skc_dport &gt;&gt; 8;
    @flows[comm, $daddr, $dport] = count();
}
interval:s:30 { print(@flows); clear(@flows); }
&#039; --timeout 120
</code></pre>
<p>Sample output (every 30 seconds):</p>
<pre><code class="" data-line="">@flows[curl, 93.184.216.34, 443]:         12    # curl → example.com:443
@flows[coredns, 10.96.0.10, 53]:          341   # CoreDNS upstream queries
@flows[payment-svc, 172.16.4.23, 443]:   1204   # payment service → gateway
@flows[nginx, 10.244.2.3, 8080]:          89    # nginx → upstream pod
</code></pre>
<p>For retransmit tracking specifically:</p>
<pre><code class="" data-line=""># Combined flow + retransmit watcher — runs until Ctrl-C
bpftrace -e &#039;
kprobe:tcp_retransmit_skb {
    $sk = (struct sock *)arg0;
    $daddr = ntop(AF_INET, $sk-&gt;__sk_common.skc_daddr);
    @retx[comm, $daddr] = count();
}
kprobe:tcp_sendmsg {
    $sk = (struct sock *)arg0;
    $daddr = ntop(AF_INET, $sk-&gt;__sk_common.skc_daddr);
    @sends[comm, $daddr] = count();
}
interval:s:10 {
    printf(&quot;=== Retransmit ratio (last 10s) ===\n&quot;);
    print(@retx);
    print(@sends);
    clear(@retx);
    clear(@sends);
}
&#039;
</code></pre>
<p>This gives you both the volume of sends and the retransmit count side by side — the ratio tells you whether retransmits are a rounding error (0.01%) or a signal (5%+).</p>
<hr />
<h2 id="production-gotchas"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Production Gotchas</h2>
<p><strong>Map size bounds matter.</strong> Connection tracking maps default to tens of thousands of entries. On nodes with high connection churn (serverless, short-lived batch jobs), maps can fill and start dropping new entries silently. Check <code class="" data-line="">bpftool map show id N</code> for <code class="" data-line="">max_entries</code> and monitor map utilization. Cilium exposes this as <code class="" data-line="">cilium_bpf_map_pressure</code> in Prometheus.</p>
<p><strong>Per-packet overhead on high-throughput interfaces.</strong> A TC program that fires on every packet on a 10Gbps interface processes millions of packets per second. Aggregating at the program level (count per 5-tuple rather than emit per packet) keeps overhead manageable — Cilium does this. A naive bpftrace one-liner that emits a perf event per packet will saturate the perf ring buffer under real load. Use <code class="" data-line="">ringbuf</code> write paths or aggregate before emitting.</p>
<p><strong>TC hook placement and direction confusion.</strong> Ingress TC on a pod&#8217;s veth (lxcXXXX) sees egress traffic from the pod&#8217;s perspective — because the host sees the packet arriving on the veth after the pod sent it. This reversal is consistent but confusing when you&#8217;re reading direction labels in flow records. EP08 covered this in detail for policy enforcement; the same asymmetry applies to flow data.</p>
<p><strong>Retransmit counters reset on connection close.</strong> If you&#8217;re tracking retransmit totals for a long-lived connection, the count is stored in the kernel&#8217;s socket state and is cleared when the socket closes. For persistent tracking across reconnects, aggregate at the flow level in userspace before the connection closes.</p>
<p><strong>Hubble flow visibility requires pod interfaces.</strong> Hubble only sees traffic that crosses a pod&#8217;s veth interface. Node-to-node traffic that doesn&#8217;t involve a pod (e.g., node SSH, kubelet-to-API-server on the node IP) is not captured by default. For host-level network observability, you need a TC program on the physical interface (<code class="" data-line="">eth0</code>, <code class="" data-line="">ens3</code>), not just on pod veth pairs.</p>
<hr />
<h2 id="quick-reference">Quick Reference</h2>
<table>
<thead>
<tr>
<th>What you want to see</th>
<th>Command</th>
</tr>
</thead>
<tbody>
<tr>
<td>What TC programs are attached</td>
<td><code class="" data-line="">bpftool net list</code></td>
</tr>
<tr>
<td>Which maps a program uses</td>
<td><code class="" data-line="">bpftool prog show id N</code> (check <code class="" data-line="">map_ids</code>)</td>
</tr>
<tr>
<td>Connection tracking entries</td>
<td><code class="" data-line="">bpftool map dump id N</code></td>
</tr>
<tr>
<td>Retransmits per destination</td>
<td><code class="" data-line="">bpftrace -e &#039;kprobe:tcp_retransmit_skb { ... }&#039;</code></td>
</tr>
<tr>
<td>Flow counts per process</td>
<td><code class="" data-line="">bpftrace -e &#039;kprobe:tcp_sendmsg { @[comm, daddr] = count(); }&#039;</code></td>
</tr>
<tr>
<td>Hubble flow stream (Cilium)</td>
<td><code class="" data-line="">hubble observe --follow</code></td>
</tr>
<tr>
<td>Hubble flows for one pod</td>
<td><code class="" data-line="">hubble observe --pod mynamespace/mypod --follow</code></td>
</tr>
<tr>
<td>Verify map pressure</td>
<td><code class="" data-line="">bpftool map show id N</code> (check <code class="" data-line="">max_entries</code> vs entries)</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>Kernel function</th>
<th>What it marks</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="" data-line="">tcp_sendmsg</code></td>
<td>Data being sent on a TCP socket</td>
</tr>
<tr>
<td><code class="" data-line="">tcp_recvmsg</code></td>
<td>Data being received on a TCP socket</td>
</tr>
<tr>
<td><code class="" data-line="">tcp_retransmit_skb</code></td>
<td>A segment being retransmitted</td>
</tr>
<tr>
<td><code class="" data-line="">tcp_send_reset</code></td>
<td>RST being sent</td>
</tr>
<tr>
<td><code class="" data-line="">tcp_fin</code></td>
<td>Connection teardown initiated</td>
</tr>
<tr>
<td><code class="" data-line="">tcp_connect</code></td>
<td>New outbound TCP connection attempt</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>Network flow observability with eBPF attaches TC programs that record every connection event continuously — not sampled, not throttled, not filtered by what the application reports</li>
<li>Retransmit telemetry from <code class="" data-line="">tcp_retransmit_skb</code> reveals congestion and endpoint failures that are structurally invisible to application-layer monitoring tools</li>
<li>Cilium Hubble, Pixie, and Retina are all eBPF flow exporters — they run TC programs, drain a ringbuf, enrich with Kubernetes metadata, and expose the result over an API</li>
<li>You can verify what any flow tool is actually collecting with <code class="" data-line="">bpftool net list</code>, <code class="" data-line="">bpftool prog show</code>, and <code class="" data-line="">bpftool map dump</code> — four commands, no documentation needed</li>
<li>Map sizing and per-packet overhead are the two production concerns; aggregate at the kernel level, bound your maps, and monitor map pressure</li>
<li>The kernel&#8217;s connection tracking map is the ground truth. APM dashboards, service mesh metrics, and load balancer health checks are all interpretations of what that map contains</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>Flow observability tells you what connections exist. EP11 goes one level deeper: what names your pods are resolving those connections to. DNS is where a compromised workload first reveals itself — it queries a domain that has no business being queried from a production pod, and if you&#8217;re not watching the kernel-level DNS path, you won&#8217;t see it until after the damage.</p>
<p>DNS observability at the kernel level uses tracepoint hooks on the DNS syscall path — the same ground-truth approach as flow telemetry, but for name resolution: every query, every response, tied to the pod that made it, without deploying a sidecar.</p>
<p><em>Next: <a href="/ebpf-dns-observability-kubernetes/">DNS observability at the kernel level — what your pods are actually resolving</a></em></p>
<p>Get EP11 in your inbox when it publishes → <a href="https://linuxcent.com/subscribe">linuxcent.com/subscribe</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-network-flow-observability%2F&amp;linkname=Network%20Flow%20Observability%20%E2%80%94%20What%20Every%20Connection%20Reveals" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-network-flow-observability%2F&amp;linkname=Network%20Flow%20Observability%20%E2%80%94%20What%20Every%20Connection%20Reveals" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-network-flow-observability%2F&amp;linkname=Network%20Flow%20Observability%20%E2%80%94%20What%20Every%20Connection%20Reveals" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-network-flow-observability%2F&amp;linkname=Network%20Flow%20Observability%20%E2%80%94%20What%20Every%20Connection%20Reveals" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-network-flow-observability%2F&amp;linkname=Network%20Flow%20Observability%20%E2%80%94%20What%20Every%20Connection%20Reveals" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-network-flow-observability%2F&amp;linkname=Network%20Flow%20Observability%20%E2%80%94%20What%20Every%20Connection%20Reveals" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-network-flow-observability%2F&amp;linkname=Network%20Flow%20Observability%20%E2%80%94%20What%20Every%20Connection%20Reveals" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Febpf-network-flow-observability%2F&#038;title=Network%20Flow%20Observability%20%E2%80%94%20What%20Every%20Connection%20Reveals" data-a2a-url="https://linuxcent.com/ebpf-network-flow-observability/" data-a2a-title="Network Flow Observability — What Every Connection Reveals"></a></p><p>The post <a href="https://linuxcent.com/ebpf-network-flow-observability/">Network Flow Observability — What Every Connection Reveals</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/ebpf-network-flow-observability/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1838</post-id>	</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: linuxcent.com @ 2026-08-30 10:54:02 by W3 Total Cache
-->