<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Victor</title>
    <description>The latest articles on DEV Community by Victor (@victorkhen).</description>
    <link>https://dev.to/victorkhen</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4214856%2Fa873853e-51ce-4e08-835b-a81a2c42cc67.png</url>
      <title>DEV Community: Victor</title>
      <link>https://dev.to/victorkhen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/victorkhen"/>
    <language>en</language>
    <item>
      <title>Ping and "BGP up" passed. The route leak was still there.</title>
      <dc:creator>Victor</dc:creator>
      <pubDate>Thu, 29 Oct 2026 09:25:17 +0000</pubDate>
      <link>https://dev.to/victorkhen/ping-and-bgp-up-passed-the-route-leak-was-still-there-5ehl</link>
      <guid>https://dev.to/victorkhen/ping-and-bgp-up-passed-the-route-leak-was-still-there-5ehl</guid>
      <description>&lt;p&gt;&lt;em&gt;2026-10-28 · Netdryrun 0.3.1, containerlab 0.81.1, FRR 10.4.1&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A one-line change makes a router send its neighbor a default route. After the change, both BGP sessions are established and the ping between the LANs answers. A test made of those checks reports a pass. The leaked default is in the neighbor's routing table anyway, received but not selected, waiting for the day the route it loses to goes away.&lt;/p&gt;

&lt;p&gt;This post walks through that case with the bundled &lt;a href="https://netdryrun.com/examples/?ref=devto#frr-bgp-broken" rel="noopener noreferrer"&gt;&lt;code&gt;frr-bgp-broken&lt;/code&gt;&lt;/a&gt; example: the lab, the change, the commands, the evidence, and what a lab run like this can and can't tell you. Everything quoted is real output from the runs named next to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lab and the change
&lt;/h2&gt;

&lt;p&gt;Three FRR routers in a triangle, each in its own AS, each announcing one LAN over eBGP: r1 (AS 65001, &lt;code&gt;192.168.1.0/24&lt;/code&gt;), r2 (AS 65002, &lt;code&gt;192.168.2.0/24&lt;/code&gt;) and r3 (AS 65003, &lt;code&gt;192.168.3.0/24&lt;/code&gt;). The example is the &lt;a href="https://netdryrun.com/examples/?ref=devto#frr-bgp" rel="noopener noreferrer"&gt;&lt;code&gt;frr-bgp&lt;/code&gt;&lt;/a&gt; lab with one change in r2's config, &lt;a href="https://github.com/netdryrun/netdryrun/blob/main/examples/frr-bgp-broken/r2-bad.conf" rel="noopener noreferrer"&gt;&lt;code&gt;r2-bad.conf&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;router bgp 65002
 ...
 address-family ipv4 unicast
  network 192.168.2.0/24
  ! The bad change: someone added default-originate toward r1
  neighbor 10.0.12.1 default-originate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;default-originate&lt;/code&gt; makes r2 announce &lt;code&gt;0.0.0.0/0&lt;/code&gt; to r1 whether or not r2 has a default route itself. Toward a customer that is sometimes intended. Toward a peer, or a router that has its own way out, it offers to carry all traffic that has no more specific route.&lt;/p&gt;

&lt;h2&gt;
  
  
  The checks
&lt;/h2&gt;

&lt;p&gt;The example's &lt;a href="https://github.com/netdryrun/netdryrun/blob/main/examples/frr-bgp-broken/netdryrun.yml" rel="noopener noreferrer"&gt;&lt;code&gt;netdryrun.yml&lt;/code&gt;&lt;/a&gt; has six checks, all on r1: both BGP sessions established, between 1 and 10 prefixes from r2, a route to r3's LAN, a ping from r1's LAN to r3's, and one negative check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;route&lt;/span&gt;        &lt;span class="c1"&gt;# nothing should send r1 a default route&lt;/span&gt;
  &lt;span class="na"&gt;node&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;r1&lt;/span&gt;
  &lt;span class="na"&gt;prefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.0.0.0/0&lt;/span&gt;
  &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bgp&lt;/span&gt;
  &lt;span class="na"&gt;present&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The run
&lt;/h2&gt;

&lt;p&gt;On Linux or WSL2 with Docker and containerlab:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipx &lt;span class="nb"&gt;install &lt;/span&gt;netdryrun
git clone https://github.com/netdryrun/netdryrun &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;netdryrun
netdryrun run examples/frr-bgp-broken
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This output is from the re-recorded &lt;a href="https://netdryrun.com/guides/frr-ci/?ref=devto#3-run-it-locally" rel="noopener noreferrer"&gt;FRR CI guide&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Netdryrun 0.3.1: 6 checks from examples/frr-bgp-broken/netdryrun.yml
Deploying lab ndr-frr-broken (3 nodes); this usually takes 30-90 s, longer the first time images are pulled...
  [PASS] r1: BGP session to 10.0.12.2 is established -- state is established (13.5s)
  [PASS] r1: BGP session to 10.0.13.2 is established -- state is established (13.5s)
  [PASS] r1: route 192.168.3.0/24 via bgp is present -- active via bgp (13.5s)
  [PASS] r1: ping 192.168.3.1 from 192.168.1.1 expects success -- 3/3 replies (need 2) (13.5s)
Convergence checks done; settling 10s before post-convergence checks...
  [PASS] r1: prefixes from 10.0.12.2 &amp;gt;= 1 and &amp;lt;= 10 -- 3 ipv4-unicast prefixes accepted (held 10s, 5 samples) (10.1s)
  [FAIL] r1: route 0.0.0.0/0 via bgp is absent -- unexpectedly present via bgp (not selected) (0.2s)
Destroying lab...
FAILED: 5 passed, 1 failed (6 checks)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit code &lt;code&gt;1&lt;/code&gt;. In the 0.3.1 release check, the same example took 39 seconds from deploy to destroy, with the images already pulled (&lt;a href="https://github.com/netdryrun/netdryrun/blob/main/releases/0.3.1/gate/live.json" rel="noopener noreferrer"&gt;&lt;code&gt;live.json&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  The diff against a ping-only check
&lt;/h2&gt;

&lt;p&gt;Five of the six checks passed. Split them by what a common pre-change test looks at:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;th&gt;Ping-only test&lt;/th&gt;
&lt;th&gt;Sessions-up test&lt;/th&gt;
&lt;th&gt;This file&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ping 192.168.3.1 from 192.168.1.1&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BGP session to r2 established&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BGP session to r3 established&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;route to r3's LAN present&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 to 10 prefixes from r2&lt;/td&gt;
&lt;td&gt;pass (3 accepted)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;no default route via BGP&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;fail&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Verdict&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;pass&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;pass&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;fail&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A test made only of the ping, or only of the sessions, would have run on this same lab and passed this change. The prefix ceiling passed too: the leak added one prefix (2 became 3), well inside a range that leaves room for growth. Only the check that names the forbidden route failed.&lt;/p&gt;

&lt;p&gt;Why the ping still works: the LANs have more specific routes, so a default changes nothing for traffic between them. And r1 doesn't even select the leaked default: containerlab gives each node a default route through its management network, which FRR sees as a kernel route with distance 0, and that beats BGP's 20. So forwarding on r1 is exactly what it was before the change.&lt;/p&gt;

&lt;p&gt;The same pattern shows up in the &lt;a href="https://netdryrun.com/corpus/?ref=devto" rel="noopener noreferrer"&gt;verdict corpus&lt;/a&gt;, a benchmark of BGP changes the maintainers wrote on one FRR topology, with expected verdicts committed before any run. In &lt;a href="https://netdryrun.com/corpus/17-default-leaked-to-peer/?ref=devto" rel="noopener noreferrer"&gt;case 17&lt;/a&gt;, &lt;code&gt;default-originate&lt;/code&gt; toward a peer, the ping-only and all-sessions-up strategies approved the change and Netdryrun's intent checks rejected it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The evidence
&lt;/h2&gt;

&lt;p&gt;The run with &lt;code&gt;--json report.json&lt;/code&gt; writes every command and its raw output next to each check. The verdict as the &lt;a href="https://netdryrun.com/ai-agents/?ref=devto#result-and-verdict" rel="noopener noreferrer"&gt;MCP server&lt;/a&gt; returns it for this example, shortened to the failed check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"passed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"failed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inconclusive_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lab"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ndr-frr-broken"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"checks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"r1: route 0.0.0.0/0 via bgp is absent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"passed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unexpectedly present via bgp (not selected)"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evidence_file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/path/to/lab/netdryrun-report.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verdict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DO NOT APPLY THIS CHANGE"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The raw output behind a "(not selected)" verdict looks like this. This excerpt is from a different lab with the same leak: a peer router, &lt;code&gt;peer1&lt;/code&gt;, received an upstream's default route (a validation run on Netdryrun 0.3.0, committed as the test fixture &lt;a href="https://github.com/netdryrun/netdryrun/blob/main/tests/fixtures/report-real-failed-leak.json" rel="noopener noreferrer"&gt;&lt;code&gt;report-real-failed-leak.json&lt;/code&gt;&lt;/a&gt;. The report stores &lt;code&gt;output&lt;/code&gt; as the command's text; here it is parsed and shortened to the fields that matter):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"peer1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cmd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vtysh -c show ip route 0.0.0.0/0 json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"protocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bgp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"distance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"nexthops"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10.0.3.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"interfaceName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eth1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}]},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"protocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kernel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"selected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"installed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"distance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"nexthops"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"172.20.20.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"interfaceName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eth0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}]}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two entries for &lt;code&gt;0.0.0.0/0&lt;/code&gt;. The BGP one, from the neighbor at &lt;code&gt;10.0.3.1&lt;/code&gt;, has no &lt;code&gt;selected&lt;/code&gt; or &lt;code&gt;installed&lt;/code&gt;. The kernel one, containerlab's management default through &lt;code&gt;eth0&lt;/code&gt;, is selected and installed. A check that asks "what does the router forward with?" sees only the kernel route. Netdryrun's &lt;code&gt;present: false&lt;/code&gt; counts any BGP path the router received, selected or not, and fails.&lt;/p&gt;

&lt;p&gt;To look at it yourself, rerun with &lt;code&gt;--keep-on-failure&lt;/code&gt; and query the router, then destroy the lab with the command Netdryrun prints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;netdryrun run examples/frr-bgp-broken &lt;span class="nt"&gt;--keep-on-failure&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;clab-ndr-frr-broken-r1 vtysh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"show ip route 0.0.0.0/0"&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;clab-ndr-frr-broken-r1 vtysh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"show bgp ipv4 unicast 0.0.0.0/0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why it matters if nothing changed
&lt;/h2&gt;

&lt;p&gt;In this lab the leak is harmless today. It stops being harmless when the route it loses to goes away: a link fails, someone cleans up a static default, a device without a management default runs the same config. Then the received default becomes the best route, and all traffic with no more specific route goes to the neighbor that leaked it. The &lt;a href="https://github.com/netdryrun/netdryrun/tree/main/examples/srl-hidden-leak" rel="noopener noreferrer"&gt;hidden-leak lab&lt;/a&gt; shows the same leak on Nokia SR Linux and FRR, how to see it by hand in each CLI, and what happens when the static default goes away.&lt;/p&gt;

&lt;p&gt;A static analyzer such as Batfish also catches this particular leak, from the configs alone. The &lt;a href="https://netdryrun.com/batfish/?ref=devto" rel="noopener noreferrer"&gt;Batfish page&lt;/a&gt; shows a leak it misses and the lab catches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it without installing anything
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://codespaces.new/netdryrun/netdryrun?quickstart=1" rel="noopener noreferrer"&gt;Open Netdryrun in GitHub Codespaces&lt;/a&gt; and click &lt;strong&gt;Create codespace&lt;/strong&gt;. It comes with Docker, containerlab and Netdryrun, and pulls the FRR image. When the terminal shows &lt;code&gt;Ready&lt;/code&gt;, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;netdryrun run examples/frr-bgp-broken
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It uses your GitHub account's free Codespaces hours (2-core machine). Stop or delete the codespace when you're done.&lt;/p&gt;

&lt;p&gt;To add the same check to a lab you already have, see &lt;a href="https://netdryrun.com/guides/your-lab/?ref=devto" rel="noopener noreferrer"&gt;Add Netdryrun to the lab you already have&lt;/a&gt;. More guards like this one are on &lt;a href="https://netdryrun.com/recipes/?ref=devto" rel="noopener noreferrer"&gt;Guard recipes&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a lab can't prove
&lt;/h2&gt;

&lt;p&gt;This run shows one thing: in this lab, with these configs, r1 received a default route over BGP that it shouldn't have, and the check that names it caught it. It does not show:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;That your network is free of leaks.&lt;/strong&gt; The check knows about &lt;code&gt;0.0.0.0/0&lt;/code&gt; and nothing else. A leak of any other prefix needs its own check, or a prefix ceiling tight enough to notice it. A pass covers the checks in the file and nothing more.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;That your vendor behaves like FRR.&lt;/strong&gt; The lab ran FRR containers. Route selection, defaults and timers on the routers you run in production can differ.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Behavior at production scale.&lt;/strong&gt; Three routers and a handful of prefixes say nothing about full tables, many peers, hardware or ASIC limits, or convergence time at scale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What production traffic would do.&lt;/strong&gt; Ping tests a path between two addresses you choose; it's evidence for that path only.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More in &lt;a href="https://netdryrun.com/trust/?ref=devto#what-a-lab-cannot-prove" rel="noopener noreferrer"&gt;what a lab cannot prove&lt;/a&gt;. A lab run is evidence for one change in one lab, and a reviewer can read every command and output behind it.&lt;/p&gt;

</description>
      <category>networking</category>
      <category>bgp</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
