<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Tommi Niemi · Writing</title>
    <link>https://niemi.lol/blog</link>
    <atom:link href="https://niemi.lol/feed.xml" rel="self" type="application/rss+xml" />
    <description>Writing on networking, infrastructure, and systems by Tommi Niemi.</description>
    <language>en</language>
    <lastBuildDate>Tue, 16 Jun 2026 00:00:00 GMT</lastBuildDate>
    <item>
      <title>building an unnumbered bgp fabric</title>
      <link>https://niemi.lol/blog/unnumbered-bgp</link>
      <guid isPermaLink="true">https://niemi.lol/blog/unnumbered-bgp</guid>
      <pubDate>Tue, 16 Jun 2026 00:00:00 GMT</pubDate>
      <description>how we wired the AS64500 rack with interior BGP unnumbered. IPv6 link-local, RFC 5549, no /31s, and the places it broke.</description>
      <content:encoded><![CDATA[<p>most rack designs hand out a /31 (and a /127) on every internal link, write them into an IPAM spreadsheet, then configure a BGP neighbor by address. it works fine. it&#39;s also a lot of bookkeeping for links that only ever talk to the one box on the other end.</p>
<p>i recently got to write the design and configuration for a client&#39;s new rack (AS64500). most network work is migrating and iterating on someone&#39;s old setup. this one was a blank page, nothing to inherit, so i did the opposite and configured the interior fabric with BGP unnumbered. no addresses on the internal links. peers find each other over IPv6 link-local, IPv4 routes ride an IPv6 next-hop, and the numbered links are the external ones, where the other side isn&#39;t ours to configure.</p>
<p>this is the long version of how it&#39;s built, node by node, plus the two things that didn&#39;t work the way i expected.</p>
<figure class="diagram">
<svg id="my-svg" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="flowchart" style="max-width: 1284.25px; background-color: transparent;" viewBox="0 -47 1284.25 1138" role="graphics-document document" aria-roledescription="flowchart-v2"><style>#my-svg{font-size:22px;fill:#1d2125;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#my-svg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#my-svg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#my-svg .error-icon{fill:hsl(180, 0%, 100%);}#my-svg .error-text{fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);}#my-svg .edge-thickness-normal{stroke-width:1px;}#my-svg .edge-thickness-thick{stroke-width:3.5px;}#my-svg .edge-pattern-solid{stroke-dasharray:0;}#my-svg .edge-thickness-invisible{stroke-width:0;fill:none;}#my-svg .edge-pattern-dashed{stroke-dasharray:3;}#my-svg .edge-pattern-dotted{stroke-dasharray:2;}#my-svg .marker{fill:#0f4c5c;stroke:#0f4c5c;}#my-svg .marker.cross{stroke:#0f4c5c;}#my-svg svg{font-size:22px;}#my-svg p{margin:0;}#my-svg .label{color:#1d2125;}#my-svg .cluster-label text{fill:rgb(0, 0, 0);}#my-svg .cluster-label span{color:rgb(0, 0, 0);}#my-svg .cluster-label span p{background-color:transparent;}#my-svg .label text,#my-svg span{fill:#1d2125;color:#1d2125;}#my-svg .node rect,#my-svg .node circle,#my-svg .node ellipse,#my-svg .node polygon,#my-svg .node path{fill:#ffffff;stroke:#0f4c5c;stroke-width:1px;}#my-svg .rough-node .label text,#my-svg .node .label text,#my-svg .image-shape .label,#my-svg .icon-shape .label{text-anchor:middle;}#my-svg .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#my-svg .rough-node .label,#my-svg .node .label,#my-svg .image-shape .label,#my-svg .icon-shape .label{text-align:center;}#my-svg .node.clickable{cursor:pointer;}#my-svg .root .anchor path{fill:#0f4c5c!important;stroke-width:0;stroke:#0f4c5c;}#my-svg .arrowheadPath{fill:#0b0b0b;}#my-svg .edgePath .path{stroke:#0f4c5c;stroke-width:1px;}#my-svg .flowchart-link{stroke:#0f4c5c;fill:none;}#my-svg .edgeLabel{background-color:hsl(-120, 0%, 100%);text-align:center;}#my-svg .edgeLabel p{background-color:hsl(-120, 0%, 100%);}#my-svg .edgeLabel rect{opacity:0.5;background-color:hsl(-120, 0%, 100%);fill:hsl(-120, 0%, 100%);}#my-svg .labelBkg{background-color:rgba(255, 255, 255, 0.5);}#my-svg .cluster rect{fill:#f6f8f9;stroke:#d3dadd;stroke-width:1px;}#my-svg .cluster text{fill:rgb(0, 0, 0);}#my-svg .cluster span{color:rgb(0, 0, 0);}#my-svg div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-size:12px;background:hsl(180, 0%, 100%);border:1px solid hsl(180, 0%, 90%);border-radius:2px;pointer-events:none;z-index:100;}#my-svg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#1d2125;}#my-svg rect.text{fill:none;stroke-width:0;}#my-svg .icon-shape,#my-svg .image-shape{background-color:hsl(-120, 0%, 100%);text-align:center;}#my-svg .icon-shape p,#my-svg .image-shape p{background-color:hsl(-120, 0%, 100%);padding:2px;}#my-svg .icon-shape .label rect,#my-svg .image-shape .label rect{opacity:0.5;background-color:hsl(-120, 0%, 100%);fill:hsl(-120, 0%, 100%);}#my-svg .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#my-svg .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#my-svg .node .neo-node{stroke:#0f4c5c;}#my-svg [data-look="neo"].node rect,#my-svg [data-look="neo"].cluster rect,#my-svg [data-look="neo"].node polygon{stroke:url(#my-svg-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg [data-look="neo"].node path{stroke:url(#my-svg-gradient);stroke-width:1px;}#my-svg [data-look="neo"].node .outer-path{filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg [data-look="neo"].node .neo-line path{stroke:#0f4c5c;filter:none;}#my-svg [data-look="neo"].node circle{stroke:url(#my-svg-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg [data-look="neo"].node circle .state-start{fill:#000000;}#my-svg [data-look="neo"].icon-shape .icon{fill:url(#my-svg-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg [data-look="neo"].icon-shape .icon-neo path{stroke:url(#my-svg-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g><marker id="my-svg_flowchart-v2-pointEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-pointStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="4.5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-pointEnd-margin" class="marker flowchart-v2" viewBox="0 0 11.5 14" refX="11.5" refY="7" markerUnits="userSpaceOnUse" markerWidth="10.5" markerHeight="14" orient="auto"><path d="M 0 0 L 11.5 7 L 0 14 z" class="arrowMarkerPath" style="stroke-width: 0; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-pointStart-margin" class="marker flowchart-v2" viewBox="0 0 11.5 14" refX="1" refY="7" markerUnits="userSpaceOnUse" markerWidth="11.5" markerHeight="14" orient="auto"><polygon points="0,7 11.5,14 11.5,0" class="arrowMarkerPath" style="stroke-width: 0; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-circleEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-circleStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-circleEnd-margin" class="marker flowchart-v2" viewBox="0 0 10 10" refY="5" refX="12.25" markerUnits="userSpaceOnUse" markerWidth="14" markerHeight="14" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 0; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-circleStart-margin" class="marker flowchart-v2" viewBox="0 0 10 10" refX="-2" refY="5" markerUnits="userSpaceOnUse" markerWidth="14" markerHeight="14" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 0; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-crossEnd" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-crossStart" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-crossEnd-margin" class="marker cross flowchart-v2" viewBox="0 0 15 15" refX="17.7" refY="7.5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 1,1 L 14,14 M 1,14 L 14,1" class="arrowMarkerPath" style="stroke-width: 2.5;"/></marker><marker id="my-svg_flowchart-v2-crossStart-margin" class="marker cross flowchart-v2" viewBox="0 0 15 15" refX="-3.5" refY="7.5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 1,1 L 14,14 M 1,14 L 14,1" class="arrowMarkerPath" style="stroke-width: 2.5; stroke-dasharray: 1, 0;"/></marker><g class="root"><g class="clusters"><g class="cluster" id="my-svg-K8S" data-look="classic"><rect style="" x="8" y="438" width="480" height="180"/><g class="cluster-label" transform="translate(101.5, 438)"><g><rect class="background" style="stroke: none"/><text y="-10.1" style=""><tspan class="text-outer-tspan row" x="0" y="-0.1em" dy="1.1em"><tspan font-style="normal" class="text-inner-tspan" font-weight="normal">kubernetes</tspan><tspan font-style="normal" class="text-inner-tspan" font-weight="normal"> ·</tspan><tspan font-style="normal" class="text-inner-tspan" font-weight="normal"> 2x</tspan><tspan font-style="normal" class="text-inner-tspan" font-weight="normal"> R640</tspan><tspan font-style="normal" class="text-inner-tspan" font-weight="normal"> /</tspan><tspan font-style="normal" class="text-inner-tspan" font-weight="normal"> cilium</tspan></tspan></text></g></g></g><g class="cluster" id="my-svg-CORE" data-look="classic"><rect style="" x="40.5" y="8" width="1043" height="360"/><g class="cluster-label" transform="translate(409.5, 8)"><g><rect class="background" style="stroke: none"/><text y="-10.1" style=""><tspan class="text-outer-tspan row" x="0" y="-0.1em" dy="1.1em"><tspan font-style="normal" class="text-inner-tspan" font-weight="normal">core</tspan><tspan font-style="normal" class="text-inner-tspan" font-weight="normal"> fabric</tspan><tspan font-style="normal" class="text-inner-tspan" font-weight="normal"> ·</tspan><tspan font-style="normal" class="text-inner-tspan" font-weight="normal"> 2x</tspan><tspan font-style="normal" class="text-inner-tspan" font-weight="normal"> Arista</tspan><tspan font-style="normal" class="text-inner-tspan" font-weight="normal"> 7060CX</tspan></tspan></text></g></g></g><g class="cluster" id="my-svg-EDGE" data-look="classic"><rect style="" x="763.5" y="438" width="432.75" height="395"/><g class="cluster-label" transform="translate(863.375, 438)"><g><rect class="background" style="stroke: none"/><text y="-10.1" style=""><tspan class="text-outer-tspan row" x="0" y="-0.1em" dy="1.1em"><tspan font-style="normal" class="text-inner-tspan" font-weight="normal">edge</tspan><tspan font-style="normal" class="text-inner-tspan" font-weight="normal"> ·</tspan><tspan font-style="normal" class="text-inner-tspan" font-weight="normal"> 2x</tspan><tspan font-style="normal" class="text-inner-tspan" font-weight="normal"> R640</tspan><tspan font-style="normal" class="text-inner-tspan" font-weight="normal"> /</tspan><tspan font-style="normal" class="text-inner-tspan" font-weight="normal"> VyOS</tspan></tspan></text></g></g></g><g class="cluster" id="my-svg-TRANSIT" data-look="classic"><rect style="" x="712" y="903" width="564.25" height="180"/><g class="cluster-label" transform="translate(963.125, 903)"><g><rect class="background" style="stroke: none"/><text y="-10.1" style=""><tspan class="text-outer-tspan row" x="0" y="-0.1em" dy="1.1em"><tspan font-style="normal" class="text-inner-tspan" font-weight="normal">transit</tspan></tspan></text></g></g></g></g><g class="edgePaths"><path d="M966,559.5L938.917,569.25C911.833,579,857.667,598.5,830.583,614.083C803.5,629.667,803.5,641.333,803.5,662.167C803.5,683,803.5,713,803.5,743C803.5,773,803.5,803,803.5,823.833C803.5,844.667,803.5,856.333,803.5,868C803.5,879.667,803.5,891.333,807.551,903C811.602,914.667,819.704,926.333,823.755,932.167L827.806,938" id="my-svg-L_vyos1_ua_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style="stroke:#9aa7ac;stroke-width:1.5px;stroke-dasharray:6 4;fill:none;;;stroke:#9aa7ac;stroke-width:1.5px;stroke-dasharray:6 4;fill:none" data-edge="true" data-et="edge" data-id="L_vyos1_ua_0" data-points="W3sieCI6OTY2LCJ5Ijo1NTkuNX0seyJ4Ijo4MDMuNSwieSI6NjE4fSx7IngiOjgwMy41LCJ5Ijo2NTN9LHsieCI6ODAzLjUsInkiOjc0M30seyJ4Ijo4MDMuNSwieSI6ODMzfSx7IngiOjgwMy41LCJ5Ijo4Njh9LHsieCI6ODAzLjUsInkiOjkwM30seyJ4Ijo4MjcuODA1NTU1NTU1NTU1NSwieSI6OTM4fV0=" data-look="classic"/><path d="M928.5,798L928.5,803.833C928.5,809.667,928.5,821.333,928.5,833C928.5,844.667,928.5,856.333,928.5,868C928.5,879.667,928.5,891.333,924.449,903C920.398,914.667,912.296,926.333,908.245,932.167L904.194,938" id="my-svg-L_vyos2_ua_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style="stroke:#9aa7ac;stroke-width:1.5px;stroke-dasharray:6 4;fill:none;;;stroke:#9aa7ac;stroke-width:1.5px;stroke-dasharray:6 4;fill:none" data-edge="true" data-et="edge" data-id="L_vyos2_ua_0" data-points="W3sieCI6OTI4LjUsInkiOjc5OH0seyJ4Ijo5MjguNSwieSI6ODMzfSx7IngiOjkyOC41LCJ5Ijo4Njh9LHsieCI6OTI4LjUsInkiOjkwM30seyJ4Ijo5MDQuMTk0NDQ0NDQ0NDQ0NSwieSI6OTM4fV0=" data-look="classic"/><path d="M1059.611,583L1060.259,588.833C1060.907,594.667,1062.204,606.333,1062.852,618C1063.5,629.667,1063.5,641.333,1063.5,662.167C1063.5,683,1063.5,713,1063.5,743C1063.5,773,1063.5,803,1063.5,823.833C1063.5,844.667,1063.5,856.333,1063.5,868C1063.5,879.667,1063.5,891.333,1068.053,903C1072.606,914.667,1081.713,926.333,1086.266,932.167L1090.819,938" id="my-svg-L_vyos1_ub_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style="stroke:#9aa7ac;stroke-width:1.5px;stroke-dasharray:6 4;fill:none;;;stroke:#9aa7ac;stroke-width:1.5px;stroke-dasharray:6 4;fill:none" data-edge="true" data-et="edge" data-id="L_vyos1_ub_0" data-points="W3sieCI6MTA1OS42MTExMTExMTExMTEsInkiOjU4M30seyJ4IjoxMDYzLjUsInkiOjYxOH0seyJ4IjoxMDYzLjUsInkiOjY1M30seyJ4IjoxMDYzLjUsInkiOjc0M30seyJ4IjoxMDYzLjUsInkiOjgzM30seyJ4IjoxMDYzLjUsInkiOjg2OH0seyJ4IjoxMDYzLjUsInkiOjkwM30seyJ4IjoxMDkwLjgxOTQ0NDQ0NDQ0NDMsInkiOjkzOH1d" data-look="classic"/><path d="M1016,774.786L1042.708,784.488C1069.417,794.191,1122.833,813.595,1149.542,829.131C1176.25,844.667,1176.25,856.333,1176.25,868C1176.25,879.667,1176.25,891.333,1173.495,903C1170.741,914.667,1165.231,926.333,1162.477,932.167L1159.722,938" id="my-svg-L_vyos2_ub_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style="stroke:#9aa7ac;stroke-width:1.5px;stroke-dasharray:6 4;fill:none;;;stroke:#9aa7ac;stroke-width:1.5px;stroke-dasharray:6 4;fill:none" data-edge="true" data-et="edge" data-id="L_vyos2_ub_0" data-points="W3sieCI6MTAxNiwieSI6Nzc0Ljc4NjA3NDY3MjA0ODV9LHsieCI6MTE3Ni4yNSwieSI6ODMzfSx7IngiOjExNzYuMjUsInkiOjg2OH0seyJ4IjoxMTc2LjI1LCJ5Ijo5MDN9LHsieCI6MTE1OS43MjIyMjIyMjIyMjIyLCJ5Ijo5Mzh9XQ==" data-look="classic"/><path d="M579.778,153L577.023,158.833C574.269,164.667,568.759,176.333,566.005,197.167C563.25,218,563.25,248,563.25,278C563.25,308,563.25,338,563.25,358.833C563.25,379.667,563.25,391.333,563.25,403C563.25,414.667,563.25,426.333,567.301,438C571.352,449.667,579.454,461.333,583.505,467.167L587.556,473" id="my-svg-L_sw1_cust_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style="stroke:#9aa7ac;stroke-width:1.5px;stroke-dasharray:6 4;fill:none;;;stroke:#9aa7ac;stroke-width:1.5px;stroke-dasharray:6 4;fill:none" data-edge="true" data-et="edge" data-id="L_sw1_cust_0" data-points="W3sieCI6NTc5Ljc3Nzc3Nzc3Nzc3NzgsInkiOjE1M30seyJ4Ijo1NjMuMjUsInkiOjE4OH0seyJ4Ijo1NjMuMjUsInkiOjI3OH0seyJ4Ijo1NjMuMjUsInkiOjM2OH0seyJ4Ijo1NjMuMjUsInkiOjQwM30seyJ4Ijo1NjMuMjUsInkiOjQzOH0seyJ4Ijo1ODcuNTU1NTU1NTU1NTU1NSwieSI6NDczfV0=" data-look="classic"/><path d="M688.25,333L688.25,338.833C688.25,344.667,688.25,356.333,688.25,368C688.25,379.667,688.25,391.333,688.25,403C688.25,414.667,688.25,426.333,684.199,438C680.148,449.667,672.046,461.333,667.995,467.167L663.944,473" id="my-svg-L_sw2_cust_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style="stroke:#9aa7ac;stroke-width:1.5px;stroke-dasharray:6 4;fill:none;;;stroke:#9aa7ac;stroke-width:1.5px;stroke-dasharray:6 4;fill:none" data-edge="true" data-et="edge" data-id="L_sw2_cust_0" data-points="W3sieCI6Njg4LjI1LCJ5IjozMzN9LHsieCI6Njg4LjI1LCJ5IjozNjh9LHsieCI6Njg4LjI1LCJ5Ijo0MDN9LHsieCI6Njg4LjI1LCJ5Ijo0Mzh9LHsieCI6NjYzLjk0NDQ0NDQ0NDQ0NDUsInkiOjQ3M31d" data-look="classic"/><path d="M693.25,117.924L744.542,129.603C795.833,141.283,898.417,164.641,949.708,191.321C1001,218,1001,248,1001,278C1001,308,1001,338,1001,358.833C1001,379.667,1001,391.333,1001,403C1001,414.667,1001,426.333,1004.403,438C1007.806,449.667,1014.611,461.333,1018.014,467.167L1021.417,473" id="my-svg-L_sw1_vyos1_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style="stroke:#0f4c5c;stroke-width:2px;fill:none;;;stroke:#0f4c5c;stroke-width:2px;fill:none" data-edge="true" data-et="edge" data-id="L_sw1_vyos1_0" data-points="W3sieCI6NjkzLjI1LCJ5IjoxMTcuOTI0MDk4NjcxNzI2NzV9LHsieCI6MTAwMSwieSI6MTg4fSx7IngiOjEwMDEsInkiOjI3OH0seyJ4IjoxMDAxLCJ5IjozNjh9LHsieCI6MTAwMSwieSI6NDAzfSx7IngiOjEwMDEsInkiOjQzOH0seyJ4IjoxMDIxLjQxNjY2NjY2NjY2NjYsInkiOjQ3M31d" data-look="classic"/><path d="M693.25,128.259L722.042,138.216C750.833,148.173,808.417,168.086,837.208,193.043C866,218,866,248,866,278C866,308,866,338,866,358.833C866,379.667,866,391.333,866,403C866,414.667,866,426.333,866,447.167C866,468,866,498,866,528C866,558,866,588,866,608.833C866,629.667,866,641.333,870.051,653C874.102,664.667,882.204,676.333,886.255,682.167L890.306,688" id="my-svg-L_sw1_vyos2_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style="stroke:#0f4c5c;stroke-width:2px;fill:none;;;stroke:#0f4c5c;stroke-width:2px;fill:none" data-edge="true" data-et="edge" data-id="L_sw1_vyos2_0" data-points="W3sieCI6NjkzLjI1LCJ5IjoxMjguMjU5MzY1OTk0MjM2M30seyJ4Ijo4NjYsInkiOjE4OH0seyJ4Ijo4NjYsInkiOjI3OH0seyJ4Ijo4NjYsInkiOjM2OH0seyJ4Ijo4NjYsInkiOjQwM30seyJ4Ijo4NjYsInkiOjQzOH0seyJ4Ijo4NjYsInkiOjUyOH0seyJ4Ijo4NjYsInkiOjYxOH0seyJ4Ijo4NjYsInkiOjY1M30seyJ4Ijo4OTAuMzA1NTU1NTU1NTU1NSwieSI6Njg4fV0=" data-look="classic"/><path d="M775.75,298.986L823.708,310.488C871.667,321.991,967.583,344.995,1015.542,362.331C1063.5,379.667,1063.5,391.333,1063.5,403C1063.5,414.667,1063.5,426.333,1062.852,438C1062.204,449.667,1060.907,461.333,1060.259,467.167L1059.611,473" id="my-svg-L_sw2_vyos1_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style="stroke:#0f4c5c;stroke-width:2px;fill:none;;;stroke:#0f4c5c;stroke-width:2px;fill:none" data-edge="true" data-et="edge" data-id="L_sw2_vyos1_0" data-points="W3sieCI6Nzc1Ljc1LCJ5IjoyOTguOTg2MDA5MzI3MTE1MjZ9LHsieCI6MTA2My41LCJ5IjozNjh9LHsieCI6MTA2My41LCJ5Ijo0MDN9LHsieCI6MTA2My41LCJ5Ijo0Mzh9LHsieCI6MTA1OS42MTExMTExMTExMTEsInkiOjQ3M31d" data-look="classic"/><path d="M775.75,310.778L801.208,320.315C826.667,329.852,877.583,348.926,903.042,364.296C928.5,379.667,928.5,391.333,928.5,403C928.5,414.667,928.5,426.333,928.5,447.167C928.5,468,928.5,498,928.5,528C928.5,558,928.5,588,928.5,608.833C928.5,629.667,928.5,641.333,928.5,653C928.5,664.667,928.5,676.333,928.5,682.167L928.5,688" id="my-svg-L_sw2_vyos2_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style="stroke:#0f4c5c;stroke-width:2px;fill:none;;;stroke:#0f4c5c;stroke-width:2px;fill:none" data-edge="true" data-et="edge" data-id="L_sw2_vyos2_0" data-points="W3sieCI6Nzc1Ljc1LCJ5IjozMTAuNzc4MzU1ODc5MjkyNH0seyJ4Ijo5MjguNSwieSI6MzY4fSx7IngiOjkyOC41LCJ5Ijo0MDN9LHsieCI6OTI4LjUsInkiOjQzOH0seyJ4Ijo5MjguNSwieSI6NTI4fSx7IngiOjkyOC41LCJ5Ijo2MTh9LHsieCI6OTI4LjUsInkiOjY1M30seyJ4Ijo5MjguNSwieSI6Njg4fV0=" data-look="classic"/><path d="M656.167,153L661.514,158.833C666.861,164.667,677.556,176.333,682.903,188C688.25,199.667,688.25,211.333,688.25,217.167L688.25,223" id="my-svg-L_sw1_sw2_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style="stroke:#0f4c5c;stroke-width:2px;fill:none;;;stroke:#0f4c5c;stroke-width:2px;fill:none" data-edge="true" data-et="edge" data-id="L_sw1_sw2_0" data-points="W3sieCI6NjU2LjE2NjY2NjY2NjY2NjYsInkiOjE1M30seyJ4Ijo2ODguMjUsInkiOjE4OH0seyJ4Ijo2ODguMjUsInkiOjIyM31d" data-look="classic"/><path d="M1021.417,583L1018.014,588.833C1014.611,594.667,1007.806,606.333,1004.403,618C1001,629.667,1001,641.333,996.301,653C991.602,664.667,982.204,676.333,977.505,682.167L972.806,688" id="my-svg-L_vyos1_vyos2_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style="stroke:#0f4c5c;stroke-width:2px;fill:none;;;stroke:#0f4c5c;stroke-width:2px;fill:none" data-edge="true" data-et="edge" data-id="L_vyos1_vyos2_0" data-points="W3sieCI6MTAyMS40MTY2NjY2NjY2NjY2LCJ5Ijo1ODN9LHsieCI6MTAwMSwieSI6NjE4fSx7IngiOjEwMDEsInkiOjY1M30seyJ4Ijo5NzIuODA1NTU1NTU1NTU1NSwieSI6Njg4fV0=" data-look="classic"/><path d="M518.25,114.313L452.375,126.594C386.5,138.875,254.75,163.438,188.875,190.719C123,218,123,248,123,278C123,308,123,338,123,358.833C123,379.667,123,391.333,123,403C123,414.667,123,426.333,123.648,438C124.296,449.667,125.593,461.333,126.241,467.167L126.889,473" id="my-svg-L_sw1_cilium1_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style="stroke:#0f4c5c;stroke-width:2px;fill:none;;;stroke:#0f4c5c;stroke-width:2px;fill:none" data-edge="true" data-et="edge" data-id="L_sw1_cilium1_0" data-points="W3sieCI6NTE4LjI1LCJ5IjoxMTQuMzEyNzkxMjk5ODQ0NjN9LHsieCI6MTIzLCJ5IjoxODh9LHsieCI6MTIzLCJ5IjoyNzh9LHsieCI6MTIzLCJ5IjozNjh9LHsieCI6MTIzLCJ5Ijo0MDN9LHsieCI6MTIzLCJ5Ijo0Mzh9LHsieCI6MTI2Ljg4ODg4ODg4ODg4ODg5LCJ5Ijo0NzN9XQ==" data-look="classic"/><path d="M518.25,120.646L474.875,131.871C431.5,143.097,344.75,165.549,301.375,191.774C258,218,258,248,258,278C258,308,258,338,258,358.833C258,379.667,258,391.333,258,403C258,414.667,258,426.333,264.806,438C271.611,449.667,285.222,461.333,292.028,467.167L298.833,473" id="my-svg-L_sw1_cilium2_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style="stroke:#0f4c5c;stroke-width:2px;fill:none;;;stroke:#0f4c5c;stroke-width:2px;fill:none" data-edge="true" data-et="edge" data-id="L_sw1_cilium2_0" data-points="W3sieCI6NTE4LjI1LCJ5IjoxMjAuNjQ1NTc4NzIwMzQ1MDd9LHsieCI6MjU4LCJ5IjoxODh9LHsieCI6MjU4LCJ5IjoyNzh9LHsieCI6MjU4LCJ5IjozNjh9LHsieCI6MjU4LCJ5Ijo0MDN9LHsieCI6MjU4LCJ5Ijo0Mzh9LHsieCI6Mjk4LjgzMzMzMzMzMzMzMzMsInkiOjQ3M31d" data-look="classic"/><path d="M600.75,295.49L540.292,307.575C479.833,319.66,358.917,343.83,298.458,361.748C238,379.667,238,391.333,238,403C238,414.667,238,426.333,231.194,438C224.389,449.667,210.778,461.333,203.972,467.167L197.167,473" id="my-svg-L_sw2_cilium1_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style="stroke:#0f4c5c;stroke-width:2px;fill:none;;;stroke:#0f4c5c;stroke-width:2px;fill:none" data-edge="true" data-et="edge" data-id="L_sw2_cilium1_0" data-points="W3sieCI6NjAwLjc1LCJ5IjoyOTUuNDkwMjgzMTc2MDEzMzV9LHsieCI6MjM4LCJ5IjozNjh9LHsieCI6MjM4LCJ5Ijo0MDN9LHsieCI6MjM4LCJ5Ijo0Mzh9LHsieCI6MTk3LjE2NjY2NjY2NjY2NjY5LCJ5Ijo0NzN9XQ==" data-look="classic"/><path d="M600.75,306.873L569.875,317.06C539,327.248,477.25,347.624,446.375,363.645C415.5,379.667,415.5,391.333,415.5,403C415.5,414.667,415.5,426.333,412.097,438C408.694,449.667,401.889,461.333,398.486,467.167L395.083,473" id="my-svg-L_sw2_cilium2_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style="stroke:#0f4c5c;stroke-width:2px;fill:none;;;stroke:#0f4c5c;stroke-width:2px;fill:none" data-edge="true" data-et="edge" data-id="L_sw2_cilium2_0" data-points="W3sieCI6NjAwLjc1LCJ5IjozMDYuODcyNTkzOTUwNTA0MX0seyJ4Ijo0MTUuNSwieSI6MzY4fSx7IngiOjQxNS41LCJ5Ijo0MDN9LHsieCI6NDE1LjUsInkiOjQzOH0seyJ4IjozOTUuMDgzMzMzMzMzMzMzMywieSI6NDczfV0=" data-look="classic"/></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" data-id="L_vyos1_ua_0" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" dy="1.1em" text-anchor="middle"/></text></g></g><g><rect class="background" style="stroke: none"/></g><g class="edgeLabel"><g class="label" data-id="L_vyos2_ua_0" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" dy="1.1em" text-anchor="middle"/></text></g></g><g><rect class="background" style="stroke: none"/></g><g class="edgeLabel"><g class="label" data-id="L_vyos1_ub_0" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" dy="1.1em" text-anchor="middle"/></text></g></g><g><rect class="background" style="stroke: none"/></g><g class="edgeLabel"><g class="label" data-id="L_vyos2_ub_0" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" dy="1.1em" text-anchor="middle"/></text></g></g><g><rect class="background" style="stroke: none"/></g><g class="edgeLabel"><g class="label" data-id="L_sw1_cust_0" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" dy="1.1em" text-anchor="middle"/></text></g></g><g><rect class="background" style="stroke: none"/></g><g class="edgeLabel"><g class="label" data-id="L_sw2_cust_0" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" dy="1.1em" text-anchor="middle"/></text></g></g><g><rect class="background" style="stroke: none"/></g><g class="edgeLabel"><g class="label" data-id="L_sw1_vyos1_0" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" dy="1.1em" text-anchor="middle"/></text></g></g><g><rect class="background" style="stroke: none"/></g><g class="edgeLabel"><g class="label" data-id="L_sw1_vyos2_0" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" dy="1.1em" text-anchor="middle"/></text></g></g><g><rect class="background" style="stroke: none"/></g><g class="edgeLabel"><g class="label" data-id="L_sw2_vyos1_0" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" dy="1.1em" text-anchor="middle"/></text></g></g><g><rect class="background" style="stroke: none"/></g><g class="edgeLabel"><g class="label" data-id="L_sw2_vyos2_0" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" dy="1.1em" text-anchor="middle"/></text></g></g><g><rect class="background" style="stroke: none"/></g><g class="edgeLabel"><g class="label" data-id="L_sw1_sw2_0" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" dy="1.1em" text-anchor="middle"/></text></g></g><g><rect class="background" style="stroke: none"/></g><g class="edgeLabel"><g class="label" data-id="L_vyos1_vyos2_0" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" dy="1.1em" text-anchor="middle"/></text></g></g><g><rect class="background" style="stroke: none"/></g><g class="edgeLabel"><g class="label" data-id="L_sw1_cilium1_0" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" dy="1.1em" text-anchor="middle"/></text></g></g><g><rect class="background" style="stroke: none"/></g><g class="edgeLabel"><g class="label" data-id="L_sw1_cilium2_0" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" dy="1.1em" text-anchor="middle"/></text></g></g><g><rect class="background" style="stroke: none"/></g><g class="edgeLabel"><g class="label" data-id="L_sw2_cilium1_0" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" dy="1.1em" text-anchor="middle"/></text></g></g><g><rect class="background" style="stroke: none"/></g><g class="edgeLabel"><g class="label" data-id="L_sw2_cilium2_0" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" dy="1.1em" text-anchor="middle"/></text></g></g><g><rect class="background" style="stroke: none"/></g></g><g class="nodes"><g class="node default" id="my-svg-flowchart-ua-0" data-look="classic" transform="translate(866, 993)"><rect class="basic label-container" style="" x="-105.5" y="-55" width="211" height="110"/><g class="label" style="" transform="translate(-61.5, -33)"><rect/><foreignObject width="123" height="66"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>upstream-a<br />AS65536</p></span></div></foreignObject></g></g><g class="node default" id="my-svg-flowchart-ub-1" data-look="classic" transform="translate(1133.75, 993)"><rect class="basic label-container" style="" x="-105" y="-55" width="210" height="110"/><g class="label" style="" transform="translate(-61, -33)"><rect/><foreignObject width="122" height="66"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>upstream-b<br />AS65537</p></span></div></foreignObject></g></g><g class="node default" id="my-svg-flowchart-vyos1-2" data-look="classic" transform="translate(1053.5, 528)"><rect class="basic label-container" style="" x="-87.5" y="-55" width="175" height="110"/><g class="label" style="" transform="translate(-43.5, -33)"><rect/><foreignObject width="87" height="66"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>vyos1<br />AS64500</p></span></div></foreignObject></g></g><g class="node default" id="my-svg-flowchart-vyos2-3" data-look="classic" transform="translate(928.5, 743)"><rect class="basic label-container" style="" x="-87.5" y="-55" width="175" height="110"/><g class="label" style="" transform="translate(-43.5, -33)"><rect/><foreignObject width="87" height="66"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>vyos2<br />AS64500</p></span></div></foreignObject></g></g><g class="node default" id="my-svg-flowchart-sw1-4" data-look="classic" transform="translate(605.75, 98)"><rect class="basic label-container" style="" x="-87.5" y="-55" width="175" height="110"/><g class="label" style="" transform="translate(-43.5, -33)"><rect/><foreignObject width="87" height="66"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>sw1<br />AS65001</p></span></div></foreignObject></g></g><g class="node default" id="my-svg-flowchart-sw2-5" data-look="classic" transform="translate(688.25, 278)"><rect class="basic label-container" style="" x="-87.5" y="-55" width="175" height="110"/><g class="label" style="" transform="translate(-43.5, -33)"><rect/><foreignObject width="87" height="66"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>sw2<br />AS65002</p></span></div></foreignObject></g></g><g class="node default" id="my-svg-flowchart-cilium1-6" data-look="classic" transform="translate(133, 528)"><rect class="basic label-container" style="" x="-87.5" y="-55" width="175" height="110"/><g class="label" style="" transform="translate(-43.5, -33)"><rect/><foreignObject width="87" height="66"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>cilium1<br />AS65301</p></span></div></foreignObject></g></g><g class="node default" id="my-svg-flowchart-cilium2-7" data-look="classic" transform="translate(363, 528)"><rect class="basic label-container" style="" x="-87.5" y="-55" width="175" height="110"/><g class="label" style="" transform="translate(-43.5, -33)"><rect/><foreignObject width="87" height="66"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>cilium2<br />AS65302</p></span></div></foreignObject></g></g><g class="node default" id="my-svg-flowchart-cust-8" data-look="classic" transform="translate(625.75, 528)"><rect class="basic label-container" style="" x="-93" y="-55" width="186" height="110"/><g class="label" style="" transform="translate(-49, -33)"><rect/><foreignObject width="98" height="66"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>customer<br />AS65538</p></span></div></foreignObject></g></g></g></g></g><defs><filter id="my-svg-drop-shadow" height="130%" width="130%"><feDropShadow dx="4" dy="4" stdDeviation="0" flood-opacity="0.06" flood-color="#000000"/></filter></defs><defs><filter id="my-svg-drop-shadow-small" height="150%" width="150%"><feDropShadow dx="2" dy="2" stdDeviation="0" flood-opacity="0.06" flood-color="#000000"/></filter></defs><linearGradient id="my-svg-gradient" gradientUnits="objectBoundingBox" x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" stop-color="#0f4c5c" stop-opacity="1"/><stop offset="100%" stop-color="hsl(-120, 0%, 90%)" stop-opacity="1"/></linearGradient><text text-anchor="middle" x="642.125" y="-25" class="flowchartTitleText">rack-mvp</text></svg>
<figcaption>rack-mvp, generated from the containerlab topology. solid teal links are unnumbered interior fabric (IPv6 link-local + RFC 5549), dashed grey links are the numbered external transit and customer sessions.</figcaption>
</figure>

<p>none of this went onto the hardware blind. i built the whole rack in containerlab first (<a href="https://github.com/rotkonetworks/unnumbered-bgp">full anonymized lab on github</a>), cEOS standing in for the arista switches and FRR for the vyos edges. in that fabric lab FRR also stands in for the cilium leaves, so i can iterate on switch and edge configs without booting kubernetes for every change. a second lab runs the real thing, Cilium on a kind cluster with its own BGP control-plane, where the server side gets validated against the same switches. that&#39;s the pre-production stage: every change goes through a lab before it touches a real port. the diagram above is generated from the fabric topology, and it&#39;s where the two bugs further down turned up.</p>
<input type="checkbox" id="lb-clab" class="lb-toggle" aria-hidden="true" />
<figure class="diagram">
<label for="lb-clab" class="lb-zoom" title="click to enlarge"><img src="https://niemi.lol/clab-topoviewer.png" width="1837" height="1066" loading="lazy" decoding="async" alt="containerlab topoViewer rendering of the rack: two arista switches, two vyos edges, two cilium leaves, routinators, and the transit path out to the upstreams." /></label>
<figcaption>the same rack in containerlab's topoViewer. this is the fabric lab, where FRR stands in for the cilium leaves so the topology boots without kubernetes, the open panel shows <code>cilium2</code> running the frr image with role <code>cilium-sim</code>. click to enlarge.</figcaption>
</figure>
<label for="lb-clab" class="lb-backdrop" aria-hidden="true"><img src="https://niemi.lol/clab-topoviewer.png" loading="lazy" decoding="async" alt="" /></label>

<h2>the rule</h2>
<p>everything follows from one decision. the interior is IPv6-only, and IPv4 only appears on numbered external links.</p>
<p>inside the AS, switch to switch, switch to edge, edge to edge, there are no IPv4 addresses and no /31s. the loopbacks are IPv6 (<code>fd00::1</code>, <code>fd00::2</code>, <code>fd00::11</code>, <code>fd00::12</code>). the router-ids are still dotted-quad (<code>10.0.0.1</code>, <code>10.0.0.11</code>, and so on) but that&#39;s a 32-bit identifier, not an address you can ping. IPv4 only shows up where it has to, on the transit and customer links, where the peer can&#39;t hand us an IPv6-only session.</p>
<p>the topology is two arista 7060cx switches (<code>sw1</code> AS65001, <code>sw2</code> AS65002), two vyos edges sharing one external face (<code>vyos1</code> and <code>vyos2</code>, both AS64500), and two cilium speakers (<code>cilium1</code> AS65301, <code>cilium2</code> AS65302). transit comes in from two upstreams, and a customer hangs off the switches. it&#39;s eBGP everywhere except the single iBGP session between the two edges: per-switch and per-server private ASNs, no route reflector.</p>
<p>the switches run arista EOS. the edges run vyos, which is FRR under the hood. the servers run Cilium, and in the fabric lab FRR stands in for them. so the configs below are EOS on the switches, FRR on the edges, and Cilium on the servers.</p>
<p>in the diagram, every solid teal link is unnumbered and every dashed grey link is numbered. the dashed ones are the links that leave the building.</p>
<h2>first, a little ipv6</h2>
<p>most people never learn IPv6 properly. they assume it&#39;s just IPv4 with hexadecimals. but the protocol has real depth, and this whole trick leans on one piece of it that IPv4 never had: a link addresses itself, with no help from you. it&#39;s worth two minutes at the packet level. if you want more than two minutes, APNIC&#39;s week-long onsite <a href="https://academy.apnic.net/en/course/ipv6-fundamentals">IPv6 fundamentals</a> course is where i actually learned this, and it was worth the week.</p>
<p>the moment an interface comes up it gives itself a link-local address in <code>fe80::/10</code>, with a stable, opaque interface id (<a href="https://www.rfc-editor.org/info/rfc7217">RFC 7217</a>). no DHCP, no assignment, and the address never leaves the link it&#39;s on.</p>
<p>before it uses that address it checks nobody else has it. that&#39;s duplicate address detection (DAD, <a href="https://www.rfc-editor.org/info/rfc4862">RFC 4862</a>): it sends one neighbor solicitation, an NS (ICMPv6 type 135), with the source set to <code>::</code>, the unspecified address, because it has no valid address yet, and the destination set to the solicited-node multicast of the tentative address, <code>ff02::1:ffXX:XXXX</code>, formed from the low 24 bits of the target. if some other node already holds that address it answers with a neighbor advertisement (NA, type 136) and the tentative address is abandoned. if nothing answers, it flips from tentative to usable. the interface id is 64 bits of effectively random, so a collision is vanishingly unlikely and DAD passes on the first NS.</p>
<p>then the routers make themselves known. a host can ask with a router solicitation (RS, type 133) sent to <code>ff02::2</code>, the all-routers group, and routers answer, and also advertise on their own schedule, with router advertisements (RA, type 134) to <code>ff02::1</code>, all-nodes. the RA is sourced from the router&#39;s own link-local and carries its presence, the on-link prefixes, the MTU. resolving an address to a MAC uses the same NS/NA pair aimed at the solicited-node multicast, the IPv6 replacement for ARP: a targeted multicast that only the likely owner hears, not a broadcast to the whole segment.</p>
<p>tldr: on any IPv6 link, both ends come up with a stable, unique, self-assigned address and a built-in way to find and reach each other, before a single line of config. that is the property the rest of this rides on.</p>
<h2>what unnumbered means here</h2>
<p>we lean on exactly that. an interior link gets no /31. FRR peers over the <code>fe80::</code> IPv6 already put on the interface, and the arista side just needs <code>ipv6 enable</code>. the only thing we switch on is RAs, so each end learns the other&#39;s link-local and knows who its neighbor is:</p>
<pre><code>interface eth1
 no ipv6 nd suppress-ra
 ipv6 nd ra-interval 5
</code></pre>
<p>without that, <code>neighbor eth1 interface</code> has nothing to discover and the session stays down.</p>
<p>the last piece is carrying IPv4 over that IPv6 next-hop. that&#39;s <a href="https://www.rfc-editor.org/info/rfc5549">RFC 5549</a> (updated by <a href="https://www.rfc-editor.org/info/rfc8950">8950</a>): an IPv4 prefix advertised with an IPv6 next-hop, so <code>192.0.2.0/24</code> crosses a link with no IPv4 address on it. arista has to be told to do this; FRR does it on its own. both below.</p>
<h2>the switch side</h2>
<p>arista EOS doesn&#39;t implement <code>remote-as external</code>. you give it the AS per neighbor and peer the interface into a peer-group:</p>
<pre><code>neighbor PG-INTERIOR peer group
neighbor PG-INTERIOR timers 10 30
neighbor PG-INTERIOR bfd
!
neighbor interface Et33 peer-group PG-INTERIOR remote-as 65002   ! peer-link to sw2
neighbor interface Et1  peer-group PG-INTERIOR remote-as 64500   ! vyos1
neighbor interface Et2  peer-group PG-INTERIOR remote-as 64500   ! vyos2
</code></pre>
<p>and the IPv4-over-IPv6 next-hop that FRR did for free has to be turned on by hand:</p>
<pre><code>address-family ipv4
   neighbor PG-INTERIOR activate
   neighbor PG-INTERIOR next-hop address-family ipv6 originate
</code></pre>
<p><code>next-hop address-family ipv6 originate</code> is the EOS knob for advertising IPv4 NLRI with an IPv6 next-hop. the interior interfaces themselves are just <code>no switchport</code>, <code>no ip address</code>, <code>ipv6 enable</code>, with RA on. the only interior neighbors missing here are the servers, which peer by address in a separate group, for reasons that come up below.</p>
<h2>the edge</h2>
<p>the vyos edges are where the two worlds meet. inside, the iBGP between the edges and the eBGP down to the switches is all unnumbered:</p>
<pre><code>! ibgp to vyos1 (unnumbered)
neighbor eth3 interface remote-as 64500
neighbor eth3 timers 10 30
neighbor eth3 bfd
!
! ebgp to switches (unnumbered)
neighbor eth1 interface remote-as external
neighbor eth1 timers 10 30
neighbor eth1 bfd
neighbor eth2 interface remote-as external
neighbor eth2 timers 10 30
neighbor eth2 bfd
</code></pre>
<p><code>remote-as 64500</code> on the iBGP peer is the one place we give an explicit AS instead of <code>external</code>. the two edges are the same AS on purpose: AS64500 is the single face we show the world, one origin for the prefixes, not two. that makes the link between them iBGP, the only iBGP in the network. everything else has its own private ASN, so it&#39;s eBGP. with just two edges that one session is already a full mesh, so there&#39;s no route reflector to run.</p>
<p>interior timers are tightened to <code>10 30</code> from FRR&#39;s default <code>60 180</code>, with BFD doing the sub-second work on top.</p>
<p>outside, the transit sessions stay numbered. real addresses on the upstream links, <code>local-role customer</code> (<a href="https://www.rfc-editor.org/info/rfc9234">RFC 9234</a>) and <code>ttl-security hops 1</code> (<a href="https://www.rfc-editor.org/info/rfc5082">GTSM</a>) per peer, <code>bgp ebgp-requires-policy</code> left on for <a href="https://www.rfc-editor.org/info/bcp194">BCP 194</a>. that&#39;s the normal ISP pattern and there&#39;s no reason to fight it. unnumbered is for links you own both ends of.</p>
<p>the result is less to manage, and it scales without planning. adding an interior link is <code>ip link set up</code>, <code>no ipv6 nd suppress-ra</code>, and one <code>neighbor ethN interface</code> line, the same on every box, so a new switch or edge is that recipe plus its ASN, nothing to look up. no subnet to allocate, no IPAM row, no /31 pool that grows with the fabric. that&#39;s why unnumbered is the default for large leaf-spine networks, where the interior links dwarf a two-switch rack and hand-numbering them would be a job in itself (<a href="https://www.rfc-editor.org/info/rfc7938">RFC 7938</a>).</p>
<h2>the server side</h2>
<p>the one interior link that doesn&#39;t get unnumbered is the server. the servers run Cilium, and Cilium&#39;s BGP control-plane only peers by explicit address, there&#39;s no <code>interface</code> peer the way FRR has one. so the cilium-to-switch links are the single interior exception: numbered, a <code>/31</code> and a <code>/127</code> per leg.</p>
<p>the speaker is declared in a <code>CiliumBGPClusterConfig</code>, one BGP instance per node with a peer entry per switch and family:</p>
<pre><code class="language-yaml">apiVersion: cilium.io/v2
kind: CiliumBGPClusterConfig
metadata:
  name: cilium1
spec:
  nodeSelector:
    matchLabels:
      server: &quot;1&quot;
  bgpInstances:
    - name: &quot;65301&quot;
      localASN: 65301
      peers:
        - name: sw1-v4
          peerASN: 65001
          peerAddress: 10.10.110.0
          peerConfigRef: { name: cilium-peer }
        - name: sw1-v6
          peerASN: 65001
          peerAddress: &quot;fd00:10:110::&quot;
          peerConfigRef: { name: cilium-peer }
        - name: sw2-v4
          peerASN: 65002
          peerAddress: 10.10.111.0
          peerConfigRef: { name: cilium-peer }
        - name: sw2-v6
          peerASN: 65002
          peerAddress: &quot;fd00:10:111::&quot;
          peerConfigRef: { name: cilium-peer }
</code></pre>
<p>the parts that matter:</p>
<ul>
<li>every peer is keyed by a <code>peerAddress</code>, so a node brings up four sessions, a v4 and a v6 to each switch. the unnumbered side of the fabric gets one interface session that carries both families; here it&#39;s a session per family per neighbor, with an address to allocate for each.</li>
<li>the matching switch side is plain numbered eBGP in its own <code>PG-CILIUM</code> group: <code>neighbor 10.10.110.1 remote-as 65301</code>, <code>neighbor fd00:10:110::1 remote-as 65301</code>, and so on. same peer-group and timers, just addresses instead of interfaces.</li>
<li>what gets advertised isn&#39;t a <code>network</code> statement, it&#39;s a Cilium object. a <code>CiliumLoadBalancerIPPool</code> hands out the service IPs and a <code>CiliumBGPAdvertisement</code> picks which to announce. the anycast LB address comes out of both cilium1 and cilium2 and gets ECMP&#39;d at the switches.</li>
</ul>
<p>this is the boundary between the routed fabric and kubernetes, and it&#39;s the one spot in the interior that looks like an ordinary numbered eBGP session.</p>
<h2>where it broke</h2>
<p>unnumbered works well until something downstream doesn&#39;t speak it. two things caught us.</p>
<p>BFD is fine on unnumbered, broken on numbered eBGP, at least on this EOS. on <code>ceos:4.36.0.1F</code>, BFD on the unnumbered link-local peers comes up reliably, <code>show bfd peers brief</code> shows <code>up</code>. but add <code>neighbor X bfd</code> to a numbered eBGP peer, the switch-to-customer link, and that session wedges in <code>Idle</code> with <code>Last reset: Address family activated (n/a)</code> and never establishes. the unnumbered peers on the same switch stay up. FRR-to-FRR numbered eBGP does BFD fine, so it&#39;s specific to numbered eBGP on EOS. we left the numbered customer peers without BFD and let them fall back to BGP holdtime plus GTSM. the link you&#39;d reach for BFD on first, the external one, is the one place it wouldn&#39;t go.</p>
<p>the second one is cilium, the place i wanted unnumbered most. the server links above are numbered because they have to be: cilium&#39;s BGP doesn&#39;t implement interface peering (<a href="https://github.com/cilium/cilium/issues/22132">cilium/cilium#22132</a>, open since 2022), and it doesn&#39;t negotiate BFD either (<a href="https://github.com/cilium/cilium/issues/22394">#22394</a>) in the OSS build. so failure detection on those links drops from BFD&#39;s sub-second to BGP holdtime, around 30 seconds. the FRR stand-ins in the fabric lab do speak unnumbered and BFD, so any demo that shows sub-second cilium failover is really showing the FRR stubs, not stock cilium.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
