<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en" xml:base="https://vincent.bernat.ch">
  <title>Vincent Bernat — Linux</title>
  <link href="https://vincent.bernat.ch/en/blog/atom-linux.xml" rel="self"/>
  <link href="https://vincent.bernat.ch/en/blog#tag-linux" rel="alternate"/>
  <id>http://www.luffy.cx/en/blog/atom-linux.xml/</id>
  <updated>2026-09-01T05:51:51Z</updated>

  <entry>
    <title type="html">RAID 5 with mixed-capacity disks on Linux</title>
    <author><name>Vincent Bernat</name></author>
    <link href="https://vincent.bernat.ch/en/blog/2026-unequal-linux-raid" rel="alternate"/>
    <link href="https://vincent.bernat.ch/en/blog/2026-unequal-linux-raid#isso-thread" rel="replies" type="text/html"/>
    <updated>2026-01-19T04:49:42Z</updated>
    <id>http://www.luffy.cx/en/blog/2026-unequal-linux-raid.html</id>

    <content type="xhtml">
      <div xmlns="http://www.w3.org/1999/xhtml">
<p>Standard RAID solutions waste space when disks have different sizes. Linux
software RAID with <abbr title="Logical Volume Manager">LVM</abbr> uses the full capacity of each disk and lets you grow
storage by replacing one or two disks at a time.<sup id="fnref:shr"><a class="footnote-ref" href="#fn:shr">1</a></sup></p>
<p>We start with four disks of equal size:</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp">$ </span>lsblk<span class="w"> </span>-Mo<span class="w"> </span>NAME,TYPE,SIZE
<span class="go">NAME TYPE  SIZE</span>
<span class="go">vda  disk  101M</span>
<span class="go">vdb  disk  101M</span>
<span class="go">vdc  disk  101M</span>
<span class="go">vdd  disk  101M</span>
</code></pre></div>

<p>We create one partition on each of them:</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp">$ </span>sgdisk<span class="w"> </span>--zap-all<span class="w"> </span>--new<span class="o">=</span><span class="m">0</span>:0:0<span class="w"> </span>-t<span class="w"> </span><span class="m">0</span>:fd00<span class="w"> </span>/dev/vda
<span class="gp">$ </span>sgdisk<span class="w"> </span>--zap-all<span class="w"> </span>--new<span class="o">=</span><span class="m">0</span>:0:0<span class="w"> </span>-t<span class="w"> </span><span class="m">0</span>:fd00<span class="w"> </span>/dev/vdb
<span class="gp">$ </span>sgdisk<span class="w"> </span>--zap-all<span class="w"> </span>--new<span class="o">=</span><span class="m">0</span>:0:0<span class="w"> </span>-t<span class="w"> </span><span class="m">0</span>:fd00<span class="w"> </span>/dev/vdc
<span class="gp">$ </span>sgdisk<span class="w"> </span>--zap-all<span class="w"> </span>--new<span class="o">=</span><span class="m">0</span>:0:0<span class="w"> </span>-t<span class="w"> </span><span class="m">0</span>:fd00<span class="w"> </span>/dev/vdd
<span class="gp">$ </span>lsblk<span class="w"> </span>-Mo<span class="w"> </span>NAME,TYPE,SIZE
<span class="go">NAME   TYPE  SIZE</span>
<span class="go">vda    disk  101M</span>
<span class="go">└─vda1 part  100M</span>
<span class="go">vdb    disk  101M</span>
<span class="go">└─vdb1 part  100M</span>
<span class="go">vdc    disk  101M</span>
<span class="go">└─vdc1 part  100M</span>
<span class="go">vdd    disk  101M</span>
<span class="go">└─vdd1 part  100M</span>
</code></pre></div>

<p>We set up a RAID 5 device by assembling the four partitions:<sup id="fnref:bitmap"><a class="footnote-ref" href="#fn:bitmap">2</a></sup></p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp">$ </span>mdadm<span class="w"> </span>--create<span class="w"> </span>/dev/md0<span class="w"> </span>--level<span class="o">=</span>raid5<span class="w"> </span>--bitmap<span class="o">=</span>internal<span class="w"> </span>--raid-devices<span class="o">=</span><span class="m">4</span><span class="w"> </span><span class="se">\</span>
<span class="gp">&gt; </span><span class="w">  </span>/dev/vda1<span class="w"> </span>/dev/vdb1<span class="w"> </span>/dev/vdc1<span class="w"> </span>/dev/vdd1
<span class="gp">$ </span>lsblk<span class="w"> </span>-Mo<span class="w"> </span>NAME,TYPE,SIZE
<span class="go">    NAME          TYPE    SIZE</span>
<span class="go">    vda           disk    101M</span>
<span class="go">┌┈▶ └─vda1        part    100M</span>
<span class="go">┆   vdb           disk    101M</span>
<span class="go">├┈▶ └─vdb1        part    100M</span>
<span class="go">┆   vdc           disk    101M</span>
<span class="go">├┈▶ └─vdc1        part    100M</span>
<span class="go">┆   vdd           disk    101M</span>
<span class="go">└┬▶ └─vdd1        part    100M</span>
<span class="go"> └┈┈md0           raid5 292.5M</span>
<span class="gp">$ </span>cat<span class="w"> </span>/proc/mdstat
<span class="go">md0 : active raid5 vdd1[4] vdc1[2] vdb1[1] vda1[0]</span>
<span class="go">      299520 blocks super 1.2 level 5, 512k chunk, algorithm 2 [4/4] [UUUU]</span>
<span class="go">      bitmap: 0/1 pages [0KB], 65536KB chunk</span>
</code></pre></div>

<p>We use <abbr title="Logical Volume Manager">LVM</abbr> to create logical volumes on top of the RAID 5 device.</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp">$ </span>pvcreate<span class="w"> </span>/dev/md0
<span class="go">  Physical volume "/dev/md0" successfully created.</span>
<span class="gp">$ </span>vgcreate<span class="w"> </span>data<span class="w"> </span>/dev/md0
<span class="go">  Volume group "data" successfully created</span>
<span class="gp">$ </span>lvcreate<span class="w"> </span>-L<span class="w"> </span>100m<span class="w"> </span>-n<span class="w"> </span>bits<span class="w"> </span>data
<span class="go">  Logical volume "bits" created.</span>
<span class="gp">$ </span>lvcreate<span class="w"> </span>-L<span class="w"> </span>100m<span class="w"> </span>-n<span class="w"> </span>pieces<span class="w"> </span>data
<span class="go">  Logical volume "pieces" created.</span>
<span class="gp">$ </span>mkfs.ext4<span class="w"> </span>-q<span class="w"> </span>/dev/data/bits
<span class="gp">$ </span>mkfs.ext4<span class="w"> </span>-q<span class="w"> </span>/dev/data/pieces
<span class="gp">$ </span>lsblk<span class="w"> </span>-Mo<span class="w"> </span>NAME,TYPE,SIZE
<span class="go">    NAME          TYPE    SIZE</span>
<span class="go">    vda           disk    101M</span>
<span class="go">┌┈▶ └─vda1        part    100M</span>
<span class="go">┆   vdb           disk    101M</span>
<span class="go">├┈▶ └─vdb1        part    100M</span>
<span class="go">┆   vdc           disk    101M</span>
<span class="go">├┈▶ └─vdc1        part    100M</span>
<span class="go">┆   vdd           disk    101M</span>
<span class="go">└┬▶ └─vdd1        part    100M</span>
<span class="go"> └┈┈md0           raid5 292.5M</span>
<span class="go">    ├─data-bits   lvm     100M</span>
<span class="go">    └─data-pieces lvm     100M</span>
<span class="gp">$ </span>vgs
<span class="go">  VG   #PV #LV #SN Attr   VSize   VFree</span>
<span class="go">  data   1   2   0 wz--n- 288.00m 88.00m</span>
</code></pre></div>

<p>This gives us the following setup:</p>
<figure class="lf-fullbleed"><div class="lf-media-outer" style="width: 460px"><span class="lf-media-inner" style="padding-bottom: 169.565%"><img alt="One RAID 5 device built from four partitions from four disks of equal&#10;capacity. The RAID device is part of an LVM volume group with two logical&#10;volumes." src="https://media-bernat-ch.b-cdn.net/images/raid-md0.9a97f806e8b366.svg" width="460" height="780" class="lf-media"/></span></div><figcaption>RAID 5 setup with disks of equal capacity</figcaption></figure>
<p>We replace <code>/dev/vda</code> with a bigger disk. We add it back to the RAID 5 array
after copying the partitions from <code>/dev/vdb</code>:</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp">$ </span>cat<span class="w"> </span>/proc/mdstat
<span class="go">md0 : active (auto-read-only) raid5 vdb1[1] vdd1[4] vdc1[2]</span>
<span class="go">      299520 blocks super 1.2 level 5, 512k chunk, algorithm 2 [4/3] [_UUU]</span>
<span class="go">      bitmap: 0/1 pages [0KB], 65536KB chunk</span>
<span class="gp">$ </span>sgdisk<span class="w"> </span>--replicate<span class="o">=</span>/dev/vda<span class="w"> </span>/dev/vdb
<span class="gp">$ </span>sgdisk<span class="w"> </span>--randomize-guids<span class="w"> </span>/dev/vda
<span class="gp">$ </span>mdadm<span class="w"> </span>--manage<span class="w"> </span>/dev/md0<span class="w"> </span>--add<span class="w"> </span>/dev/vda1
<span class="gp">$ </span>cat<span class="w"> </span>/proc/mdstat
<span class="go">md0 : active raid5 vda1[5] vdb1[1] vdd1[4] vdc1[2]</span>
<span class="go">      299520 blocks super 1.2 level 5, 512k chunk, algorithm 2 [4/4] [UUUU]</span>
<span class="go">      bitmap: 0/1 pages [0KB], 65536KB chunk</span>
</code></pre></div>

<p>We do not use the additional capacity: this setup would not survive the loss of
<code>/dev/vda</code> because we have no spare capacity. We need a second disk replacement,
like <code>/dev/vdb</code>:</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp">$ </span>cat<span class="w"> </span>/proc/mdstat
<span class="go">md0 : active (auto-read-only) raid5 vda1[5] vdd1[4] vdc1[2]</span>
<span class="go">      299520 blocks super 1.2 level 5, 512k chunk, algorithm 2 [4/3] [U_UU]</span>
<span class="go">      bitmap: 0/1 pages [0KB], 65536KB chunk</span>
<span class="gp">$ </span>sgdisk<span class="w"> </span>--replicate<span class="o">=</span>/dev/vdb<span class="w"> </span>/dev/vdc
<span class="gp">$ </span>sgdisk<span class="w"> </span>--randomize-guids<span class="w"> </span>/dev/vdb
<span class="gp">$ </span>mdadm<span class="w"> </span>--manage<span class="w"> </span>/dev/md0<span class="w"> </span>--add<span class="w"> </span>/dev/vdb1
<span class="gp">$ </span>cat<span class="w"> </span>/proc/mdstat
<span class="go">md0 : active raid5 vdb1[6] vda1[5] vdd1[4] vdc1[2]</span>
<span class="go">      299520 blocks super 1.2 level 5, 512k chunk, algorithm 2 [4/4] [UUUU]</span>
<span class="go">      bitmap: 0/1 pages [0KB], 65536KB chunk</span>
</code></pre></div>

<p>We create a new RAID 1 array by using the free space on <code>/dev/vda</code> and
<code>/dev/vdb</code>:</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp">$ </span>sgdisk<span class="w"> </span>--new<span class="o">=</span><span class="m">0</span>:0:0<span class="w"> </span>-t<span class="w"> </span><span class="m">0</span>:fd00<span class="w"> </span>/dev/vda
<span class="gp">$ </span>sgdisk<span class="w"> </span>--new<span class="o">=</span><span class="m">0</span>:0:0<span class="w"> </span>-t<span class="w"> </span><span class="m">0</span>:fd00<span class="w"> </span>/dev/vdb
<span class="gp">$ </span>mdadm<span class="w"> </span>--create<span class="w"> </span>/dev/md1<span class="w"> </span>--level<span class="o">=</span>raid1<span class="w"> </span>--bitmap<span class="o">=</span>internal<span class="w"> </span>--raid-devices<span class="o">=</span><span class="m">2</span><span class="w"> </span><span class="se">\</span>
<span class="gp">&gt; </span><span class="w">  </span>/dev/vda2<span class="w"> </span>/dev/vdb2
<span class="gp">$ </span>cat<span class="w"> </span>/proc/mdstat
<span class="go">md1 : active raid1 vdb2[1] vda2[0]</span>
<span class="go">      101312 blocks super 1.2 [2/2] [UU]</span>
<span class="go">      bitmap: 0/1 pages [0KB], 65536KB chunk</span>

<span class="go">md0 : active raid5 vdb1[6] vda1[5] vdd1[4] vdc1[2]</span>
<span class="go">      299520 blocks super 1.2 level 5, 512k chunk, algorithm 2 [4/4] [UUUU]</span>
<span class="go">      bitmap: 0/1 pages [0KB], 65536KB chunk</span>
</code></pre></div>

<p>We add <code>/dev/md1</code> to the volume group:</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp">$ </span>pvcreate<span class="w"> </span>/dev/md1
<span class="go">  Physical volume "/dev/md1" successfully created.</span>
<span class="gp">$ </span>vgextend<span class="w"> </span>data<span class="w"> </span>/dev/md1
<span class="go">  Volume group "data" successfully extended</span>
<span class="gp">$ </span>vgs
<span class="go">  VG   #PV #LV #SN Attr   VSize   VFree</span>
<span class="go">  data   2   2   0 wz--n- 384.00m 184.00m</span>
<span class="gp">$  </span>lsblk<span class="w"> </span>-Mo<span class="w"> </span>NAME,TYPE,SIZE
<span class="go">       NAME          TYPE    SIZE</span>
<span class="go">       vda           disk    201M</span>
<span class="go">   ┌┈▶ ├─vda1        part    100M</span>
<span class="go">┌┈▶┆   └─vda2        part    100M</span>
<span class="go">┆  ┆   vdb           disk    201M</span>
<span class="go">┆  ├┈▶ ├─vdb1        part    100M</span>
<span class="go">└┬▶┆   └─vdb2        part    100M</span>
<span class="go"> └┈┆┈┈┈md1           raid1  98.9M</span>
<span class="go">   ┆   vdc           disk    101M</span>
<span class="go">   ├┈▶ └─vdc1        part    100M</span>
<span class="go">   ┆   vdd           disk    101M</span>
<span class="go">   └┬▶ └─vdd1        part    100M</span>
<span class="go">    └┈┈md0           raid5 292.5M</span>
<span class="go">       ├─data-bits   lvm     100M</span>
<span class="go">       └─data-pieces lvm     100M</span>
</code></pre></div>

<p>This gives us the following setup:<sup id="fnref:lsblk"><a class="footnote-ref" href="#fn:lsblk">3</a></sup></p>
<figure class="lf-fullbleed"><div class="lf-media-outer" style="width: 880px"><span class="lf-media-inner" style="padding-bottom: 88.636%"><img alt="One RAID 5 device built from four partitions and one RAID 1 device built from&#10;two partitions. The two last disks are smaller. The two RAID devices are part of&#10;a single LVM volume group." src="https://media-bernat-ch.b-cdn.net/images/raid-md0-4-md1-2.346353151ddf57.svg" width="880" height="780" class="lf-media"/></span></div><figcaption>Setup mixing both RAID 1 and RAID 5</figcaption></figure>
<p>We extend our capacity further by replacing <code>/dev/vdc</code>:</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp">$ </span>cat<span class="w"> </span>/proc/mdstat
<span class="go">md1 : active (auto-read-only) raid1 vda2[0] vdb2[1]</span>
<span class="go">      101312 blocks super 1.2 [2/2] [UU]</span>
<span class="go">      bitmap: 0/1 pages [0KB], 65536KB chunk</span>

<span class="go">md0 : active (auto-read-only) raid5 vda1[5] vdd1[4] vdb1[6]</span>
<span class="go">      299520 blocks super 1.2 level 5, 512k chunk, algorithm 2 [4/3] [UU_U]</span>
<span class="go">      bitmap: 0/1 pages [0KB], 65536KB chunk</span>
<span class="gp">$ </span>sgdisk<span class="w"> </span>--replicate<span class="o">=</span>/dev/vdc<span class="w"> </span>/dev/vdb
<span class="gp">$ </span>sgdisk<span class="w"> </span>--randomize-guids<span class="w"> </span>/dev/vdc
<span class="gp">$ </span>mdadm<span class="w"> </span>--manage<span class="w"> </span>/dev/md0<span class="w"> </span>--add<span class="w"> </span>/dev/vdc1
<span class="gp">$ </span>cat<span class="w"> </span>/proc/mdstat
<span class="go">md1 : active (auto-read-only) raid1 vda2[0] vdb2[1]</span>
<span class="go">      101312 blocks super 1.2 [2/2] [UU]</span>
<span class="go">      bitmap: 0/1 pages [0KB], 65536KB chunk</span>

<span class="go">md0 : active raid5 vdc1[7] vda1[5] vdd1[4] vdb1[6]</span>
<span class="go">      299520 blocks super 1.2 level 5, 512k chunk, algorithm 2 [4/4] [UUUU]</span>
<span class="go">      bitmap: 0/1 pages [0KB], 65536KB chunk</span>
</code></pre></div>

<p>Then, we convert <code>/dev/md1</code> from RAID 1 to RAID 5:</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp">$ </span>mdadm<span class="w"> </span>--grow<span class="w"> </span>/dev/md1<span class="w"> </span>--level<span class="o">=</span><span class="m">5</span><span class="w"> </span>--raid-devices<span class="o">=</span><span class="m">3</span><span class="w"> </span>--add<span class="w"> </span>/dev/vdc2
<span class="go">mdadm: level of /dev/md1 changed to raid5</span>
<span class="go">mdadm: added /dev/vdc2</span>
<span class="gp">$ </span>cat<span class="w"> </span>/proc/mdstat
<span class="go">md1 : active raid5 vdc2[2] vda2[0] vdb2[1]</span>
<span class="go">      202624 blocks super 1.2 level 5, 64k chunk, algorithm 2 [3/3] [UUU]</span>
<span class="go">      bitmap: 0/1 pages [0KB], 65536KB chunk</span>

<span class="go">md0 : active raid5 vdc1[7] vda1[5] vdd1[4] vdb1[6]</span>
<span class="go">      299520 blocks super 1.2 level 5, 512k chunk, algorithm 2 [4/4] [UUUU]</span>
<span class="go">      bitmap: 0/1 pages [0KB], 65536KB chunk</span>
<span class="gp">$ </span>pvresize<span class="w"> </span>/dev/md1
<span class="gp">$ </span>vgs
<span class="go">  VG   #PV #LV #SN Attr   VSize   VFree</span>
<span class="go">  data   2   2   0 wz--n- 482.00m 282.00m</span>
</code></pre></div>

<p>This gives us the following layout:</p>
<figure class="lf-fullbleed"><div class="lf-media-outer" style="width: 880px"><span class="lf-media-inner" style="padding-bottom: 88.636%"><img alt="Two RAID 5 devices built from four disks of different sizes. The last disk is&#10;smaller and contains only one partition, while the others have two partitions:&#10;one for /dev/md0 and one for /dev/md1. The two RAID devices are part of a single&#10;LVM volume group." src="https://media-bernat-ch.b-cdn.net/images/raid-md0-4-md1-3.d53108978a7f1f.svg" width="880" height="780" class="lf-media"/></span></div><figcaption>RAID 5 setup with mixed-capacity disks using partitions and LVM</figcaption></figure>
<p>We further extend our capacity by replacing <code>/dev/vdd</code>:</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp">$ </span>cat<span class="w"> </span>/proc/mdstat
<span class="go">md0 : active (auto-read-only) raid5 vda1[5] vdc1[7] vdb1[6]</span>
<span class="go">      299520 blocks super 1.2 level 5, 512k chunk, algorithm 2 [4/3] [UUU_]</span>
<span class="go">      bitmap: 0/1 pages [0KB], 65536KB chunk</span>

<span class="go">md1 : active (auto-read-only) raid5 vda2[0] vdc2[2] vdb2[1]</span>
<span class="go">      202624 blocks super 1.2 level 5, 64k chunk, algorithm 2 [3/3] [UUU]</span>
<span class="go">      bitmap: 0/1 pages [0KB], 65536KB chunk</span>
<span class="gp">$ </span>sgdisk<span class="w"> </span>--replicate<span class="o">=</span>/dev/vdd<span class="w"> </span>/dev/vdc
<span class="gp">$ </span>sgdisk<span class="w"> </span>--randomize-guids<span class="w"> </span>/dev/vdd
<span class="gp">$ </span>mdadm<span class="w"> </span>--manage<span class="w"> </span>/dev/md0<span class="w"> </span>--add<span class="w"> </span>/dev/vdd1
<span class="gp">$ </span>cat<span class="w"> </span>/proc/mdstat
<span class="go">md0 : active raid5 vdd1[4] vda1[5] vdc1[7] vdb1[6]</span>
<span class="go">      299520 blocks super 1.2 level 5, 512k chunk, algorithm 2 [4/4] [UUUU]</span>
<span class="go">      bitmap: 0/1 pages [0KB], 65536KB chunk</span>

<span class="go">md1 : active (auto-read-only) raid5 vda2[0] vdc2[2] vdb2[1]</span>
<span class="go">      202624 blocks super 1.2 level 5, 64k chunk, algorithm 2 [3/3] [UUU]</span>
<span class="go">      bitmap: 0/1 pages [0KB], 65536KB chunk</span>
</code></pre></div>

<p>We grow the second RAID 5 array:</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp">$ </span>mdadm<span class="w"> </span>--grow<span class="w"> </span>/dev/md1<span class="w"> </span>--raid-devices<span class="o">=</span><span class="m">4</span><span class="w"> </span>--add<span class="w"> </span>/dev/vdd2
<span class="go">mdadm: added /dev/vdd2</span>
<span class="gp">$ </span>cat<span class="w"> </span>/proc/mdstat
<span class="go">md0 : active raid5 vdd1[4] vda1[5] vdc1[7] vdb1[6]</span>
<span class="go">      299520 blocks super 1.2 level 5, 512k chunk, algorithm 2 [4/4] [UUUU]</span>
<span class="go">      bitmap: 0/1 pages [0KB], 65536KB chunk</span>

<span class="go">md1 : active raid5 vdd2[3] vda2[0] vdc2[2] vdb2[1]</span>
<span class="go">      303936 blocks super 1.2 level 5, 64k chunk, algorithm 2 [4/4] [UUUU]</span>
<span class="go">      bitmap: 0/1 pages [0KB], 65536KB chunk</span>
<span class="gp">$ </span>pvresize<span class="w"> </span>/dev/md1
<span class="gp">$ </span>vgs
<span class="go">  VG   #PV #LV #SN Attr   VSize   VFree</span>
<span class="go">  data   2   2   0 wz--n- 580.00m 380.00m</span>
<span class="gp">$ </span>lsblk<span class="w"> </span>-Mo<span class="w"> </span>NAME,TYPE,SIZE
<span class="go">       NAME          TYPE    SIZE</span>
<span class="go">       vda           disk    201M</span>
<span class="go">   ┌┈▶ ├─vda1        part    100M</span>
<span class="go">┌┈▶┆   └─vda2        part    100M</span>
<span class="go">┆  ┆   vdb           disk    201M</span>
<span class="go">┆  ├┈▶ ├─vdb1        part    100M</span>
<span class="go">├┈▶┆   └─vdb2        part    100M</span>
<span class="go">┆  ┆   vdc           disk    201M</span>
<span class="go">┆  ├┈▶ ├─vdc1        part    100M</span>
<span class="go">├┈▶┆   └─vdc2        part    100M</span>
<span class="go">┆  ┆   vdd           disk    301M</span>
<span class="go">┆  └┬▶ ├─vdd1        part    100M</span>
<span class="go">└┬▶ ┆  └─vdd2        part    100M</span>
<span class="go"> ┆  └┈┈md0           raid5 292.5M</span>
<span class="go"> ┆     ├─data-bits   lvm     100M</span>
<span class="go"> ┆     └─data-pieces lvm     100M</span>
<span class="go"> └┈┈┈┈┈md1           raid5 296.8M</span>
</code></pre></div>

<p>You can continue by replacing each disk one by one using the same steps. ♾️</p>
<div class="footnote">
<hr/>
<ol>
<li id="fn:shr">
<p>This is the same approach <a href="https://www.synology.com" title="Synology">Synology</a> uses for its <a href="https://kb.synology.com/en-us/DSM/tutorial/What_is_Synology_Hybrid_RAID_SHR" title="What Is Synology Hybrid RAID (SHR)?">Hybrid RAID</a> and
what the <a href="https://forum.openmediavault.org/index.php?thread/53652-howto-build-an-shr-sliced-hybrid-raid/" title="HowTo build an SHR -- Sliced Hybrid Raid">OpenMediaVault</a> community calls a “Sliced Hybrid RAID.” <a class="footnote-backref" href="#fnref:shr" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:bitmap">
<p>Write-intent bitmaps speed up recovery of the RAID array after a
power failure by marking unsynchronized regions as dirty. They have an
<a href="https://blog.liw.fi/posts/write-intent-bitmaps/" title="Write intent bitmaps with Linux software RAID">impact on performance</a>, but I did not measure it myself. An alternative
is to use a <a href="https://docs.kernel.org/driver-api/md/raid5-ppl.html" title="Partial Parity Log">partial parity log</a> with <code>--consistency-policy=ppl</code>. <a class="footnote-backref" href="#fnref:bitmap" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
<li id="fn:lsblk">
<p>In the <code>lsblk</code> output, <code>/dev/md1</code> appears unused because the logical
volumes do not use any space from it yet. Once you create more logical
volumes or extend them, <code>lsblk</code> will reflect the usage. <a class="footnote-backref" href="#fnref:lsblk" title="Jump back to footnote 3 in the text">↩</a></p>
</li>
</ol>
</div>
      </div>
    </content>
  </entry>
  <entry>
    <title type="html">Using eBPF to load-balance traffic across UDP sockets with Go</title>
    <author><name>Vincent Bernat</name></author>
    <link href="https://vincent.bernat.ch/en/blog/2026-reuseport-ebpf-go" rel="alternate"/>
    <link href="https://vincent.bernat.ch/en/blog/2026-reuseport-ebpf-go#isso-thread" rel="replies" type="text/html"/>
    <updated>2026-01-05T07:51:52Z</updated>
    <id>http://www.luffy.cx/en/blog/2026-reuseport-ebpf-go.html</id>

    <content type="xhtml">
      <div xmlns="http://www.w3.org/1999/xhtml">
<p><a href="/en/blog/2022-akvorado-flow-collector" title="Akvorado: a flow collector, enricher, and visualizer">Akvorado</a> collects <a href="https://www.rfc-editor.org/rfc/rfc3176" title="RFC 3176: InMon Corporation's sFlow: A Method for Monitoring Traffic in Switched and Routed Networks">sFlow</a> and <a href="https://www.rfc-editor.org/rfc/rfc7011" title="RFC 7011: Specification of the IP Flow Information Export (IPFIX) Protocol for the Exchange of Flow Information">IPFIX</a> flows over UDP. Because UDP does
not retransmit lost packets, it needs to process them quickly. Akvorado runs
several workers listening to the same port. The kernel should load-balance
received packets fairly between these workers. However, this does not work as
expected. A couple of workers exhibit high packet loss:</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp">$ </span>curl<span class="w"> </span>-s<span class="w"> </span><span class="m">127</span>.0.0.1:8080/api/v0/inlet/metrics<span class="w"> </span><span class="se">\</span>
<span class="gp">&gt; </span><span class="p">|</span><span class="w"> </span>sed<span class="w"> </span>-n<span class="w"> </span>s/akvorado_inlet_flow_input_udp_in_dropped//p
<span class="go">packets_total{listener="0.0.0.0:2055",worker="0"} 0</span>
<span class="go">packets_total{listener="0.0.0.0:2055",worker="1"} 0</span>
<span class="go">packets_total{listener="0.0.0.0:2055",worker="2"} 0</span>
<span class="go">packets_total{listener="0.0.0.0:2055",worker="3"} 1.614933572278264e+15</span>
<span class="go">packets_total{listener="0.0.0.0:2055",worker="4"} 0</span>
<span class="go">packets_total{listener="0.0.0.0:2055",worker="5"} 0</span>
<span class="go">packets_total{listener="0.0.0.0:2055",worker="6"} 9.59964121598348e+14</span>
<span class="go">packets_total{listener="0.0.0.0:2055",worker="7"} 0</span>
</code></pre></div>

<p>eBPF can help by implementing an alternate balancing algorithm. 🐝</p>
<div class="toc">
<ul>
<li><a href="#options-for-load-balancing">Options for load-balancing</a><ul>
<li><a href="#so_reuseport-option">SO_REUSEPORT option</a></li>
<li><a href="#so_attach_reuseport_ebpf-option">SO_ATTACH_REUSEPORT_EBPF option</a></li>
</ul>
</li>
<li><a href="#load-balancing-with-ebpf-and-go">Load-balancing with eBPF and Go</a><ul>
<li><a href="#ebpf-program-in-c">eBPF program in C</a><ul>
<li><a href="#header-files">Header files</a></li>
<li><a href="#compilation">Compilation</a></li>
</ul>
</li>
<li><a href="#usage-from-go">Usage from Go</a></li>
<li><a href="#graceful-restart">Graceful restart</a></li>
</ul>
</li>
<li><a href="#addendum">Addendum</a></li>
</ul>
</div>
<h1 id="options-for-load-balancing">Options for load-balancing</h1>
<p>There are three methods to load-balance UDP packets across workers:</p>
<ol>
<li>One worker receives the packets and dispatches them to the other workers.</li>
<li>All workers share the same socket.</li>
<li>Each worker has its own socket, listening to the same port, with the
   <code>SO_REUSEPORT</code> socket option.</li>
</ol>
<h2 id="so_reuseport-option"><code>SO_REUSEPORT</code> option</h2>
<p>Tom Hebert <a href="https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git/commit/?id=c617f398edd4db2b8567a28e899a88f8f574798d" title="Merge commit adding the SO_REUSEPORT option">added</a> the <code>SO_REUSEPORT</code> socket option in Linux 3.9. The
cover letter for his patch series explains why this new option is better than
the two existing ones from a performance point of view:</p>
<blockquote>
<p><code>SO_REUSEPORT</code> allows multiple listener sockets to be bound to the same port.
[…] Received packets are distributed to multiple sockets bound to the same
port using a 4-tuple hash.</p>
<p>The motivating case for <code>SO_RESUSEPORT</code> in TCP would be something like a web
server binding to port 80 running with multiple threads, where each thread
might have it’s own listener socket. This could be done as an alternative to
other models:</p>
<ol>
<li>have one listener thread which dispatches completed connections to workers, or</li>
<li>accept on a single listener socket from multiple threads.</li>
</ol>
<p>In case #1, the listener thread can easily become the bottleneck with high
connection turn-over rate. In case #2, the proportion of connections accepted
per thread tends to be uneven under high connection load. […] We have seen the
disproportion to be as high as 3:1 ratio between thread accepting most
connections and the one accepting the fewest. With <code>SO_REUSEPORT</code> the
distribution is uniform.</p>
<p>The motivating case for <code>SO_REUSEPORT</code> in UDP would be something like a DNS
server. An alternative would be to receive on the same socket from multiple
threads. As in the case of TCP, the load across these threads tends to be
disproportionate and we also see a lot of contection on the socket lock.</p>
</blockquote>
<p>Akvorado uses the <code>SO_REUSEPORT</code> option to dispatch the packets across the
workers. However, because the distribution uses a 4-tuple hash, a single socket
handles all the flows from one exporter.</p>
<h2 id="so_attach_reuseport_ebpf-option"><code>SO_ATTACH_REUSEPORT_EBPF</code> option</h2>
<p>In Linux 4.5, Craig Gallek <a href="https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git/commit/?id=538950a1b7527a0a52ccd9337e3fcd304f027f13" title="soreuseport: setsockopt SO_ATTACH_REUSEPORT_[CE]BPF">added</a> the <code>SO_ATTACH_REUSEPORT_EBPF</code>
option to attach an eBPF program to select the target UDP socket. In Linux 4.6,
he <a href="https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git/commit/?id=c125e80b88687b25b321795457309eaaee4bf270" title="soreuseport: fast reuseport TCP socket selection">extended</a> it to support TCP. The <a href="https://manpages.debian.org/socket.7.html" title="Manual page for socket(7)"><code>socket(7)</code></a> manual page
documents this mechanism:<sup id="fnref:accuracy"><a class="footnote-ref" href="#fn:accuracy">1</a></sup></p>
<blockquote>
<p>The BPF program must return an index between 0 and N-1 representing the socket
which should receive the packet (where N is the number of sockets in the
group). If the BPF program returns an invalid index, socket selection will
fall back to the plain <code>SO_REUSEPORT</code> mechanism.</p>
</blockquote>
<p>In Linux 4.19, Martin KaFai Lau <a href="https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git/commit/?id=2dbb9b9e6df67d444fbe425c7f6014858d337adf" title="bpf: Introduce BPF_PROG_TYPE_SK_REUSEPORT">added</a> the
<a href="https://docs.ebpf.io/linux/program-type/BPF_PROG_TYPE_SK_REUSEPORT/" title="eBPF Docs: program type BPF_PROG_TYPE_SK_REUSEPORT"><code>BPF_PROG_TYPE_SK_REUSEPORT</code></a> program type. Such an eBPF program
selects the socket from a <code>BPF_MAP_TYPE_REUSEPORT_ARRAY</code> map instead. This new
approach is more reliable when switching target sockets from one instance to
another—for example, when upgrading, a new instance can add its sockets and
remove the old ones.</p>
<h1 id="load-balancing-with-ebpf-and-go">Load-balancing with eBPF and Go</h1>
<p>Altering the load-balancing algorithm for a group of sockets requires two steps:</p>
<ol>
<li>write and compile an eBPF program in C,<sup id="fnref:rust"><a class="footnote-ref" href="#fn:rust">2</a></sup> and</li>
<li>load it and attach it in Go.</li>
</ol>
<h2 id="ebpf-program-in-c">eBPF program in C</h2>
<p>A simple load-balancing algorithm is to randomly choose the destination socket.
The kernel provides the <code>bpf_get_prandom_u32()</code> helper function to get a
pseudo-random number.</p>
<div class="language-c codehilite"><pre><span/><code><span class="k">volatile</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="n">__u32</span><span class="w"> </span><span class="n">num_sockets</span><span class="p">;</span><span class="w"> </span><span class="c1">// ❶</span>

<span class="k">struct</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="n">__uint</span><span class="p">(</span><span class="n">type</span><span class="p">,</span><span class="w"> </span><span class="n">BPF_MAP_TYPE_REUSEPORT_SOCKARRAY</span><span class="p">);</span>
<span class="w">    </span><span class="n">__type</span><span class="p">(</span><span class="n">key</span><span class="p">,</span><span class="w"> </span><span class="n">__u32</span><span class="p">);</span>
<span class="w">    </span><span class="n">__type</span><span class="p">(</span><span class="n">value</span><span class="p">,</span><span class="w"> </span><span class="n">__u64</span><span class="p">);</span>
<span class="w">    </span><span class="n">__uint</span><span class="p">(</span><span class="n">max_entries</span><span class="p">,</span><span class="w"> </span><span class="mi">256</span><span class="p">);</span>
<span class="p">}</span><span class="w"> </span><span class="n">socket_map</span><span class="w"> </span><span class="n">SEC</span><span class="p">(</span><span class="s">".maps"</span><span class="p">);</span><span class="w"> </span><span class="c1">// ❷</span>

<span class="n">SEC</span><span class="p">(</span><span class="s">"sk_reuseport"</span><span class="p">)</span>
<span class="kt">int</span><span class="w"> </span><span class="n">reuseport_balance_prog</span><span class="p">(</span><span class="k">struct</span><span class="w"> </span><span class="nc">sk_reuseport_md</span><span class="w"> </span><span class="o">*</span><span class="n">reuse_md</span><span class="p">)</span>
<span class="p">{</span>
<span class="w">    </span><span class="n">__u32</span><span class="w"> </span><span class="n">index</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">bpf_get_prandom_u32</span><span class="p">()</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="n">num_sockets</span><span class="p">;</span><span class="w"> </span><span class="c1">// ❸</span>
<span class="w">    </span><span class="n">bpf_sk_select_reuseport</span><span class="p">(</span><span class="n">reuse_md</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">socket_map</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">index</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w"> </span><span class="c1">// ❹</span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="n">SK_PASS</span><span class="p">;</span><span class="w"> </span><span class="c1">// ❺</span>
<span class="p">}</span>

<span class="kt">char</span><span class="w"> </span><span class="n">_license</span><span class="p">[]</span><span class="w"> </span><span class="n">SEC</span><span class="p">(</span><span class="s">"license"</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">"GPL"</span><span class="p">;</span>
</code></pre></div>

<p>In ❶, we declare a volatile constant for the number of sockets in the group. We
will initialize this constant before loading the eBPF program into the kernel.
In ❷, we define the socket map. We will populate it with the socket file
descriptors. In ❸, we randomly select the index of the target socket.<sup id="fnref:random"><a class="footnote-ref" href="#fn:random">3</a></sup>
In ❹, we invoke the <code>bpf_sk_select_reuseport()</code> helper to record our decision.
Finally, in ❺, we accept the packet.</p>
<h3 id="header-files">Header files</h3>
<p>If you compile the C source with <code>clang</code>, you get errors due to missing headers.
The recommended way to solve this is to generate a <code>vmlinux.h</code> file with
<code>bpftool</code>:</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp">$ </span>bpftool<span class="w"> </span>btf<span class="w"> </span>dump<span class="w"> </span>file<span class="w"> </span>/sys/kernel/btf/vmlinux<span class="w"> </span>format<span class="w"> </span>c<span class="w"> </span>&gt;<span class="w"> </span>vmlinux.h
</code></pre></div>

<p>Then, include the following headers:<sup id="fnref:kernel"><a class="footnote-ref" href="#fn:kernel">4</a></sup></p>
<div class="language-c codehilite"><pre><span/><code><span class="cp">#include</span><span class="w"> </span><span class="cpf">"vmlinux.h"</span>
<span class="cp">#include</span><span class="w"> </span><span class="cpf">&lt;bpf/bpf_helpers.h&gt;</span>
</code></pre></div>

<p>For my 6.17 kernel, the generated <code>vmlinux.h</code> is quite big: 2.7 MiB. Moreover,
<code>bpf/bpf_helpers.h</code> is shipped with libbpf. This adds another dependency for
users. As the eBPF program is quite small, I prefer to put the strict minimum in
<code>vmlinux.h</code> by <a href="https://github.com/vincentbernat/ebpf-reuseport-go/blob/main/vmlinux.h" title="vmlinux.h file">cherry-picking the definitions I need</a>.</p>
<h3 id="compilation">Compilation</h3>
<p>The <a href="https://ebpf-go.dev/" title="The eBPF Library for Go">eBPF Library for Go</a> ships <code>bpf2go</code>, a tool to compile eBPF programs and
to generate some scaffolding code. We create a <code>gen.go</code> file with the following
content:</p>
<div class="language-go codehilite"><pre><span/><code><span class="kn">package</span><span class="w"> </span><span class="nx">main</span>

<span class="c1">//go:generate go tool bpf2go -tags linux reuseport reuseport_kern.c</span>
</code></pre></div>

<p>After running <code>go generate ./...</code>, we can inspect the resulting objects with
<code>readelf</code> and <code>llvm-objdump</code>:</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp">$ </span>readelf<span class="w"> </span>-S<span class="w"> </span>reuseport_bpfeb.o
<span class="go">There are 14 section headers, starting at offset 0x840:</span>
<span class="go">  [Nr] Name              Type             Address           Offset</span>
<span class="go">[…]</span>
<span class="go">  [ 3] sk_reuseport      PROGBITS         0000000000000000  00000040</span>
<span class="go">  [ 6] .maps             PROGBITS         0000000000000000  000000c8</span>
<span class="go">  [ 7] license           PROGBITS         0000000000000000  000000e8</span>
<span class="go">[…]</span>
<span class="gp">$ </span>llvm-objdump<span class="w"> </span>-S<span class="w"> </span>reuseport_bpfeb.o
<span class="go">reuseport_bpfeb.o:  file format elf64-bpf</span>
<span class="go">Disassembly of section sk_reuseport:</span>
<span class="go">0000000000000000 &lt;reuseport_balance_prog&gt;:</span>
<span class="go">; {</span>
<span class="go">       0:   bf 61 00 00 00 00 00 00     r6 = r1</span>
<span class="go">;     __u32 index = bpf_get_prandom_u32() % num_sockets;</span>
<span class="go">       1:   85 00 00 00 00 00 00 07     call 0x7</span>
<span class="go">[…]</span>
</code></pre></div>

<h2 id="usage-from-go">Usage from Go</h2>
<p>Let’s set up 10 workers listening to the same port.<sup id="fnref:listenaddr"><a class="footnote-ref" href="#fn:listenaddr">5</a></sup> Each socket
enables the <code>SO_REUSEPORT</code> option before binding:<sup id="fnref:s1"><a class="footnote-ref" href="#fn:s1">6</a></sup></p>
<div class="language-go codehilite"><pre><span/><code><span class="kd">var</span><span class="w"> </span><span class="p">(</span>
<span class="w">    </span><span class="nx">err</span><span class="w"> </span><span class="kt">error</span>
<span class="w">    </span><span class="nx">fds</span><span class="w"> </span><span class="p">[]</span><span class="kt">uintptr</span>
<span class="w">    </span><span class="nx">conns</span><span class="w"> </span><span class="p">[]</span><span class="o">*</span><span class="nx">net</span><span class="p">.</span><span class="nx">UDPConn</span>
<span class="p">)</span>
<span class="nx">workers</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">10</span>
<span class="nx">listenAddr</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="s">"127.0.0.1:0"</span>
<span class="nx">listenConfig</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">net</span><span class="p">.</span><span class="nx">ListenConfig</span><span class="p">{</span>
<span class="w">    </span><span class="nx">Control</span><span class="p">:</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="nx">c</span><span class="w"> </span><span class="nx">syscall</span><span class="p">.</span><span class="nx">RawConn</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="nx">c</span><span class="p">.</span><span class="nx">Control</span><span class="p">(</span><span class="kd">func</span><span class="p">(</span><span class="nx">fd</span><span class="w"> </span><span class="kt">uintptr</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="hll"><span class="w">            </span><span class="nx">err</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">unix</span><span class="p">.</span><span class="nx">SetsockoptInt</span><span class="p">(</span><span class="kt">int</span><span class="p">(</span><span class="nx">fd</span><span class="p">),</span><span class="w"> </span><span class="nx">unix</span><span class="p">.</span><span class="nx">SOL_SOCKET</span><span class="p">,</span><span class="w"> </span><span class="nx">unix</span><span class="p">.</span><span class="nx">SO_REUSEPORT</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span>
</span><span class="w">            </span><span class="nx">fds</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">append</span><span class="p">(</span><span class="nx">fds</span><span class="p">,</span><span class="w"> </span><span class="nx">fd</span><span class="p">)</span>
<span class="w">        </span><span class="p">})</span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">err</span>
<span class="w">    </span><span class="p">},</span>
<span class="p">}</span>
<span class="k">for</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">workers</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="nx">pconn</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">listenConfig</span><span class="p">.</span><span class="nx">ListenPacket</span><span class="p">(</span><span class="nx">t</span><span class="p">.</span><span class="nx">Context</span><span class="p">(),</span><span class="w"> </span><span class="s">"udp"</span><span class="p">,</span><span class="w"> </span><span class="nx">listenAddr</span><span class="p">)</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="nx">t</span><span class="p">.</span><span class="nx">Fatalf</span><span class="p">(</span><span class="s">"ListenPacket() error:\n%+v"</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span>
<span class="w">    </span><span class="p">}</span>
<span class="w">    </span><span class="nx">udpConn</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">pconn</span><span class="p">.(</span><span class="o">*</span><span class="nx">net</span><span class="p">.</span><span class="nx">UDPConn</span><span class="p">)</span>
<span class="w">    </span><span class="nx">listenAddr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">udpConn</span><span class="p">.</span><span class="nx">LocalAddr</span><span class="p">().</span><span class="nx">String</span><span class="p">()</span>
<span class="w">    </span><span class="nx">conns</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">append</span><span class="p">(</span><span class="nx">conns</span><span class="p">,</span><span class="w"> </span><span class="nx">udpConn</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div>

<p>The second step is to load the eBPF program, initialize the <code>num_sockets</code>
variable, populate the socket map, and attach the program to the first
socket.<sup id="fnref:s2"><a class="footnote-ref" href="#fn:s2">7</a></sup></p>
<div class="language-go codehilite"><pre><span/><code><span class="c1">// Load the eBPF collection.</span>
<span class="nx">spec</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">loadReuseport</span><span class="p">()</span>
<span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="nx">t</span><span class="p">.</span><span class="nx">Fatalf</span><span class="p">(</span><span class="s">"loadVariables() error:\n%+v"</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span>
<span class="p">}</span>

<span class="c1">// Set "num_sockets" global variable to the number of file descriptors we will register</span>
<span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">spec</span><span class="p">.</span><span class="nx">Variables</span><span class="p">[</span><span class="s">"num_sockets"</span><span class="p">].</span><span class="nx">Set</span><span class="p">(</span><span class="kt">uint32</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="nx">fds</span><span class="p">)));</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="nx">t</span><span class="p">.</span><span class="nx">Fatalf</span><span class="p">(</span><span class="s">"NumSockets.Set() error:\n%+v"</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span>
<span class="p">}</span>

<span class="c1">// Load the map and the program into the kernel.</span>
<span class="kd">var</span><span class="w"> </span><span class="nx">objs</span><span class="w"> </span><span class="nx">reuseportObjects</span>
<span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">spec</span><span class="p">.</span><span class="nx">LoadAndAssign</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">objs</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="nx">t</span><span class="p">.</span><span class="nx">Fatalf</span><span class="p">(</span><span class="s">"loadReuseportObjects() error:\n%+v"</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="nx">t</span><span class="p">.</span><span class="nx">Cleanup</span><span class="p">(</span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">objs</span><span class="p">.</span><span class="nx">Close</span><span class="p">()</span><span class="w"> </span><span class="p">})</span>

<span class="c1">// Assign the file descriptors to the socket map.</span>
<span class="k">for</span><span class="w"> </span><span class="nx">worker</span><span class="p">,</span><span class="w"> </span><span class="nx">fd</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">fds</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">objs</span><span class="p">.</span><span class="nx">reuseportMaps</span><span class="p">.</span><span class="nx">SocketMap</span><span class="p">.</span><span class="nx">Put</span><span class="p">(</span><span class="kt">uint32</span><span class="p">(</span><span class="nx">worker</span><span class="p">),</span><span class="w"> </span><span class="kt">uint64</span><span class="p">(</span><span class="nx">fd</span><span class="p">));</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="nx">t</span><span class="p">.</span><span class="nx">Fatalf</span><span class="p">(</span><span class="s">"SocketMap.Put() error:\n%+v"</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span>
<span class="w">    </span><span class="p">}</span>
<span class="p">}</span>

<span class="c1">// Attach the eBPF program to the first socket.</span>
<span class="nx">socketFD</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="kt">int</span><span class="p">(</span><span class="nx">fds</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="nx">progFD</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">objs</span><span class="p">.</span><span class="nx">reuseportPrograms</span><span class="p">.</span><span class="nx">ReuseportBalanceProg</span><span class="p">.</span><span class="nx">FD</span><span class="p">()</span>
<span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">unix</span><span class="p">.</span><span class="nx">SetsockoptInt</span><span class="p">(</span><span class="nx">socketFD</span><span class="p">,</span><span class="w"> </span><span class="nx">unix</span><span class="p">.</span><span class="nx">SOL_SOCKET</span><span class="p">,</span><span class="w"> </span><span class="nx">unix</span><span class="p">.</span><span class="nx">SO_ATTACH_REUSEPORT_EBPF</span><span class="p">,</span><span class="w"> </span><span class="nx">progFD</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="nx">t</span><span class="p">.</span><span class="nx">Fatalf</span><span class="p">(</span><span class="s">"SetsockoptInt() error:\n%+v"</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div>

<p>We are now ready to process incoming packets. Each worker is a goroutine
incrementing a counter for each received packet:<sup id="fnref:s3"><a class="footnote-ref" href="#fn:s3">8</a></sup></p>
<div class="language-go codehilite"><pre><span/><code><span class="kd">var</span><span class="w"> </span><span class="nx">wg</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">WaitGroup</span>
<span class="nx">receivedPackets</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">([]</span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="nx">workers</span><span class="p">)</span>
<span class="k">for</span><span class="w"> </span><span class="nx">worker</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">workers</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="nx">conn</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">conns</span><span class="p">[</span><span class="nx">worker</span><span class="p">]</span>
<span class="w">    </span><span class="nx">packets</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">receivedPackets</span><span class="p">[</span><span class="nx">worker</span><span class="p">]</span>
<span class="w">    </span><span class="nx">wg</span><span class="p">.</span><span class="nx">Go</span><span class="p">(</span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="nx">payload</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">([]</span><span class="kt">byte</span><span class="p">,</span><span class="w"> </span><span class="mi">9000</span><span class="p">)</span>
<span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="p">{</span>
<span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nx">Read</span><span class="p">(</span><span class="nx">payload</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span>
<span class="w">                </span><span class="k">if</span><span class="w"> </span><span class="nx">errors</span><span class="p">.</span><span class="nx">Is</span><span class="p">(</span><span class="nx">err</span><span class="p">,</span><span class="w"> </span><span class="nx">net</span><span class="p">.</span><span class="nx">ErrClosed</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">                    </span><span class="k">return</span>
<span class="w">                </span><span class="p">}</span>
<span class="w">                </span><span class="nx">t</span><span class="p">.</span><span class="nx">Logf</span><span class="p">(</span><span class="s">"Read() error:\n%+v"</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span>
<span class="w">            </span><span class="p">}</span>
<span class="w">            </span><span class="o">*</span><span class="nx">packets</span><span class="o">++</span>
<span class="w">        </span><span class="p">}</span>
<span class="w">    </span><span class="p">})</span>
<span class="p">}</span>
</code></pre></div>

<p>Let’s send 1000 packets:</p>
<div class="language-go codehilite"><pre><span/><code><span class="nx">sentPackets</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">1000</span>
<span class="nx">conn</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">net</span><span class="p">.</span><span class="nx">Dial</span><span class="p">(</span><span class="s">"udp"</span><span class="p">,</span><span class="w"> </span><span class="nx">conns</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">LocalAddr</span><span class="p">().</span><span class="nx">String</span><span class="p">())</span>
<span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="nx">t</span><span class="p">.</span><span class="nx">Fatalf</span><span class="p">(</span><span class="s">"Dial() error:\n%+v"</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">defer</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nx">Close</span><span class="p">()</span>
<span class="k">for</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">sentPackets</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nx">Write</span><span class="p">([]</span><span class="kt">byte</span><span class="p">(</span><span class="s">"hello world!"</span><span class="p">));</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="nx">t</span><span class="p">.</span><span class="nx">Fatalf</span><span class="p">(</span><span class="s">"Write() error:\n%+v"</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span>
<span class="w">    </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>

<p>If we print the content of the <code>receivedPackets</code> array, we can check the
balancing works as expected, with each worker getting about 100 packets:</p>
<div class="language-text-only codehilite"><pre><span/><code>=== RUN   TestUDPWorkerBalancing
    balancing_test.go:84: receivedPackets[0] = 107
    balancing_test.go:84: receivedPackets[1] = 92
    balancing_test.go:84: receivedPackets[2] = 99
    balancing_test.go:84: receivedPackets[3] = 105
    balancing_test.go:84: receivedPackets[4] = 107
    balancing_test.go:84: receivedPackets[5] = 96
    balancing_test.go:84: receivedPackets[6] = 102
    balancing_test.go:84: receivedPackets[7] = 105
    balancing_test.go:84: receivedPackets[8] = 99
    balancing_test.go:84: receivedPackets[9] = 88

    balancing_test.go:91: receivedPackets = 1000
    balancing_test.go:92: sentPackets     = 1000
</code></pre></div>

<h2 id="graceful-restart">Graceful restart</h2>
<p>You can also use <code>SO_ATTACH_REUSEPORT_EBPF</code> to gracefully restart an
application. A new instance of the application binds to the same address and
prepares its own version of the socket map. Once it attaches the eBPF program to
the first socket, the kernel steers incoming packets to this new instance. The
old instance needs to drain the already-received packets before shutting down.</p>
<p>To check we are not losing any packet, we spawn a goroutine to send as many
packets as possible:</p>
<div class="language-go codehilite"><pre><span/><code><span class="nx">sentPackets</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span>
<span class="nx">notSentPackets</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span>
<span class="nx">done</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kt">chan</span><span class="w"> </span><span class="kt">bool</span><span class="p">)</span>
<span class="nx">conn</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">net</span><span class="p">.</span><span class="nx">Dial</span><span class="p">(</span><span class="s">"udp"</span><span class="p">,</span><span class="w"> </span><span class="nx">conns1</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">LocalAddr</span><span class="p">().</span><span class="nx">String</span><span class="p">())</span>
<span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="nx">t</span><span class="p">.</span><span class="nx">Fatalf</span><span class="p">(</span><span class="s">"Dial() error:\n%+v"</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">defer</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nx">Close</span><span class="p">()</span>
<span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nx">Write</span><span class="p">([]</span><span class="kt">byte</span><span class="p">(</span><span class="s">"hello world!"</span><span class="p">));</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span>
<span class="w">            </span><span class="nx">notSentPackets</span><span class="o">++</span>
<span class="w">        </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span>
<span class="w">            </span><span class="nx">sentPackets</span><span class="o">++</span>
<span class="w">        </span><span class="p">}</span>
<span class="w">        </span><span class="k">select</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">done</span><span class="p">:</span>
<span class="w">            </span><span class="k">return</span>
<span class="w">        </span><span class="k">default</span><span class="p">:</span>
<span class="w">        </span><span class="p">}</span>
<span class="w">    </span><span class="p">}</span>
<span class="p">}()</span>
</code></pre></div>

<p>Then, while the Go routine runs, we start the second set of workers. Once they
are running, they start receiving packets. If we gracefully stop the initial set
of workers, not a single packet is lost!<sup id="fnref:s4"><a class="footnote-ref" href="#fn:s4">9</a></sup></p>
<div class="language-text-only codehilite"><pre><span/><code>=== RUN   TestGracefulRestart
    graceful_test.go:135: receivedPackets1[0] = 165
    graceful_test.go:135: receivedPackets1[1] = 195
    graceful_test.go:135: receivedPackets1[2] = 194
    graceful_test.go:135: receivedPackets1[3] = 190
    graceful_test.go:135: receivedPackets1[4] = 213
    graceful_test.go:135: receivedPackets1[5] = 187
    graceful_test.go:135: receivedPackets1[6] = 170
    graceful_test.go:135: receivedPackets1[7] = 190
    graceful_test.go:135: receivedPackets1[8] = 194
    graceful_test.go:135: receivedPackets1[9] = 155

    graceful_test.go:139: receivedPackets2[0] = 1631
    graceful_test.go:139: receivedPackets2[1] = 1582
    graceful_test.go:139: receivedPackets2[2] = 1594
    graceful_test.go:139: receivedPackets2[3] = 1611
    graceful_test.go:139: receivedPackets2[4] = 1571
    graceful_test.go:139: receivedPackets2[5] = 1660
    graceful_test.go:139: receivedPackets2[6] = 1587
    graceful_test.go:139: receivedPackets2[7] = 1605
    graceful_test.go:139: receivedPackets2[8] = 1631
    graceful_test.go:139: receivedPackets2[9] = 1689

    graceful_test.go:147: receivedPackets = 18014
    graceful_test.go:148: sentPackets     = 18014
</code></pre></div>

<p>Unfortunately, gracefully shutting down a UDP socket is not trivial in Go.<sup id="fnref:c"><a class="footnote-ref" href="#fn:c">10</a></sup>
Previously, we were terminating workers by closing their sockets. However, if we
close them too soon, the application loses packets that were assigned to them
but not yet processed. Before stopping, a worker needs to call <code>conn.Read()</code>
until there are no more packets. A solution is to set a deadline for
<code>conn.Read()</code> and check if we should stop the goroutine when the deadline is
exceeded:</p>
<div class="language-go codehilite"><pre><span/><code><span class="nx">payload</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">([]</span><span class="kt">byte</span><span class="p">,</span><span class="w"> </span><span class="mi">9000</span><span class="p">)</span>
<span class="k">for</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="nx">conn</span><span class="p">.</span><span class="nx">SetReadDeadline</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Now</span><span class="p">().</span><span class="nx">Add</span><span class="p">(</span><span class="mi">50</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">))</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nx">Read</span><span class="p">(</span><span class="nx">payload</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">errors</span><span class="p">.</span><span class="nx">Is</span><span class="p">(</span><span class="nx">err</span><span class="p">,</span><span class="w"> </span><span class="nx">os</span><span class="p">.</span><span class="nx">ErrDeadlineExceeded</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">            </span><span class="k">select</span><span class="w"> </span><span class="p">{</span>
<span class="w">            </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">done</span><span class="p">:</span>
<span class="w">                </span><span class="k">return</span>
<span class="w">            </span><span class="k">default</span><span class="p">:</span>
<span class="w">                </span><span class="k">continue</span>
<span class="w">            </span><span class="p">}</span>
<span class="w">        </span><span class="p">}</span>
<span class="w">        </span><span class="nx">t</span><span class="p">.</span><span class="nx">Logf</span><span class="p">(</span><span class="s">"Read() error:\n%+v"</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span>
<span class="w">    </span><span class="p">}</span>
<span class="w">    </span><span class="o">*</span><span class="nx">packets</span><span class="o">++</span>
<span class="p">}</span>
</code></pre></div>

<p>With TCP, this aspect is simpler: after enabling the <code>net.ipv4.tcp_migrate_req</code>
sysctl, the kernel automatically migrates waiting connections to a random socket
in the same group. Alternatively, eBPF can also <a href="https://docs.ebpf.io/linux/program-type/BPF_PROG_TYPE_SK_REUSEPORT/#socket-migration" title="eBPF and socket migration">control this
migration</a>. Both features are available since Linux 5.14.</p>
<h1 id="addendum">Addendum</h1>
<p>After implementing this strategy in Akvorado, all workers now drop packets! 😱</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp">$ </span>curl<span class="w"> </span>-s<span class="w"> </span><span class="m">127</span>.0.0.1:8080/api/v0/inlet/metrics<span class="w"> </span><span class="se">\</span>
<span class="gp">&gt; </span><span class="p">|</span><span class="w"> </span>sed<span class="w"> </span>-n<span class="w"> </span>s/akvorado_inlet_flow_input_udp_in_dropped//p
<span class="go">packets_total{listener="0.0.0.0:2055",worker="0"} 838673</span>
<span class="go">packets_total{listener="0.0.0.0:2055",worker="1"} 843675</span>
<span class="go">packets_total{listener="0.0.0.0:2055",worker="2"} 837922</span>
<span class="go">packets_total{listener="0.0.0.0:2055",worker="3"} 841443</span>
<span class="go">packets_total{listener="0.0.0.0:2055",worker="4"} 840668</span>
<span class="go">packets_total{listener="0.0.0.0:2055",worker="5"} 850274</span>
<span class="go">packets_total{listener="0.0.0.0:2055",worker="6"} 835488</span>
<span class="go">packets_total{listener="0.0.0.0:2055",worker="7"} 834479</span>
</code></pre></div>

<p>The root cause is the default limit of 32 records for Kafka batch sizes. This
limit is too low because the brokers have a large overhead when handling each
batch: they need to <a href="https://www.confluent.io/blog/kafka-producer-internals-handling-producer-request/" title="Handling the Producer Request: Kafka Producer and Consumer Internals, Part 2">ensure they persist correctly before acknowledging
them</a>. Increasing the limit to <a href="https://github.com/akvorado/akvorado/commit/39cc3728261f5185ba85c5fdd8b034c4c9697a55">4096 records</a> fixes this issue.</p>
<p>While load-balancing incoming flows with eBPF remains useful, it did not solve
the main issue. At least the even distribution of dropped packets helped
identify the real bottleneck. 😅</p>
<div class="footnote">
<hr/>
<ol>
<li id="fn:accuracy">
<p>The current version of the manual page is incomplete and does not
cover the evolution introduced in Linux 4.19. There is a <a href="https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/commit/?id=41788bdd42312828532c4ddbadc0a4d28426d4fd" title="man/man7/socket.7: Fix documentation for SO_ATTACH_REUSEPORT_PORT">pending patch</a>
about this. <a class="footnote-backref" href="#fnref:accuracy" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:rust">
<p><a href="https://aya-rs.dev/book/" title="Building eBPF Programs with Aya">Rust</a> is another option. However, the program we use is so trivial that
it does not make sense to use Rust. <a class="footnote-backref" href="#fnref:rust" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
<li id="fn:random">
<p>As <code>bpf_get_prandom_u32()</code> returns a pseudo-random 32-bit unsigned
value, this method exhibits a very slight bias towards the first indexes.
This is unlikely to be worth fixing. <a class="footnote-backref" href="#fnref:random" title="Jump back to footnote 3 in the text">↩</a></p>
</li>
<li id="fn:kernel">
<p>Some examples include <code>&lt;linux/bpf.h&gt;</code> instead of <code>"vmlinux.h"</code>. This
makes your eBPF program dependent on the installed kernel headers. <a class="footnote-backref" href="#fnref:kernel" title="Jump back to footnote 4 in the text">↩</a></p>
</li>
<li id="fn:listenaddr">
<p><code>listenAddr</code> is initially set to <code>127.0.0.1:0</code> to allocate a
random port. After the first iteration, it is updated with the allocated
port. <a class="footnote-backref" href="#fnref:listenaddr" title="Jump back to footnote 5 in the text">↩</a></p>
</li>
<li id="fn:s1">
<p>This is the <code>setupSockets()</code> function in <a href="https://github.com/vincentbernat/ebpf-reuseport-go/blob/f932198b577a6ae48560434ca91ec471d9ed7d75/fixtures_test.go#L16-L38"><code>fixtures_test.go</code></a>. <a class="footnote-backref" href="#fnref:s1" title="Jump back to footnote 6 in the text">↩</a></p>
</li>
<li id="fn:s2">
<p>This is the <code>setupEBPF()</code> function in <a href="https://github.com/vincentbernat/ebpf-reuseport-go/blob/f932198b577a6ae48560434ca91ec471d9ed7d75/fixtures_test.go#L40-L68"><code>fixtures_test.go</code></a>. <a class="footnote-backref" href="#fnref:s2" title="Jump back to footnote 7 in the text">↩</a></p>
</li>
<li id="fn:s3">
<p>The complete code is in <a href="https://github.com/vincentbernat/ebpf-reuseport-go/blob/f932198b577a6ae48560434ca91ec471d9ed7d75/balancing_test.go"><code>balancing_test.go</code></a> <a class="footnote-backref" href="#fnref:s3" title="Jump back to footnote 8 in the text">↩</a></p>
</li>
<li id="fn:s4">
<p>The complete code is in <a href="https://github.com/vincentbernat/ebpf-reuseport-go/blob/f932198b577a6ae48560434ca91ec471d9ed7d75/graceful_test.go"><code>graceful_test.go</code></a> <a class="footnote-backref" href="#fnref:s4" title="Jump back to footnote 9 in the text">↩</a></p>
</li>
<li id="fn:c">
<p>In C, we would <code>poll()</code> both the socket and a pipe used to signal for
shutdown. When the second condition is triggered, we drain the socket by
executing a series of non-blocking <code>read()</code> until we get <code>EWOULDBLOCK</code>. <a class="footnote-backref" href="#fnref:c" title="Jump back to footnote 10 in the text">↩</a></p>
</li>
</ol>
</div>
      </div>
    </content>
  </entry>
  <entry>
    <title type="html">Multi-tier load-balancing with Linux</title>
    <author><name>Vincent Bernat</name></author>
    <link href="https://vincent.bernat.ch/en/blog/2018-multi-tier-loadbalancer" rel="alternate"/>
    <link href="https://vincent.bernat.ch/en/blog/2018-multi-tier-loadbalancer#isso-thread" rel="replies" type="text/html"/>
    <updated>2018-05-23T06:59:53Z</updated>
    <id>http://www.luffy.cx/en/blog/2018-multi-tier-loadbalancer.html</id>

    <content type="xhtml">
      <div xmlns="http://www.w3.org/1999/xhtml">
<p>A common solution to provide a highly-available and scalable service is to
insert a load-balancing layer to spread requests from users to backend
servers.<sup id="fnref:backend"><a class="footnote-ref" href="#fn:backend">1</a></sup> We usually have several expectations for such a layer:</p>
<dl>
<dt>Scalability</dt>
<dd>It allows a service to scale by pushing traffic to newly provisioned backend
  servers. It should also be able to scale itself when it becomes the
  bottleneck.</dd>
<dt>Availability</dt>
<dd>It provides high availability to the service. If one server becomes
  unavailable, the traffic should be quickly steered to another server. The
  load-balancing layer itself should also be highly available.</dd>
<dt>Flexibility</dt>
<dd>It handles both short and long connections. It is flexible enough to offer
  all the features backends generally expect from a load-balancer like TLS or
  HTTP routing.</dd>
<dt>Operability</dt>
<dd>With some cooperation, any expected change should be seamless: rolling out a
  new software on the backends, adding or removing backends, or scaling up or
  down the load-balancing layer itself.</dd>
</dl>
<p>The problem and its solutions are well known. From recently published articles
on the topic, “<a href="https://web.archive.org/web/20181123100637/https://blog.envoyproxy.io/introduction-to-modern-network-load-balancing-and-proxying-a57f6ff80236?gi=485ded021f22" title="Introduction to modern network load balancing and proxying">Introduction to modern network load-balancing and proxying</a>”
provides an overview of the state of the art. Google released “<a href="https://research.google/pubs/maglev-a-fast-and-reliable-software-network-load-balancer/" title="Maglev: A Fast and Reliable Software Network Load Balancer">Maglev: A Fast
and Reliable Software Network Load Balancer</a>” describing their in-house
solution in detail.<sup id="fnref:morningpaper"><a class="footnote-ref" href="#fn:morningpaper">2</a></sup> However, the associated software is not
available. Building a load-balancing solution with commodity servers consists of
assembling three components:</p>
<ul>
<li><abbr title="Equal-Cost Multipath">ECMP</abbr> routing</li>
<li>stateless L4 load-balancing</li>
<li>stateful L7 load-balancing</li>
</ul>
<p>In this article, I describe and support a multi-tier solution using Linux and
only open-source components. It should offer you the basis to build a
production-ready load-balancing layer.</p>
<div class="toc">
<ul>
<li><a href="#last-tier-l7-load-balancing">Last tier: L7 load-balancing</a></li>
<li><a href="#first-tier-ecmp-routing">First tier: ECMP routing</a></li>
<li><a href="#second-tier-l4-load-balancing">Second tier: L4 load-balancing</a><ul>
<li><a href="#configuration">Configuration</a></li>
<li><a href="#evaluation-of-the-resilience">Evaluation of the resilience</a></li>
</ul>
</li>
<li><a href="#tier-0-dns-load-balancing">Tier 0: DNS load-balancing</a></li>
</ul>
</div>
<div class="admonition">
<p class="admonition-title">Update (2018-05)</p>
<p>Facebook just released <a href="https://engineering.fb.com/2018/05/22/open-source/open-sourcing-katran-a-scalable-network-load-balancer/" title="Open-sourcing Katran, a scalable network load balancer">Katran</a>, an L4 load-balancer
implemented with <abbr title="eXpress Data Path">XDP</abbr> and <abbr title="Enhanced Berkeley Packet Filter">eBPF</abbr> and using consistent hashing. It could be inserted
in the configuration described below.</p>
</div>
<div class="admonition">
<p class="admonition-title">Update (2018-08)</p>
<p>GitHub just released <a href="https://github.com/github/glb-director" title="GitHub Load Balancer Director">GLB Director</a>, an L4
load-balancer using a <a href="https://en.wikipedia.org/wiki/Rendezvous_hashing" title="Rendezvous hashing on Wikipedia">rendez-vous hashing</a> to select a pair of L7
load-balancers. Using a custom Netfilter module, the first member redirects the
flow to the second if it is unable to find a match in its connection table.</p>
</div>
<div class="admonition">
<p class="admonition-title">Update (2021-12)</p>
<p>Cilium 1.10+ uses a similar approach to the one
described in this article to manage services in a Kubernetes cluster.
Check out the documentation about <a href="https://docs.cilium.io/en/v1.12/gettingstarted/kubeproxy-free/">Kubernetes without kube-proxy</a> as
well as the documentation on <a href="https://docs.cilium.io/en/v1.12/gettingstarted/bgp/">how to set up <abbr title="Border Gateway Protocol">BGP</abbr></a>.</p>
</div>
<h1 id="last-tier-l7-load-balancing">Last tier: L7 load-balancing</h1>
<p>Let’s start with the last tier. Its role is to provide high availability, by
forwarding requests to only healthy backends, and scalability, by spreading
requests fairly between them. Working in the highest layers of the <a href="https://en.wikipedia.org/wiki/OSI_model" title="OSI model on Wikipedia">OSI
model</a>, it can also offer additional services, like TLS-termination, HTTP
routing, header rewriting, rate-limiting of unauthenticated users, and so on.
Being stateful, it can leverage complex load-balancing algorithms. Being the
first point of contact with backend servers, it should ease maintenances and
minimize impact during daily changes.</p>
<figure class="lf-fullbleed"><div class="lf-media-outer" style="width: 270px"><span class="lf-media-inner" style="padding-bottom: 185.556%"><img alt="L7 load-balancers" src="https://media-bernat-ch.b-cdn.net/images/multitier-lb/l7.6971369bdf1acd.svg" width="270" height="501" class="lf-media"/></span></div><figcaption>The last tier of the load-balancing solution is a set of L7 load-balancers receiving user connections and forwarding them to the backends.</figcaption></figure>
<p>It also terminates client TCP connections. This introduces some loose coupling
between the load-balancing components and the backend servers with the following
benefits:</p>
<ul>
<li>connections to servers can be kept open for lower resource use and latency;</li>
<li>requests can be retried transparently in case of failure;</li>
<li>clients can use a different IP protocol than servers; and</li>
<li>servers do not have to care about <a href="https://en.wikipedia.org/wiki/Path_MTU_Discovery" title="Path MTU Discovery on Wikipedia">path <abbr title="Maximum Transmission Unit">MTU</abbr> discovery</a>, TCP congestion
   control algorithms, <a href="/en/blog/2014-tcp-time-wait-state-linux" title="Coping with the TCP TIME-WAIT state on busy Linux servers">avoidance of the TIME-WAIT state</a>, and various other
   low-level details.</li>
</ul>
<p>Many pieces of software would fit in this layer and an ample literature exists
on how to configure them. You could look at <a href="https://www.haproxy.org/" title="Reliable, High Performance TCP/HTTP Load Balancer">HAProxy</a>, <a href="https://www.envoyproxy.io/" title="Envoy is an open source edge and service proxy">Envoy</a> or
<a href="https://traefik.io/traefik" title="Traefik: modern HTTP reverse proxy and load-balancer">Traefik</a>. Here is a configuration example for <em>HAProxy</em>:</p>
<div class="language-haproxy codehilite"><pre><span/><code><span class="c c-Singleline"># L7 load-balancer endpoint</span>
<span class="kn">frontend</span> <span class="n">l7lb</span>
  <span class="c c-Singleline"># Listen on both IPv4 and IPv6</span>
  <span class="nf">bind</span> :80 v4v6
  <span class="c c-Singleline"># Redirect everything to a default backend</span>
  <span class="nf">default_backend</span> servers
  <span class="c c-Singleline"># Healthchecking</span>
  <span class="nf">acl</span> dead nbsrv(servers) lt <span class="m">1</span>
  <span class="nf">acl</span> disabled nbsrv(enabler) lt <span class="m">1</span>
  <span class="nf">monitor-uri</span> /healthcheck
  <span class="nf">monitor</span> fail if dead || disabled

<span class="c c-Singleline"># IPv6-only servers with HTTP healthchecking and remote agent checks</span>
<span class="kn">backend</span> <span class="n">servers</span>
  <span class="nf">balance</span> roundrobin
  <span class="nf">option</span> httpchk
  <span class="nf">server</span> web1 <span class="p">[</span><span class="m">2001:db8:1:0:2::1</span><span class="p">]:</span><span class="m">80</span> send-proxy check agent-check agent-port <span class="m">5555</span>
  <span class="nf">server</span> web2 <span class="p">[</span><span class="m">2001:db8:1:0:2::2</span><span class="p">]:</span><span class="m">80</span> send-proxy check agent-check agent-port <span class="m">5555</span>
  <span class="nf">server</span> web3 <span class="p">[</span><span class="m">2001:db8:1:0:2::3</span><span class="p">]:</span><span class="m">80</span> send-proxy check agent-check agent-port <span class="m">5555</span>
  <span class="nf">server</span> web4 <span class="p">[</span><span class="m">2001:db8:1:0:2::4</span><span class="p">]:</span><span class="m">80</span> send-proxy check agent-check agent-port <span class="m">5555</span>

<span class="c c-Singleline"># Fake backend: if the local agent check fails, we assume we are dead</span>
<span class="kn">backend</span> <span class="n">enabler</span>
  <span class="nf">server</span> enabler <span class="p">[</span><span class="m">::1</span><span class="p">]:</span><span class="m">0</span> agent-check agent-port <span class="m">5555</span>
</code></pre></div>

<p>This configuration is the most incomplete piece of this guide. However, it
illustrates two key concepts for operability:</p>
<ol>
<li>
<p>Healthchecking of the web servers is done both at HTTP-level (with <code>check</code>
    and <code>option httpchk</code>) and using an <a href="https://cbonte.github.io/haproxy-dconv/1.8/configuration.html#5.2-agent-check" title="agent-check directive in HAProxy documentation">auxiliary agent check</a> (with
    <code>agent-check</code>). The latter makes it easy to put a server to maintenance or to
    orchestrate a progressive rollout. On each backend, you need a process
    listening on port 5555 and reporting the status of the service (<code>UP</code>,
    <code>DOWN</code>, <code>MAINT</code>). A simple <code>socat</code> process can do the trick:<sup id="fnref:agentcheck"><a class="footnote-ref" href="#fn:agentcheck">3</a></sup></p>
<div class="language-bash codehilite"><pre><span/><code>socat<span class="w"> </span>-ly<span class="w"> </span><span class="se">\</span>
<span class="w">  </span>TCP6-LISTEN:5555,ipv6only<span class="o">=</span><span class="m">0</span>,reuseaddr,fork<span class="w"> </span><span class="se">\</span>
<span class="w">  </span>OPEN:/etc/lb/agent-check,rdonly
</code></pre></div>

<p>Put <code>UP</code> in <code>/etc/lb/agent-check</code> when the service is in nominal mode. If
the regular healthcheck is also positive, <em>HAProxy</em> will send requests to
this node. When you need to put it in maintenance, write <code>MAINT</code> and wait for
the existing connections to terminate. Use <code>READY</code> to cancel this mode.</p>
</li>
<li>
<p>The load-balancer itself should provide a healthcheck endpoint
    (<code>/healthcheck</code>) for the upper tier. It will return a 503 error if either
    there are no backend servers available or if the <code>enabler</code> backend is put
    down through the agent check. The same mechanism as for regular backends can be
    used to signal the unavailability of this load-balancer.</p>
</li>
</ol>
<p>Additionally, the <code>send-proxy</code> directive enables the <a href="https://www.haproxy.com/blog/use-the-proxy-protocol-to-preserve-a-clients-ip-address" title="Proxy Protocol">proxy protocol</a> to
transmit the real clients’ IP addresses. This protocol also works for non-HTTP
connections and is supported by a variety of servers, including <em>nginx</em>:</p>
<div class="language-nginx-configuration-file codehilite"><pre><span/><code><span class="k">http</span><span class="w"> </span><span class="p">{</span>
<span class="w">  </span><span class="kn">server</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="kn">listen</span><span class="w"> </span><span class="s">[::]:80</span><span class="w"> </span><span class="s">default</span><span class="w"> </span><span class="s">ipv6only=off</span><span class="w"> </span><span class="s">proxy_protocol</span><span class="p">;</span>
<span class="w">    </span><span class="kn">root</span><span class="w"> </span><span class="s">/var/www</span><span class="p">;</span>
<span class="w">    </span><span class="kn">set_real_ip_from</span><span class="w"> </span><span class="p">::</span><span class="s">/0</span><span class="p">;</span>
<span class="w">    </span><span class="kn">real_ip_header</span><span class="w"> </span><span class="s">proxy_protocol</span><span class="p">;</span>
<span class="w">  </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>

<p>As is, this solution is not complete. We have just moved the availability and
scalability problem somewhere else. How do we load-balance the requests between
the load-balancers?</p>
<h1 id="first-tier-ecmp-routing">First tier: <abbr title="Equal-Cost Multipath">ECMP</abbr> routing</h1>
<p>On most modern routed IP networks, redundant paths exist between clients and
servers. For each packet, routers have to choose a path. When the cost
associated to each path is equal, incoming flows<sup id="fnref:flow"><a class="footnote-ref" href="#fn:flow">4</a></sup> are load-balanced among
the available destinations. This characteristic can be used to balance
connections among available load-balancers:</p>
<figure class="lf-fullbleed"><div class="lf-media-outer" style="width: 251px"><span class="lf-media-inner" style="padding-bottom: 141.036%"><img alt="ECMP routing" src="https://media-bernat-ch.b-cdn.net/images/multitier-lb/ecmp.ce13edbd2c96c5.svg" width="251" height="354" class="lf-media"/></span></div><figcaption>ECMP routing is used as a first tier. Flows are spread among available L7 load-balancers. Routing is stateless and asymmetric. Backend servers are not represented.</figcaption></figure>
<p>There is little control over the load-balancing but <abbr title="Equal-Cost Multipath">ECMP</abbr> routing brings the
ability to scale horizontally both tiers. A common way to implement such a
solution is to use <abbr title="Border Gateway Protocol">BGP</abbr>, a routing protocol to exchange routes between network
devices. Each load-balancer announces to its connected routers the IP
addresses it is serving.</p>
<p>If we assume you already have <abbr title="Border Gateway Protocol">BGP</abbr>-enabled routers available, <a href="https://github.com/Exa-Networks/exabgp" title="The BGP swiss army knife of networking">ExaBGP</a> is a
flexible solution to let the load-balancers advertise their availability. Here
is a configuration for one of the load-balancers:</p>
<div class="language-junos codehilite"><pre><span/><code><span class="c c-Singleline"># Healthcheck for IPv6</span>
<span class="k">process</span> service-v6 <span class="p">{</span>
  run python -m exabgp healthcheck -s --interval <span class="m">10</span> --increase <span class="m">0</span> --cmd <span class="s2">"test -f /etc/lb/v6-ready -a ! -f /etc/lb/disable"</span><span class="p">;</span>
  encoder text;
<span class="p">}</span>

<span class="k">template</span> <span class="p">{</span>
  <span class="c c-Singleline"># Template for IPv6 neighbors</span>
  neighbor v6 <span class="p">{</span>
    router-id <span class="m">192.0.2.132</span><span class="p">;</span>
    local-address <span class="m">2001:db8::192</span>.0.2.132;
    local-as <span class="m">65000</span><span class="p">;</span>
    peer-as <span class="m">65000</span><span class="p">;</span>
    hold-time <span class="m">6</span><span class="p">;</span>
    family <span class="p">{</span>
      ipv6 unicast;
    <span class="p">}</span>
    api services-v6 <span class="p">{</span>
      processes [ service-v6 ];
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c c-Singleline"># First router</span>
<span class="k">neighbor</span> <span class="m">2001:db8::192</span>.0.2.254 <span class="p">{</span>
  inherit v6;
<span class="p">}</span>

<span class="c c-Singleline"># Second router</span>
<span class="k">neighbor</span> <span class="m">2001:db8::192</span>.0.2.253 <span class="p">{</span>
  inherit v6;
<span class="p">}</span>
</code></pre></div>

<p>If <code>/etc/lb/v6-ready</code> is present and <code>/etc/lb/disable</code> is absent, all the IP
addresses configured on the <code>lo</code> interface will be announced to both routers. If
the other load-balancers use a similar configuration, the routers will
distribute incoming flows between them. Some external process should manage the
existence of the <code>/etc/lb/v6-ready</code> file by checking for the healthiness of the
load-balancer (using the <code>/healthcheck</code> endpoint for example). An operator can
remove a load-balancer from the rotation by creating the <code>/etc/lb/disable</code> file.</p>
<p>To get more details on this part, have a look at “<a href="/en/blog/2013-exabgp-highavailability" title="High availability with ExaBGP">High availability with
ExaBGP</a>.” If you are in the cloud, this tier is usually implemented by your
cloud provider, either using an anycast IP address or a basic L4 load-balancer.</p>
<p>Unfortunately, this solution is not resilient when an expected or unexpected
change happens. Notably, when adding or removing a load-balancer, the number of
available routes for a destination changes. The hashing algorithm used by
routers is not consistent and flows are reshuffled among the available
load-balancers, breaking existing connections:</p>
<figure class="lf-fullbleed"><div class="lf-media-outer" style="width: 611px"><span class="lf-media-inner" style="padding-bottom: 52.209%"><img alt="Stability of ECMP routing 1/2" src="https://media-bernat-ch.b-cdn.net/images/multitier-lb/ecmp-failure1.1e4c8015bd6071.svg" width="611" height="319" class="lf-media"/></span></div><figcaption>ECMP routing is unstable when a change happens. An additional load-balancer is added to the pool and the flows are routed to different load-balancers, which do not have the appropriate entries in their connection tables.</figcaption></figure>
<p>Moreover, each router may choose its own routes. When a router becomes
unavailable, the second one may route the same flows differently:</p>
<figure class="lf-fullbleed"><div class="lf-media-outer" style="width: 531px"><span class="lf-media-inner" style="padding-bottom: 60.075%"><img alt="Stability of ECMP routing 2/2" src="https://media-bernat-ch.b-cdn.net/images/multitier-lb/ecmp-failure2.93310967861cf4.svg" width="531" height="319" class="lf-media"/></span></div><figcaption>A router becomes unavailable and the remaining router load-balances its flows differently. One of them is routed to a different load-balancer, which does not have the appropriate entry in its connection table.</figcaption></figure>
<p>If you think this is not an acceptable outcome, notably if you need to handle
long connections like file downloads, video streaming or websocket connections,
you need an additional tier. Keep reading!</p>
<div class="admonition">
<p class="admonition-title">Update (2021-03)</p>
<p>Some hardware vendors support resilient hashing
to help circumvent this limitation. See for example the documentation
from <a href="https://www.juniper.net/documentation/us/en/software/junos/interfaces-ethernet-switches/topics/topic-map/hashing-algoriths-lag-bundle-ecmp.html#id_ng1_p23_rfc" title="Understanding the Use of Resilient Hashing to Minimize Flow Remapping in Trunk/ECMP Groups">Juniper</a> and
<a href="https://docs.nvidia.com/networking-ethernet-software/cumulus-linux-41/Layer-3/Equal-Cost-Multipath-Load-Sharing-Hardware-ECMP/" title="Equal Cost Multipath Load Sharing - Hardware ECMP">Cumulus</a>. Thanks to Jessy Vetter for the
tip! This feature has also been <a href="https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git/commit/?id=2a0186a37700b0d5b8cc40be202a62af44f02fa2" title="nexthop: Resilient next-hop groups">added in Linux 5.12</a>.</p>
</div>
<h1 id="second-tier-l4-load-balancing">Second tier: L4 load-balancing</h1>
<p>The second tier is the glue between the <em>stateless</em> world of IP routers and the
<em>stateful</em> land of L7 load-balancing. It is implemented with L4 load-balancing.
The terminology can be a bit confusing here: this tier routes IP datagrams (no
TCP termination) but the scheduler uses both destination IP and port to choose
an available L7 load-balancer. The purpose of this tier is to ensure all members
take the same scheduling decision for an incoming packet.</p>
<p>There are two options:</p>
<ul>
<li>stateful L4 load-balancing with state synchronization across the members; or</li>
<li>stateless L4 load-balancing with consistent hashing.</li>
</ul>
<p>The first option increases complexity and limits scalability. We won’t use
it.<sup id="fnref:conntrackd"><a class="footnote-ref" href="#fn:conntrackd">5</a></sup> The second option is less resilient during some changes but can
be enhanced with a hybrid approach using a local state.</p>
<p>We use <a href="https://www.linuxvirtualserver.org/software/ipvs.html" title="Advanced Layer-4 Switching">IPVS</a>, a performant L4 load-balancer running inside the Linux kernel,
with <a href="https://www.keepalived.org/" title="Keepalived for Linux">Keepalived</a>, a frontend to <em>IPVS</em> with a set of healthcheckers to kick
out an unhealthy component. <em>IPVS</em> is configured to use the <em>Maglev</em> scheduler,
a <a href="https://research.google/pubs/maglev-a-fast-and-reliable-software-network-load-balancer/" title="Maglev: A Fast and Reliable Software Network Load Balancer">consistent hashing algorithm from Google</a>. Among its family, this is
a great algorithm because it spreads connections fairly, minimizes disruptions
during changes and is quite fast at building its lookup table. Finally, to
improve performance, we let the last tier—the L7 load-balancers—send back
answers directly to the clients without involving the second tier—the L4
load-balancers. This is referred to as <em>direct server return</em> (<abbr title="Direct Server Return">DSR</abbr>) or <em>direct
routing</em> (<abbr title="Direct Routing">DR</abbr>).</p>
<figure class="lf-fullbleed"><div class="lf-media-outer" style="width: 314px"><span class="lf-media-inner" style="padding-bottom: 181.847%"><img alt="Second tier: L4 load-balancing" src="https://media-bernat-ch.b-cdn.net/images/multitier-lb/l4.7a1fa53f25316a.svg" width="314" height="571" class="lf-media"/></span></div><figcaption>L4 load-balancing with IPVS and consistent hashing as a glue between the first tier and the third tier. Backend servers have been omitted. Dotted lines represent the path for the return packets.</figcaption></figure>
<p>With such a setup, we expect packets from a flow to be able to move freely
between the components of the first two tiers while sticking to the same L7
load-balancer.</p>
<h2 id="configuration">Configuration</h2>
<p>Assuming <em>ExaBGP</em> has already been configured like described in the previous
section, let’s start with the configuration of <em>Keepalived</em>:</p>
<div class="language-junos codehilite"><pre><span/><code><span class="k">virtual_server_group</span> VS_GROUP_MH_IPv6 <span class="p">{</span>
  <span class="m">2001:db8::198</span>.51.100.1 <span class="m">80</span>
<span class="p">}</span>
<span class="k">virtual_server</span> group VS_GROUP_MH_IPv6 <span class="p">{</span>
  lvs_method TUN  <span class="c c-Singleline"># Tunnel mode for DSR</span>
  lvs_sched mh    <span class="c c-Singleline"># Scheduler: Maglev</span>
  mh-port         <span class="c c-Singleline"># Use port information for scheduling</span>
  protocol TCP
  delay_loop <span class="m">5</span>
  alpha           <span class="c c-Singleline"># All servers are down on start</span>
  omega           <span class="c c-Singleline"># Execute quorum_down on shutdown</span>
  quorum_up   <span class="s2">"/bin/touch /etc/lb/v6-ready"</span>
  quorum_down <span class="s2">"/bin/rm -f /etc/lb/v6-ready"</span>

  <span class="c c-Singleline"># First L7 load-balancer</span>
  real_server <span class="m">2001:db8::192</span>.0.2.132 <span class="m">80</span> <span class="p">{</span>
    weight <span class="m">1</span>
    HTTP_GET <span class="p">{</span>
      url <span class="p">{</span>
        path /healthcheck
        status_code <span class="m">200</span>
      <span class="p">}</span>
      connect_timeout <span class="m">2</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="c c-Singleline"># Many others...</span>
<span class="p">}</span>
</code></pre></div>

<p>The <code>quorum_up</code> and <code>quorum_down</code> statements define the commands to be executed
when the service becomes available and unavailable respectively. The
<code>/etc/lb/v6-ready</code> file is used as a signal to <em>ExaBGP</em> to advertise the service
IP address to the neighbor routers.</p>
<p>Additionally, <em>IPVS</em> needs to be configured to continue routing packets from a
flow moved from another L4 load-balancer. It should also continue routing
packets from unavailable destinations to ensure we can drain properly an L7
load-balancer.</p>
<div class="language-bash codehilite"><pre><span/><code><span class="c1"># Schedule non-SYN packets</span>
sysctl<span class="w"> </span>-qw<span class="w"> </span>net.ipv4.vs.sloppy_tcp<span class="o">=</span><span class="m">1</span>
<span class="c1"># Do NOT reschedule a connection when destination</span>
<span class="c1"># doesn't exist anymore</span>
sysctl<span class="w"> </span>-qw<span class="w"> </span>net.ipv4.vs.expire_nodest_conn<span class="o">=</span><span class="m">0</span>
sysctl<span class="w"> </span>-qw<span class="w"> </span>net.ipv4.vs.expire_quiescent_template<span class="o">=</span><span class="m">0</span>
</code></pre></div>

<p>The <em>Maglev</em> scheduling algorithm will be available with Linux 4.18, thanks to
<a href="https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git/commit/?id=039f32e8cdea29b4d0680df7a83817b5ec4166e1" title="netfilter: ipvs: Add Maglev hashing scheduler">Inju Song</a>. For older kernels, I have prepared a <a href="https://github.com/vincentbernat/ip_vs_mh" title="Backport of consistent source hashing scheduler for Linux IPVS">backport</a>.<sup id="fnref:backport"><a class="footnote-ref" href="#fn:backport">6</a></sup> Use
of source hashing as a scheduling algorithm will hurt the resilience of the
setup.</p>
<p><abbr title="Direct Server Return">DSR</abbr> is implemented using the tunnel mode. This method is compatible with routed
datacenters and cloud environments. Requests are tunneled to the scheduled peer
using <a href="https://web.archive.org/web/2018/http://www.networksorcery.com/enp/protocol/ip-ip.htm" title="IP in IP Encapsulation">IPIP encapsulation</a>. It adds a small overhead and may lead to <abbr title="Maximum Transmission Unit">MTU</abbr>
issues. If possible, ensure you are using a larger <abbr title="Maximum Transmission Unit">MTU</abbr> for communication between
the second and the third tier.<sup id="fnref:mtu"><a class="footnote-ref" href="#fn:mtu">7</a></sup> Otherwise, it is better to explicitly
allow fragmentation of IP packets:</p>
<div class="language-bash codehilite"><pre><span/><code>sysctl<span class="w"> </span>-qw<span class="w"> </span>net.ipv4.vs.pmtu_disc<span class="o">=</span><span class="m">0</span>
</code></pre></div>

<p>You also need to configure the L7 load-balancers to handle encapsulated
traffic:<sup id="fnref:firewall"><a class="footnote-ref" href="#fn:firewall">8</a></sup></p>
<div class="language-bash codehilite"><pre><span/><code><span class="c1"># Setup IPIP tunnel to accept packets from any source</span>
ip<span class="w"> </span>tunnel<span class="w"> </span>add<span class="w"> </span>tunlv6<span class="w"> </span>mode<span class="w"> </span>ip6ip6<span class="w"> </span><span class="nb">local</span><span class="w"> </span><span class="m">2001</span>:db8::192.0.2.132
ip<span class="w"> </span>link<span class="w"> </span><span class="nb">set</span><span class="w"> </span>up<span class="w"> </span>dev<span class="w"> </span>tunlv6
ip<span class="w"> </span>addr<span class="w"> </span>add<span class="w"> </span><span class="m">2001</span>:db8::198.51.100.1/128<span class="w"> </span>dev<span class="w"> </span>tunlv6
</code></pre></div>

<h2 id="evaluation-of-the-resilience">Evaluation of the resilience</h2>
<p>As configured, the second tier increases the resilience of this setup for two
reasons:</p>
<ol>
<li>
<p>The scheduling algorithm is using a consistent hash to choose its
    destination. Such an algorithm reduces the negative impact of expected or
    unexpected changes by minimizing the number of flows moving to a new
    destination. “<a href="https://dgryski.medium.com/consistent-hashing-algorithmic-tradeoffs-ef6b8e2fcae8" title="Consistent Hashing: Algorithmic Tradeoffs">Consistent Hashing: Algorithmic Tradeoffs</a>” offers more
    details on this subject.</p>
</li>
<li>
<p><em>IPVS</em> keeps a local connection table for known flows. When a change impacts
    only the third tier, existing flows will be correctly directed according to
    the connection table.</p>
</li>
</ol>
<p>If we add or remove an L4 load-balancer, existing flows are not impacted because
each load-balancer takes the same decision, as long as they see the same set of
L7 load-balancers:</p>
<figure class="lf-fullbleed"><div class="lf-media-outer" style="width: 685px"><span class="lf-media-inner" style="padding-bottom: 77.080%"><img alt="L4 load-balancing instability 1/3" src="https://media-bernat-ch.b-cdn.net/images/multitier-lb/l4-failure1.6f22cd88a8416b.svg" width="685" height="528" class="lf-media"/></span></div><figcaption>Losing an L4 load-balancer has no impact on existing flows. Each arrow is an example of flow. The dots are flow endpoints bound to the associated load-balancer. If they had moved to another load-balancer, connection would have been lost.</figcaption></figure>
<p>If we add an L7 load-balancer, existing flows are not impacted either because
only new connections will be scheduled to it. For existing connections, <em>IPVS</em>
will look at its local connection table and continue to forward packets to the
original destination. Similarly, if we remove an L7 load-balancer, only existing
flows terminating at this load-balancer are impacted. Other existing connections
will be forwarded correctly:</p>
<figure class="lf-fullbleed"><div class="lf-media-outer" style="width: 685px"><span class="lf-media-inner" style="padding-bottom: 77.080%"><img alt="L4 load-balancing instability 2/3" src="https://media-bernat-ch.b-cdn.net/images/multitier-lb/l4-failure2.b944a25431a499.svg" width="685" height="528" class="lf-media"/></span></div><figcaption>Losing an L7 load-balancer only impacts the flows bound to it.</figcaption></figure>
<p>We need to have simultaneous changes on both levels to get a noticeable impact.
For example, when adding both an L4 load-balancer and an L7 load-balancer, only
connections moved to an L4 load-balancer without state <em>and</em> scheduled to the new
load-balancer will be broken. Thanks to the consistent hashing algorithm, other
connections will stay bound to the right L7 load-balancer. During a planned
change, this disruption can be minimized by adding the new L4 load-balancers
first, waiting a few minutes, then adding the new L7 load-balancers.</p>
<figure class="lf-fullbleed"><div class="lf-media-outer" style="width: 685px"><span class="lf-media-inner" style="padding-bottom: 77.080%"><img alt="L4 load-balancing instability 3/3" src="https://media-bernat-ch.b-cdn.net/images/multitier-lb/l4-failure3.2608491d5365cb.svg" width="685" height="528" class="lf-media"/></span></div><figcaption>Both an L4 load-balancer and an L7 load-balancer come back to life. The consistent hash algorithm ensures that only one fifth of the existing connections would be moved to the incoming L7 load-balancer. Some of them continue to be routed through their original L4 load-balancer, which mitigates the impact.</figcaption></figure>
<p>Additionally, <em>IPVS</em> correctly routes ICMP messages to the same L7
load-balancers as the associated connections.<sup id="fnref:icmp"><a class="footnote-ref" href="#fn:icmp">9</a></sup> This ensures path <abbr title="Maximum Transmission Unit">MTU</abbr>
discovery works and there is no need for <a href="https://blog.cloudflare.com/path-mtu-discovery-in-practice/" title="Path MTU discovery in practice">smart workarounds</a>.</p>
<h1 id="tier-0-dns-load-balancing">Tier 0: DNS load-balancing</h1>
<p>Optionally, you can add DNS load-balancing to the mix. This is useful either if
your setup is spanned across multiple datacenters, or multiple cloud regions,
or if you want to break a large load-balancing cluster into smaller ones. It is
not intended to replace the first tier as it doesn’t share the same
characteristics: load-balancing is unfair (it is not flow-based) and recovery
from a failure is slow.</p>
<figure class="lf-fullbleed"><div class="lf-media-outer" style="width: 571px"><span class="lf-media-inner" style="padding-bottom: 130.998%"><img alt="Complete load-balancing solution" src="https://media-bernat-ch.b-cdn.net/images/multitier-lb/dns.9617b861d4ac1c.svg" width="571" height="748" class="lf-media"/></span></div><figcaption>A complete load-balancing solution spanning across two datacenters.</figcaption></figure>
<p><a href="https://gdnsd.org/" title="Authoritative-only DNS server which does geographic balancing, redirection, weighting, and service-state-conscious failover at the DNS layer">gdnsd</a> is an authoritative-only DNS server with integrated healthchecking. It
can serve zones from master files using the <a href="https://www.rfc-editor.org/rfc/rfc1035" title="RFC 1035: Domain names - implementation and specification">RFC 1035</a> zone format:</p>
<div class="language-zone codehilite"><pre><span/><code><span class="o">@</span><span class="w"> </span><span class="kt">SOA</span><span class="w"> </span><span class="l">ns1</span><span class="w"> </span><span class="l">ns1.example.org.</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">7200</span><span class="w"> </span><span class="mi">1800</span><span class="w"> </span><span class="mi">259200</span><span class="w"> </span><span class="mi">900</span>
<span class="o">@</span><span class="w"> </span><span class="kt">NS</span><span class="w"> </span><span class="l">ns1.example.com.</span>
<span class="o">@</span><span class="w"> </span><span class="kt">NS</span><span class="w"> </span><span class="l">ns1.example.net.</span>
<span class="o">@</span><span class="w"> </span><span class="kt">MX</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="l">smtp</span>

<span class="o">@</span><span class="w">     </span><span class="mi">60</span><span class="w"> </span><span class="kt">DYNA</span><span class="w"> </span><span class="l">multifo!web</span>
<span class="n">www</span><span class="w">   </span><span class="mi">60</span><span class="w"> </span><span class="kt">DYNA</span><span class="w"> </span><span class="l">multifo!web</span>
<span class="n">smtp</span><span class="w">     </span><span class="kt">A</span><span class="w">    </span><span class="l">198.51.100.99</span>
</code></pre></div>

<p>The special <abbr title="Resource Record">RR</abbr> type <code>DYNA</code> will return <code>A</code> and <code>AAAA</code> records after querying the
specified plugin. Here, the <code>multifo</code> plugin implements an all-active failover
of monitored addresses:</p>
<div class="language-apacheconf codehilite"><pre><span/><code><span class="nb">service_types</span><span class="w"> </span>=&gt;<span class="w"> </span>{
<span class="w">  </span><span class="nb">web</span><span class="w"> </span>=&gt;<span class="w"> </span>{
<span class="w">    </span><span class="nb">plugin</span><span class="w"> </span>=&gt;<span class="w"> </span>http_status
<span class="w">    </span><span class="nb">url_path</span><span class="w"> </span>=&gt;<span class="w"> </span><span class="sx">/healthcheck</span>
<span class="w">    </span><span class="nb">down_thresh</span><span class="w"> </span>=&gt;<span class="w"> </span><span class="m">5</span>
<span class="w">    </span><span class="nb">interval</span><span class="w"> </span>=&gt;<span class="w"> </span><span class="m">5</span>
<span class="w">  </span><span class="err">}</span>
<span class="w">  </span><span class="nb">ext</span><span class="w"> </span>=&gt;<span class="w"> </span>{
<span class="w">    </span><span class="nb">plugin</span><span class="w"> </span>=&gt;<span class="w"> </span>extfile
<span class="w">    </span><span class="nb">file</span><span class="w"> </span>=&gt;<span class="w"> </span><span class="sx">/etc/lb/ext</span>
<span class="w">    </span><span class="nb">def_down</span><span class="w"> </span>=&gt;<span class="w"> </span>false
<span class="w">  </span><span class="err">}</span>
<span class="err">}</span>

<span class="nb">plugins</span><span class="w"> </span>=&gt;<span class="w"> </span>{
<span class="w">  </span><span class="nb">multifo</span><span class="w"> </span>=&gt;<span class="w"> </span>{
<span class="w">    </span><span class="nb">web</span><span class="w"> </span>=&gt;<span class="w"> </span>{
<span class="w">      </span><span class="nb">service_types</span><span class="w"> </span>=&gt;<span class="w"> </span>[<span class="w"> </span>ext,<span class="w"> </span>web<span class="w"> </span>]
<span class="w">      </span><span class="nb">addrs_v4</span><span class="w"> </span>=&gt;<span class="w"> </span>[<span class="w"> </span><span class="m">198.51.100.1</span>,<span class="w"> </span><span class="m">198.51.100.2</span><span class="w"> </span>]
<span class="w">      </span><span class="nb">addrs_v6</span><span class="w"> </span>=&gt;<span class="w"> </span>[<span class="w"> </span><span class="m">2001</span>:db8::198.51.100.1,<span class="w"> </span><span class="m">2001</span>:db8::198.51.100.2<span class="w"> </span>]
<span class="w">    </span><span class="err">}</span>
<span class="w">  </span><span class="err">}</span>
<span class="err">}</span>
</code></pre></div>

<p>In nominal state, an <code>A</code> request will be answered with both <code>198.51.100.1</code> and
<code>198.51.100.2</code>. A healthcheck failure will update the returned set accordingly.
It is also possible to administratively remove an entry by modifying the
<code>/etc/lb/ext</code> file. For example, with the following content, <code>198.51.100.2</code> will
not be advertised anymore:</p>
<div class="language-text-only codehilite"><pre><span/><code>198.51.100.1 =&gt; UP
198.51.100.2 =&gt; DOWN
2001:db8::c633:6401 =&gt; UP
2001:db8::c633:6402 =&gt; UP
</code></pre></div>

<hr/>
<p>You can find all the configuration files and the setup of each tier in the
<a href="https://github.com/vincentbernat/network-lab/tree/master/lab-3tier-lb" title="Configuration files for 4-tier load-balancer">Git repository</a>. If you want to replicate this setup at a smaller scale,
it is possible to collapse the second and the third tiers by using either
<a href="https://web.archive.org/web/2018/http://www.austintek.com/LVS/LVS-HOWTO/HOWTO/LVS-HOWTO.localnode.html" title="LVS: LocalNode">localnode</a> or network namespaces. Even if you don’t need its fancy
load-balancing services, you should keep the last tier: while backend servers
come and go, the L7 load-balancers bring stability, which translates to
resiliency.</p>
<div class="footnote">
<hr/>
<ol>
<li id="fn:backend">
<p>In this article, “<em>backend servers</em>” are the servers behind the
load-balancing layer. To avoid confusion, we will not use the term
“<em>frontend</em>.” <a class="footnote-backref" href="#fnref:backend" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:morningpaper">
<p>A good summary of the paper is available from <a href="https://web.archive.org/web/2018/https://blog.acolyer.org/2016/03/21/maglev-a-fast-and-reliable-software-network-load-balancer/" title="Maglev: A Fast and Reliable Software Network Load Balancer">Adrian
Colyer</a>. From the same author, you may also have a look at the summary for
“<a href="https://web.archive.org/web/2018/https://blog.acolyer.org/2018/05/03/stateless-datacenter-load-balancing-with-beamer/" title="Stateless datacenter load-balancing with Beamer">Stateless datacenter load-balancing with Beamer</a>.” <a class="footnote-backref" href="#fnref:morningpaper" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
<li id="fn:agentcheck">
<p>If you feel this solution is fragile, feel free to develop your
own agent. It could coordinate with a key-value store to determine the
wanted state of the server. It is possible to centralize the agent in a
single location, but you may get a chicken-and-egg problem to ensure its
availability. <a class="footnote-backref" href="#fnref:agentcheck" title="Jump back to footnote 3 in the text">↩</a></p>
</li>
<li id="fn:flow">
<p>A flow is usually determined by the source and destination IP and the
L4 protocol. Alternatively, the source and destination port can also be
used. The router hashes this information to choose the destination. For
Linux, you may find more information on this topic in “<a href="https://web.archive.org/web/2018/https://cumulusnetworks.com/blog/celebrating-ecmp-part-one/" title="Celebrating ECMP in Linux">Celebrating <abbr title="Equal-Cost Multipath">ECMP</abbr> in
Linux</a>.” <a class="footnote-backref" href="#fnref:flow" title="Jump back to footnote 4 in the text">↩</a></p>
</li>
<li id="fn:conntrackd">
<p>On Linux, it can be implemented by using <a href="https://www.netfilter.org/" title="Packet filtering framework for Linux">Netfilter</a> for
load-balancing and <a href="https://conntrack-tools.netfilter.org/about.html" title="Connection tracking userspace tools for Linux">conntrackd</a> to synchronize state. <em>IPVS</em> only provides
active/backup synchronization. <a class="footnote-backref" href="#fnref:conntrackd" title="Jump back to footnote 5 in the text">↩</a></p>
</li>
<li id="fn:backport">
<p>The backport is not strictly equivalent to its original version. Be
sure to check the <a href="https://github.com/vincentbernat/ip_vs_mh/blob/master/README.md#differences-with-the-upstream-module"><code>README</code> file</a> to understand the differences. <a class="footnote-backref" href="#fnref:backport" title="Jump back to footnote 6 in the text">↩</a></p>
</li>
<li id="fn:mtu">
<p>At least 1520 for IPv4 and 1540 for IPv6. <a class="footnote-backref" href="#fnref:mtu" title="Jump back to footnote 7 in the text">↩</a></p>
</li>
<li id="fn:firewall">
<p>As is, this configuration is insecure. You need to ensure only
the L4 load-balancers will be able to send IPIP traffic. <a class="footnote-backref" href="#fnref:firewall" title="Jump back to footnote 8 in the text">↩</a></p>
</li>
<li id="fn:icmp">
<p>This feature was added in Linux 4.4. See for example <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=1471f35efa86407fc180ca6d55363c684d166ef" title="ipvs: sh: support scheduling icmp/inverse packets consistently">commit
1471f35efa86</a>. <a class="footnote-backref" href="#fnref:icmp" title="Jump back to footnote 9 in the text">↩</a></p>
</li>
</ol>
</div>
      </div>
    </content>
  </entry>
  <entry>
    <title type="html">Packaging an out-of-tree module for Debian with DKMS</title>
    <author><name>Vincent Bernat</name></author>
    <link href="https://vincent.bernat.ch/en/blog/2018-packaging-driver-debian-dkms" rel="alternate"/>
    <link href="https://vincent.bernat.ch/en/blog/2018-packaging-driver-debian-dkms#isso-thread" rel="replies" type="text/html"/>
    <updated>2018-03-07T16:38:52Z</updated>
    <id>http://www.luffy.cx/en/blog/2018-packaging-driver-debian-dkms.html</id>

    <content type="xhtml">
      <div xmlns="http://www.w3.org/1999/xhtml">
<blockquote>
<p><a href="https://github.com/dkms-project/dkms" title="Dynamic Kernel Module Support on GitHub"><abbr title="Dynamic Kernel Module Support">DKMS</abbr></a> is a framework designed to allow individual kernel modules to
be upgraded without changing the whole kernel. It is also very easy
to rebuild modules as you upgrade kernels.</p>
</blockquote>
<p>On Debian-like systems,<sup id="fnref:rpm"><a class="footnote-ref" href="#fn:rpm">1</a></sup> <em><abbr title="Dynamic Kernel Module Support">DKMS</abbr></em> enables the installation of
various drivers, from <a href="https://packages.debian.org/sid/zfs-dkms" title="ZFS on Linux module package for Debian">ZFS on Linux</a> to <a href="https://packages.debian.org/sid/virtualbox-dkms" title="VirtualBox kernel module package for Debian">VirtualBox kernel
modules</a> or <a href="https://packages.debian.org/sid/nvidia-kernel-dkms" title="NVIDIA kernel module package for Debian">NVIDIA drivers</a>. These out-of-tree modules are not
distributed as binaries: once installed, they need to be compiled for
your current kernel. Everything is done automatically:</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp"># </span>apt<span class="w"> </span>install<span class="w"> </span>zfs-dkms
<span class="go">Reading package lists... Done</span>
<span class="go">Building dependency tree</span>
<span class="go">Reading state information... Done</span>
<span class="go">The following additional packages will be installed:</span>
<span class="go">  binutils cpp cpp-6 dkms fakeroot gcc gcc-6 gcc-6-base libasan3 libatomic1 libc-dev-bin libc6-dev</span>
<span class="go">  libcc1-0 libcilkrts5 libfakeroot libgcc-6-dev libgcc1 libgomp1 libisl15 libitm1 liblsan0 libmpc3</span>
<span class="go">  libmpfr4 libmpx2 libnvpair1linux libquadmath0 libstdc++6 libtsan0 libubsan0 libuutil1linux libzfs2linux</span>
<span class="go">  libzpool2linux linux-compiler-gcc-6-x86 linux-headers-4.9.0-6-amd64 linux-headers-4.9.0-6-common</span>
<span class="go">  linux-headers-amd64 linux-kbuild-4.9 linux-libc-dev make manpages manpages-dev patch spl spl-dkms</span>
<span class="go">  zfs-zed zfsutils-linux</span>
<span class="go">[…]</span>
<span class="go">3 upgraded, 44 newly installed, 0 to remove and 3 not upgraded.</span>
<span class="go">Need to get 42.1 MB of archives.</span>
<span class="go">After this operation, 187 MB of additional disk space will be used.</span>
<span class="go">Do you want to continue? [Y/n]</span>
<span class="go">[…]</span>
<span class="gp"># </span>dkms<span class="w"> </span>status
<span class="go">spl, 0.6.5.9, 4.9.0-6-amd64, x86_64: installed</span>
<span class="go">zfs, 0.6.5.9, 4.9.0-6-amd64, x86_64: installed</span>
<span class="gp"># </span>modinfo<span class="w"> </span>zfs<span class="w"> </span><span class="p">|</span><span class="w"> </span>head
<span class="go">filename:       /lib/modules/4.9.0-6-amd64/updates/dkms/zfs.ko</span>
<span class="go">version:        0.6.5.9-5</span>
<span class="go">license:        CDDL</span>
<span class="go">author:         OpenZFS on Linux</span>
<span class="go">description:    ZFS</span>
<span class="go">srcversion:     42C4AB70887EA26A9970936</span>
<span class="go">depends:        spl,znvpair,zcommon,zunicode,zavl</span>
<span class="go">retpoline:      Y</span>
<span class="go">vermagic:       4.9.0-6-amd64 SMP mod_unload modversions</span>
<span class="go">parm:           zvol_inhibit_dev:Do not create zvol device nodes (uint)</span>
</code></pre></div>

<p>If you install a new kernel, a compilation of the module is
automatically triggered.</p>
<h1 id="building-your-own-dkms-enabled-package">Building your own <abbr title="Dynamic Kernel Module Support">DKMS</abbr>-enabled package</h1>
<p>Suppose you’ve gotten your hands on an <a href="https://www.intel.com/content/www/us/en/products/sku/95260/intel-ethernet-network-adapter-xxv710da2/specifications.html" title="Intel Ethernet Network Adapter XXV710-DA2">Intel XXV710-DA2</a> <abbr title="Network Interface Card">NIC</abbr>. This
card is handled by the <code>i40e</code> driver. Unfortunately, it only got
support from Linux 4.10 and you are using a stock 4.9 Debian Stretch
kernel. <em><abbr title="Dynamic Kernel Module Support">DKMS</abbr></em> provides here an easy solution!</p>
<p>Download the <a href="https://www.intel.com/content/www/us/en/download/18026/intel-network-adapter-driver-for-pcie-40-gigabit-ethernet-network-connections-under-linux.html" title="Intel Network Adapter Driver for PCIe 40 Gigabit Ethernet Network Connections Under Linux">driver from Intel</a>, unpack it in some directory and
add a <code>debian/</code> subdirectory with the following files:</p>
<ul>
<li>
<p><code>debian/changelog</code>:</p>
<div class="language-text-only codehilite"><pre><span/><code>i40e-dkms (2.4.6-0) stretch; urgency=medium

  * Initial package.

 -- Vincent Bernat &lt;bernat@debian.org&gt;  Tue, 27 Feb 2018 17:20:58 +0100
</code></pre></div>

</li>
<li>
<p><code>debian/control</code>:</p>
<div class="language-debian-control-file codehilite"><pre><span/><code><span class="k">Source</span><span class="p">:</span><span class="w"> </span><span class="s">i40e-dkms</span>
<span class="k">Maintainer</span><span class="p">:</span><span class="w"> </span>Vincent Bernat <span class="gs">&lt;bernat@debian.org&gt;</span>
<span class="k">Build-Depends</span><span class="p">:</span><span class="w"> </span><span class="nf">debhelper</span> <span class="p">(</span><span class="o">&gt;=</span><span class="w"> </span><span class="m">9</span><span class="p">)</span>, <span class="nf">dkms</span>

<span class="nf">Package</span><span class="err">:</span> <span class="nf">i40e-dkms</span>
<span class="k">Architecture</span><span class="p">:</span><span class="w"> </span><span class="s">all</span>
<span class="k">Depends</span><span class="p">:</span><span class="w"> </span><span class="o">$</span><span class="p">{</span><span class="ni">misc</span><span class="p">:</span>Depends<span class="p">}</span>
<span class="k">Description</span><span class="gs">: DKMS source for the Intel i40e network driver</span>
</code></pre></div>

</li>
<li>
<p><code>debian/rules</code>:</p>
<div class="language-makefile codehilite"><pre><span/><code><span class="c">#!/usr/bin/make -f</span>

<span class="cp">include /usr/share/dpkg/pkg-info.mk</span>

<span class="nf">%</span><span class="o">:</span>
<span class="w">        </span>dh<span class="w"> </span><span class="nv">$@</span><span class="w"> </span>--with<span class="w"> </span>dkms

<span class="nf">override_dh_install</span><span class="o">:</span>
<span class="hll"><span class="w">        </span>dh_install<span class="w"> </span>src/*<span class="w"> </span>usr/src/i40e-<span class="k">$(</span>DEB_VERSION_UPSTREAM<span class="k">)</span>/
</span>
<span class="nf">override_dh_dkms</span><span class="o">:</span>
<span class="w">        </span>dh_dkms<span class="w"> </span>-V<span class="w"> </span><span class="k">$(</span>DEB_VERSION_UPSTREAM<span class="k">)</span>

<span class="nf">override_dh_auto_configure</span><span class="o">:</span>
<span class="nf">override_dh_auto_build</span><span class="o">:</span>
<span class="nf">override_dh_auto_test</span><span class="o">:</span>
<span class="nf">override_dh_auto_install</span><span class="o">:</span>
<span class="nf">override_dh_auto_clean</span><span class="o">:</span>
</code></pre></div>

</li>
<li>
<p><code>debian/i40e-dkms.dkms</code>:</p>
<div class="language-ini codehilite"><pre><span/><code><span class="na">PACKAGE_NAME</span><span class="o">=</span><span class="s">"i40e"</span>
<span class="na">PACKAGE_VERSION</span><span class="o">=</span><span class="s">"#MODULE_VERSION#"</span>
<span class="na">BUILT_MODULE_NAME[0]</span><span class="o">=</span><span class="s">"$PACKAGE_NAME"</span>
<span class="na">DEST_MODULE_LOCATION[0]</span><span class="o">=</span><span class="s">"/updates/dkms"</span>
<span class="na">AUTOINSTALL</span><span class="o">=</span><span class="s">"YES"</span>
<span class="na">REMAKE_INITRD</span><span class="o">=</span><span class="s">"YES"</span>
</code></pre></div>

</li>
<li>
<p><code>debian/compat</code>:</p>
<div class="language-text-only codehilite"><pre><span/><code>9
</code></pre></div>

</li>
</ul>
<p>In <code>debian/changelog</code>, pay attention to the version. The version of
the driver is 2.4.6. Therefore, we use <code>2.4.6-0</code> for the package. In
<code>debian/rules</code>, we install the source of the driver in
<code>/usr/src/i40e-2.4.6</code>—the version is extracted from
<code>debian/changelog</code>.</p>
<p>The content of <code>debian/i40e-dkms.dkms</code> is described in detail in the
<a href="https://manpages.debian.org/dkms.8.html#DKMS.CONF" title="Manual page for dkms(8)"><code>dkms(8)</code> manual page</a>. The <code>i40e</code> driver is fairly
standard and <code>dkms</code> is able to figure out how to compile it. However,
if your kernel module does not follow the usual conventions, it is the
right place to override the build command.</p>
<p>Once all the files are in place, you can turn the directory into a
Debian package with, for example, the <code>dpkg-buildpackage</code>
command.<sup id="fnref:apt"><a class="footnote-ref" href="#fn:apt">2</a></sup> At the end of this operation, you get your <abbr title="Dynamic Kernel Module Support">DKMS</abbr>-enabled
package, <code>i40e-dkms_2.4.6-0_all.deb</code>. Put it in your <a href="/en/blog/2014-local-apt-repositories" title="Local corporate APT repositories">internal
repository</a> and install it on the target.</p>
<h1 id="avoiding-compilation-on-target">Avoiding compilation on target</h1>
<div class="admonition">
<p class="admonition-title">Update (2024-03)</p>
<p><em><abbr title="Dynamic Kernel Module Support">DKMS</abbr></em> upstream removed support for the <code>dkms mkbmdeb</code>
command. This impacts versions starting from 2.8.8, including the ones shipped in
Debian Bookworm and Ubuntu Noble. See <a href="https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1009179" title="Debian bug #1009179">Debian bug#1009179</a> for more
information.</p>
</div>
<div class="admonition">
<p class="admonition-title">Update (2026-09)</p>
<p>As a replacement, you can use
<a href="https://github.com/uecasm/dkms-mkbmdeb" title="Standalone replacement for dkms mkbmdeb"><code>dkms-mkbmdeb</code></a>, a standalone script with the same features.
Substitute <code>dkms mkbmdeb</code> with <code>dkms-mkbmdeb</code> in the example below. Thanks to
Gavin for this!</p>
</div>
<p>If you feel uncomfortable installing compilation tools on the target
servers, there is a simple solution. Since version
2.2.0.3-5,<sup id="fnref:version"><a class="footnote-ref" href="#fn:version">3</a></sup> thanks to <a href="https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=554843#92" title="Debian bug #554843">Thijs Kinkhorst</a>, <code>dkms</code> can build
lean binary packages with only the built modules. For each kernel
version, you build such a package in your <abbr title="Continuous Integration">CI</abbr> system:</p>
<div class="language-bash codehilite"><pre><span/><code><span class="nv">KERNEL_VERSION</span><span class="o">=</span><span class="m">4</span>.9.0-6-amd64<span class="w"> </span><span class="c1"># could be a Jenkins parameter</span>
apt<span class="w"> </span>-qyy<span class="w"> </span>install<span class="w"> </span><span class="se">\</span>
<span class="w">      </span>i40e-dkms<span class="w"> </span><span class="se">\</span>
<span class="w">      </span>linux-image-<span class="si">${</span><span class="nv">KERNEL_VERSION</span><span class="si">}</span><span class="w"> </span><span class="se">\</span>
<span class="w">      </span>linux-headers-<span class="si">${</span><span class="nv">KERNEL_VERSION</span><span class="si">}</span>

<span class="nv">DRIVER_VERSION</span><span class="o">=</span><span class="k">$(</span>dkms<span class="w"> </span>status<span class="w"> </span>i40e<span class="w"> </span><span class="p">|</span><span class="w"> </span>awk<span class="w"> </span>-F<span class="s1">', '</span><span class="w"> </span><span class="s1">'{print $2}'</span><span class="k">)</span>
<span class="hll">dkms<span class="w"> </span>mkbmdeb<span class="w"> </span>i40e/<span class="si">${</span><span class="nv">DRIVER_VERSION</span><span class="si">}</span><span class="w"> </span>-k<span class="w"> </span><span class="si">${</span><span class="nv">KERNEL_VERSION</span><span class="si">}</span>
</span>
<span class="nb">cd</span><span class="w"> </span>/var/lib/dkms/i40e/<span class="si">${</span><span class="nv">DRIVER_VERSION</span><span class="si">}</span>/bmdeb/
dpkg<span class="w"> </span>-c<span class="w"> </span>i40e-modules-<span class="si">${</span><span class="nv">KERNEL_VERSION</span><span class="si">}</span>_*
dpkg<span class="w"> </span>-I<span class="w"> </span>i40e-modules-<span class="si">${</span><span class="nv">KERNEL_VERSION</span><span class="si">}</span>_*
</code></pre></div>

<p>Here is the shortened output of the two last commands:</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp"># </span>dpkg<span class="w"> </span>-c<span class="w"> </span>i40e-modules-<span class="si">${</span><span class="nv">KERNEL_VERSION</span><span class="si">}</span>_*
<span class="go">[…]</span>
<span class="hll"><span class="go">-rw-r--r-- root/root    551664 2018-03-01 19:16 ./lib/modules/4.9.0-6-amd64/updates/dkms/i40e.ko</span>
</span><span class="go">[…]</span>
<span class="gp"># </span>dpkg<span class="w"> </span>-I<span class="w"> </span>i40e-modules-<span class="si">${</span><span class="nv">KERNEL_VERSION</span><span class="si">}</span>_*
<span class="go"> new debian package, version 2.0.</span>
<span class="go">[…]</span>
<span class="go"> Package: i40e-modules-4.9.0-6-amd64</span>
<span class="go"> Source: i40e-dkms-bin</span>
<span class="go"> Version: 2.4.6</span>
<span class="go"> Architecture: amd64</span>
<span class="go"> Installed-Size: 555</span>
<span class="go"> Depends: linux-image-4.9.0-6-amd64</span>
<span class="hll"><span class="go"> Provides: i40e-modules</span>
</span><span class="go"> Section: misc</span>
<span class="go"> Priority: optional</span>
<span class="go"> Description: i40e binary drivers for linux-image-4.9.0-6-amd64</span>
<span class="go">  This package contains i40e drivers for the 4.9.0-6-amd64 Linux kernel,</span>
<span class="go">  built from i40e-dkms for the amd64 architecture.</span>
</code></pre></div>

<p>The generated Debian package contains the pre-compiled driver and only
depends on the associated kernel. You can safely install it without
pulling dozens of packages.</p>
<div class="footnote">
<hr/>
<ol>
<li id="fn:rpm">
<p><em><abbr title="Dynamic Kernel Module Support">DKMS</abbr></em> is also compatible with RPM-based distributions but the
content of this article is not suitable for these. <a class="footnote-backref" href="#fnref:rpm" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:apt">
<p>You may need to install some additional packages:
<code>build-essential</code>, <code>fakeroot</code> and <code>debhelper</code>. <a class="footnote-backref" href="#fnref:apt" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
<li id="fn:version">
<p>Available in Debian Stretch and in the backports for
Debian Jessie. However, for Ubuntu Xenial, you need to
<a href="https://github.com/exoscale/pkg-dkms/tree/xenial" title="Backport of dkms for Ubuntu Xenial">backport</a> a more recent version of <code>dkms</code>. <a class="footnote-backref" href="#fnref:version" title="Jump back to footnote 3 in the text">↩</a></p>
</li>
</ol>
</div>
      </div>
    </content>
  </entry>
  <entry>
    <title type="html">L3 routing to the hypervisor with BGP</title>
    <author><name>Vincent Bernat</name></author>
    <link href="https://vincent.bernat.ch/en/blog/2018-l3-routing-hypervisor" rel="alternate"/>
    <link href="https://vincent.bernat.ch/en/blog/2018-l3-routing-hypervisor#isso-thread" rel="replies" type="text/html"/>
    <updated>2018-01-29T19:25:44Z</updated>
    <id>http://www.luffy.cx/en/blog/2018-l3-routing-hypervisor.html</id>

    <content type="xhtml">
      <div xmlns="http://www.w3.org/1999/xhtml">
<p>On layer 2 networks, high availability can be achieved by:</p>
<ul>
<li>antique protocols, like the <a href="https://en.wikipedia.org/wiki/Spanning_Tree_Protocol" title="Spanning Tree Protocol on Wikipedia">Spanning Tree Protocol</a>;</li>
<li>proprietary protocols, like <a href="https://www.juniper.net/documentation/us/en/software/junos/virtual-chassis/topics/concept/virtual-chassis-switches-overview.html" title="Juniper: EX Series Virtual Chassis Overview">Juniper’s virtual chassis</a>,
   <a href="https://web.archive.org/web/2018/https://www.cisco.com/c/en/us/products/collateral/switches/nexus-5000-series-switches/configuration_guide_c07-543563.html" title="Cisco: Virtual PortChannel Quick Configuration Guide">Cisco’s virtual port channel</a>, and <a href="https://www.juniper.net/documentation/us/en/software/junos/mc-lag/topics/concept/mc-lag-feature-summary-best-practices.html" title="Juniper: MC-LAG Technical Overview">other</a>
   <a href="https://docs.nvidia.com/networking-ethernet-software/cumulus-linux-43/Layer-2/Multi-Chassis-Link-Aggregation-MLAG/" title="Cumulus: Multi-Chassis Link Aggregation - MLAG"><abbr title="Multi-Chassis Link Aggregation">MC-LAG</abbr></a> <a href="https://arista.my.site.com/AristaCommunity/s/article/mlag-basic-configuration" title="Arista: MLAG">implementations</a>;<sup id="fnref:mclag"><a class="footnote-ref" href="#fn:mclag">1</a></sup></li>
<li>standardized protocols, like <a href="https://sci-hub.fr/10.1109/IEEESTD.2014.6991462" title="IEEE 802.1Q-2014 on Sci-Hub">Shortest Path Bridging Protocol</a> or <a href="https://www.rfc-editor.org/rfc/rfc6325" title="RFC 6325: Routing Bridges (RBridges): Base Protocol Specification">TRILL</a>; or</li>
<li>the underlying network (in the case of an overlay network, like
   <a href="/en/blog/2017-vxlan-linux" title="VXLAN &amp; Linux">VXLAN</a>).</li>
</ul>
<p>Layer 2 networks need very little configuration but come with a major
drawback in highly available scenarios: an incident is likely to bring
the whole network down.<sup id="fnref:incident"><a class="footnote-ref" href="#fn:incident">2</a></sup> Therefore, it is safer to <strong>limit
the scope of a single layer 2 network</strong> by, for example, using one
distinct network in each rack and connecting them with layer
3 routing. Incidents are unlikely to impact a whole IP network.</p>
<p>In the illustration below, top of the rack switches provide a default
gateway for hosts. To provide redundancy, they use an <abbr title="Multi-Chassis Link Aggregation">MC-LAG</abbr>
implementation. Layer 2 fault domains are scoped to a rack. Each IP
subnet is bound to a specific rack and routing information is shared
between top of the rack switches and core routers using a routing
protocol like <abbr title="Open Shortest Path First">OSPF</abbr>.</p>
<figure class="lf-fullbleed"><div class="lf-media-outer" style="width: 726px"><span class="lf-media-inner" style="padding-bottom: 71.350%"><img alt="Legacy L2 design" src="https://media-bernat-ch.b-cdn.net/images/l3-routing/legacy.bdd34c1fce0fd7.svg" width="726" height="518" class="lf-media"/></span></div><figcaption>Minimal layer 3 (routed) access design: each pair of top of the racks act as a gateway. Layer 2 fault domains are limited to a single rack. Hypervisors provide a bridge to virtual guests, extending the layer 2 domain. Each rack owns a set of subnets.</figcaption></figure>
<p>There are two main issues with this design:</p>
<ol>
<li>
<p>The <strong>L2 domains are still large</strong>. A rack could host several
    dozen hypervisors and several thousand virtual guests. Therefore, a
    network incident will have a large impact.</p>
</li>
<li>
<p><strong>IP subnets are pinned to each rack</strong>. A virtual guest cannot
    move to another rack and unused IP addresses in a rack cannot be
    used in another one.</p>
</li>
</ol>
<p>To solve both these problems, it is possible to push L3 routing
further to the south, turning each hypervisor into an L3
router. However, we need to ensure customer virtual guests are blind
to this change: they should keep getting their configuration from <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr>
(IP, subnet, and gateway).</p>
<div class="toc">
<ul>
<li><a href="#hypervisor-as-a-router">Hypervisor as a router</a><ul>
<li><a href="#routing-setup">Routing setup</a></li>
<li><a href="#guest-routes">Guest routes</a></li>
<li><a href="#bgp-setup">BGP setup</a><ul>
<li><a href="#fabric-design">Fabric design</a></li>
<li><a href="#route-reflectors">Route reflectors</a></li>
<li><a href="#hypervisor-configuration">Hypervisor configuration</a></li>
</ul>
</li>
<li><a href="#performance">Performance</a></li>
<li><a href="#reverse-path-filtering">Reverse path filtering</a></li>
</ul>
</li>
<li><a href="#keeping-guests-in-the-dark">Keeping guests in the dark</a></li>
<li><a href="#conclusion-and-future-work">Conclusion and future work</a></li>
</ul>
</div>
<h1 id="hypervisor-as-a-router">Hypervisor as a router</h1>
<p>In a nutshell, for a guest with an IPv4 address:</p>
<ul>
<li>the hosting hypervisor configures a <code>/32</code> route with the virtual
   interface as next-hop; and</li>
<li>this route is distributed to other hypervisors (and routers) using
   <abbr title="Border Gateway Protocol">BGP</abbr>.</li>
</ul>
<p>Our design also handles two routing domains: a <em>public</em> one (hosting
virtual guests from multiple tenants with direct Internet access) and
a <em>private</em> one (used by our own infrastructure, hypervisors
included). Each hypervisor uses two routing tables for this purpose.</p>
<p>The following illustration shows the configuration of a hypervisor
with 5 guests. No bridge is needed.</p>
<figure class="lf-fullbleed"><div class="lf-media-outer" style="width: 930px"><span class="lf-media-inner" style="padding-bottom: 56.989%"><img alt="L3 routing inside a hypervisor" src="https://media-bernat-ch.b-cdn.net/images/l3-routing/hypervisor.650f5a106382f5.svg" width="930" height="530" class="lf-media"/></span></div><figcaption>Layer 3 routing setup for a hypervisor. Each hypervisor is connected to a “public” network and a “private” one. Guests are attached to either of these networks. Each routing table contains connected routes for local guests, host routes for remote guests, and a default route for other destinations.</figcaption></figure>
<p>The complete configuration described below is also available on
<a href="https://github.com/vincentbernat/network-lab/tree/88b9b875c3c48c2f9e3ad4fb4e2719d6c4b3cc22/lab-l3-hyperv" title="Lab configuration files">GitHub</a>. In real life, a piece of software is needed to update the
hypervisor configuration when an instance is added or removed. It
would listen to notifications from your cloud orchestrator.</p>
<p><a href="https://www.tigera.io/project-calico/" title="Project Calico: Secure networking for the cloud native era">Calico</a> is a project fulfilling the same objective (L3 routing to
the hypervisor) with mostly the same ideas (except it heavily relies
on <em>Netfilter</em> to ensure separation between administrative
domains). It provides an agent (<em>Felix</em>) to serve as an interface with
orchestrators like <em>OpenStack</em> or <em>Kubernetes</em>. Check it if you want a
turnkey solution.</p>
<h2 id="routing-setup">Routing setup</h2>
<p>Using IP rules, each interface is “attached” to a routing table:</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp">$ </span>ip<span class="w"> </span>rule<span class="w"> </span>show
<span class="go">0:  from all lookup local</span>
<span class="go">20: from all iif lo lookup main</span>
<span class="go">21: from all iif lo lookup local-out</span>
<span class="hll"><span class="go">30: from all iif eth0.private lookup private</span>
</span><span class="hll"><span class="go">30: from all iif eth1.private lookup private</span>
</span><span class="hll"><span class="go">30: from all iif vnet8 lookup private</span>
</span><span class="hll"><span class="go">30: from all iif vnet9 lookup private</span>
</span><span class="hll"><span class="go">40: from all lookup public</span>
</span></code></pre></div>

<p>The most important rules are the highlighted ones (priority 30 and
40): any traffic coming from a “private” interface uses the <code>private</code>
table. Any remaining traffic uses the <code>public</code> table.</p>
<p>The two <code>iif lo</code> rules manage routing for packets originated from the
hypervisor itself. The <code>local-out</code> table is a mix of the <code>private</code> and
<code>public</code> tables. The hypervisor mostly needs the routes from the
<code>private</code> table but also needs to contact local virtual guests (for
example, to answer a ping request) using the <code>public</code> table. Both
tables contain a default route (no chaining possible), so we build a
third table, <code>local-out</code>, by <a href="https://github.com/vincentbernat/network-lab/blob/88b9b875c3c48c2f9e3ad4fb4e2719d6c4b3cc22/lab-l3-hyperv/bird-common/hypervisor.conf#L11-L40" title="local-out table configuration">copying all routes</a> from the
<code>private</code> table and directly connected routes from the <code>public</code> table.</p>
<p>To avoid an accidental leak of traffic, <code>public</code>, <code>private</code>, and
<code>local-out</code> routing tables contain a default last-resort route with a
large metric.<sup id="fnref:nodistribute"><a class="footnote-ref" href="#fn:nodistribute">3</a></sup> On normal operations, these routes should
be shadowed by a regular default route:</p>
<div class="language-bash codehilite"><pre><span/><code>ip<span class="w"> </span>route<span class="w"> </span>add<span class="w"> </span>blackhole<span class="w"> </span>default<span class="w"> </span>metric<span class="w"> </span><span class="m">4294967294</span><span class="w"> </span>table<span class="w"> </span>public
ip<span class="w"> </span>route<span class="w"> </span>add<span class="w"> </span>blackhole<span class="w"> </span>default<span class="w"> </span>metric<span class="w"> </span><span class="m">4294967294</span><span class="w"> </span>table<span class="w"> </span>private
ip<span class="w"> </span>route<span class="w"> </span>add<span class="w"> </span>blackhole<span class="w"> </span>default<span class="w"> </span>metric<span class="w"> </span><span class="m">4294967294</span><span class="w"> </span>table<span class="w"> </span>local-out
</code></pre></div>

<p>IPv6 is far simpler as we have only one routing domain. While we
keep a <code>public</code> table, there is no need for a <code>local-out</code> table:</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp">$ </span>ip<span class="w"> </span>-6<span class="w"> </span>rule<span class="w"> </span>show
<span class="go">0:  from all lookup local</span>
<span class="go">20: from all lookup main</span>
<span class="go">40: from all lookup public</span>
</code></pre></div>

<p>As the last step, forwarding is enabled and the maximum size for IPv6
route cache is increased (default is only 4096):<sup id="fnref:ddos"><a class="footnote-ref" href="#fn:ddos">4</a></sup></p>
<div class="language-bash codehilite"><pre><span/><code>sysctl<span class="w"> </span>-qw<span class="w"> </span>net.ipv4.conf.all.forwarding<span class="o">=</span><span class="m">1</span>
sysctl<span class="w"> </span>-qw<span class="w"> </span>net.ipv6.conf.all.forwarding<span class="o">=</span><span class="m">1</span>
sysctl<span class="w"> </span>-qw<span class="w"> </span>net.ipv6.route.max_size<span class="o">=</span><span class="m">524288</span>
</code></pre></div>

<div class="admonition">
<p class="admonition-title">Update (2019-12)</p>
<p>Starting from Linux 4.2, entries for the route
cache are only created when there is a PMTU exception. Therefore, it
is best to keep the default value and watch <code>/proc/net/rt6_stats</code>: the
next to last value is the current cache size in hexadecimal.</p>
</div>
<h2 id="guest-routes">Guest routes</h2>
<p>The second step is to configure routes for each guest. For IPv6, we use
the link-local address, derived from the remote MAC address, as
next-hop:<sup id="fnref:privacy"><a class="footnote-ref" href="#fn:privacy">5</a></sup></p>
<div class="language-bash codehilite"><pre><span/><code>ip<span class="w"> </span>-6<span class="w"> </span>route<span class="w"> </span>add<span class="w"> </span><span class="m">2001</span>:db8:cb00:7100:5254:33ff:fe00:f/128<span class="w"> </span><span class="se">\</span>
<span class="w">    </span>via<span class="w"> </span>fe80::5254:33ff:fe00:f<span class="w"> </span>dev<span class="w"> </span>vnet6<span class="w"> </span><span class="se">\</span>
<span class="w">    </span>table<span class="w"> </span>public
</code></pre></div>

<p>Assigning several IP addresses (or subnets) to each
guest can be done by adding more routes:</p>
<div class="language-bash codehilite"><pre><span/><code>ip<span class="w"> </span>-6<span class="w"> </span>route<span class="w"> </span>add<span class="w"> </span><span class="m">2001</span>:db8:cb00:7107::/64<span class="w"> </span><span class="se">\</span>
<span class="w">    </span>via<span class="w"> </span>fe80::5254:33ff:fe00:f<span class="w"> </span>dev<span class="w"> </span>vnet6<span class="w"> </span><span class="se">\</span>
<span class="w">    </span>table<span class="w"> </span>public
</code></pre></div>

<p>For IPv4, the
route uses the guest interface as a next-hop. Linux will issue an <abbr title="Address Resolution Protocol">ARP</abbr>
request before being able to forward the packet:<sup id="fnref:arp"><a class="footnote-ref" href="#fn:arp">6</a></sup></p>
<div class="language-bash codehilite"><pre><span/><code>ip<span class="w"> </span>route<span class="w"> </span>add<span class="w"> </span><span class="m">192</span>.0.2.15/32<span class="w"> </span>dev<span class="w"> </span>vnet6<span class="w"> </span><span class="se">\</span>
<span class="w">  </span>table<span class="w"> </span>public
</code></pre></div>

<p>Additional IP addresses and subnets can be configured the same way but
each IP address would have to answer to <abbr title="Address Resolution Protocol">ARP</abbr> requests. To avoid this,
it is possible to route additional subnets through the first IP
address:<sup id="fnref:onlink"><a class="footnote-ref" href="#fn:onlink">7</a></sup></p>
<div class="language-bash codehilite"><pre><span/><code>ip<span class="w"> </span>route<span class="w"> </span>add<span class="w"> </span><span class="m">203</span>.0.113.128/28<span class="w"> </span><span class="se">\</span>
<span class="w">  </span>via<span class="w"> </span><span class="m">192</span>.0.2.15<span class="w"> </span>dev<span class="w"> </span>vnet6<span class="w"> </span>onlink<span class="w"> </span><span class="se">\</span>
<span class="w">  </span>table<span class="w"> </span>public
</code></pre></div>

<h2 id="bgp-setup"><abbr title="Border Gateway Protocol">BGP</abbr> setup</h2>
<p>The third step is to share routes between hypervisors, through
<abbr title="Border Gateway Protocol">BGP</abbr>. This part is dependent on how hypervisors are connected to each
other.</p>
<h3 id="fabric-design">Fabric design</h3>
<p>Several designs are possible to connect hypervisors. The most obvious
one is to use a full L3 leaf-spine fabric:</p>
<figure class="lf-fullbleed"><div class="lf-media-outer" style="width: 745px"><span class="lf-media-inner" style="padding-bottom: 38.658%"><img alt="Full L3 fabric design" src="https://media-bernat-ch.b-cdn.net/images/l3-routing/full-v2.88ba996f18d400.svg" width="745" height="288" class="lf-media"/></span></div><figcaption>Full L3 leaf-spine fabric design. A BGP session is established over each link. Spine routers need to learn all routes.</figcaption></figure>
<p>Each hypervisor establishes an <abbr title="External Border Gateway Protocol">eBGP</abbr> session with each of the leaf
top-of-the-rack routers. These routers establish an <abbr title="External Border Gateway Protocol">eBGP</abbr> session
with each spine router. This solution can be expensive because the
spine routers need to handle all routes. With the current generation
of switches/routers, this puts a limit around the maximum number of
routes for the expected density.<sup id="fnref:current"><a class="footnote-ref" href="#fn:current">8</a></sup> IP and <abbr title="Border Gateway Protocol">BGP</abbr>
configuration can also be tedious unless some uncommon
autoconfiguration mechanisms are used. On the other hand, leaf routers
(and hypervisors) may optionally learn fewer routes as they can push
non-local traffic north.</p>
<p>Another potential design is to use an L2 fabric. This may sound
surprising after bad-mouthing L2 networks for their unreliability but
we don’t need them to be highly available. They can provide a very
scalable and cost-efficient design:<sup id="fnref:scale"><a class="footnote-ref" href="#fn:scale">9</a></sup></p>
<figure class="lf-fullbleed"><div class="lf-media-outer" style="width: 804px"><span class="lf-media-inner" style="padding-bottom: 48.010%"><img alt="L2 fabric design" src="https://media-bernat-ch.b-cdn.net/images/l3-routing/l2.a56c6e139047af.svg" width="804" height="386" class="lf-media"/></span></div><figcaption>L2 fabric leaf-spine fabric design with 4 distinct L2 networks. Each hypervisor establishes a BGP session to at least one route reflector for each L2 network.</figcaption></figure>
<p>Each hypervisor is connected to <strong>4 distinct L2 networks</strong>, limiting
the scope of a single failure to a quarter of the available
bandwidth. In this design, only <abbr title="Internal Border Gateway Protocol">iBGP</abbr> is used. To avoid a full-mesh
topology between all hypervisors, <strong>route reflectors</strong> are used. Each
hypervisor has an <abbr title="Internal Border Gateway Protocol">iBGP</abbr> session with one or several route reflectors
from each of the L2 networks. Route reflectors on the same L2 network
share their routes using <abbr title="Internal Border Gateway Protocol">iBGP</abbr>. <em>Calico</em> <a href="https://docs.tigera.io/calico/latest/reference/architecture/design/l2-interconnect-fabric" title="Calico over an Ethernet interconnect fabric">documents this
design</a> in more detail.</p>
<p>This is the solution described below. Public and private domains share
the same infrastructure but use distinct VLANs.</p>
<div class="admonition">
<p class="admonition-title">Update (2022-07)</p>
<p>While such a design made sense a few years
back, recent hardware can scale better and I recommend you opt for a
full L3 design. In this case, you do not need the route reflectors. If
you swap <em>BIRD</em> with <em>FRR</em>, you can even use a single IPv6 <abbr title="External Border Gateway Protocol">eBGP</abbr>
session with VPNv4 (public and private), VPNv6 (public), and EVPN (for
<a href="/en/blog/2017-vxlan-linux" title="VXLAN &amp; Linux">VXLAN</a>, not described here). Depending on the devices involved,
you can configure unnumbered <abbr title="Border Gateway Protocol">BGP</abbr> peers. This does not require any IP
addressing plan and the configuration is therefore minimized.</p>
</div>
<h3 id="route-reflectors">Route reflectors</h3>
<p>Route reflectors are <abbr title="Border Gateway Protocol">BGP</abbr>-speaking boxes acting as a hub for all routes
on a given L2 network but not routing any traffic. We need at least
one of them on each L2 network. We can use more for redundancy.</p>
<p>Here is an example of configuration for <em>Junos</em>:<sup id="fnref:instances"><a class="footnote-ref" href="#fn:instances">10</a></sup></p>
<div class="language-junos codehilite"><pre><span/><code><span class="k">protocols</span> <span class="p">{</span>
    bgp <span class="p">{</span>
        group public-v4 <span class="p">{</span>
            family inet <span class="p">{</span>
                unicast <span class="p">{</span>
                    no-install; <span class="c c-Singleline"># ❶</span>
                <span class="p">}</span>
            <span class="p">}</span>
            type internal;
            cluster <span class="m">198.51.100.126</span><span class="p">;</span> <span class="c c-Singleline"># ❷</span>
            allow <span class="m">198.51.100.0/25</span><span class="p">;</span> <span class="c c-Singleline"># ❸</span>
            neighbor <span class="m">198.51.100.127</span><span class="p">;</span>
        <span class="p">}</span>
        group public-v6 <span class="p">{</span>
            family inet6 <span class="p">{</span>
                unicast <span class="p">{</span>
                    no-install;
                <span class="p">}</span>
            <span class="p">}</span>
            type internal;
            cluster <span class="m">198.51.100.126</span><span class="p">;</span>
            allow <span class="m">2001:db8:c633:6401::/64</span><span class="p">;</span>
            neighbor <span class="m">2001:db8:c633:6401::198</span>.51.100.127;
        <span class="p">}</span>
        ttl <span class="m">255</span><span class="p">;</span>
        bfd-liveness-detection <span class="p">{</span> <span class="c c-Singleline"># ❹</span>
            minimum-interval <span class="m">100</span><span class="p">;</span>
            multiplier <span class="m">5</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
<span class="k">routing-options</span> <span class="p">{</span>
    router-id <span class="m">198.51.100.126</span><span class="p">;</span>
    autonomous-system <span class="m">65000</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>

<p>This route reflector accepts and redistributes IPv4 and IPv6
routes. In ❶, we ensure received routes are not installed in FIB:
route reflectors are not routers.</p>
<p>Each route reflector needs to be assigned a cluster identifier, which
is used in loop detection. In our case, we use the IPv4 address for
this purpose (in ❷). Having a different cluster identifier for each
route reflector on the same network ensures they share the routes they
receive—increasing resiliency.</p>
<p>Instead of explicitly declaring all hypervisors allowed to connect to
this route reflector, a whole subnet is authorized in ❸.<sup id="fnref:security"><a class="footnote-ref" href="#fn:security">11</a></sup> We
also declare the second route reflector for the same network as
a neighbor to ensure they connect to each other.</p>
<p>Another important point of this setup is how to quickly react to
<strong>unavailable paths</strong>. With directly connected <abbr title="Border Gateway Protocol">BGP</abbr> sessions, a faulty
link may be detected immediately and the associated <abbr title="Border Gateway Protocol">BGP</abbr> sessions will
be brought down. This may not always be reliable. Moreover, in our
case, <abbr title="Border Gateway Protocol">BGP</abbr> sessions are established over several switches: a link down
on a path may be left undetected until the hold timer expires.
Therefore, in ❹, we enable <abbr title="Bidirectional Forwarding Detection">BFD</abbr>, a protocol to quickly<sup id="fnref:minimum"><a class="footnote-ref" href="#fn:minimum">12</a></sup> detect
faults in the path between two <abbr title="Border Gateway Protocol">BGP</abbr> peers (<a href="https://www.rfc-editor.org/rfc/rfc5880" title="RFC 5880: Bidirectional Forwarding Detection (BFD)">RFC 5880</a>).</p>
<p>The last point to consider is whether you want to <strong>allow anycast</strong> on
your network: if an IP is advertised from more than one hypervisor,
you may want to:</p>
<ul>
<li>send all flows to only one hypervisor; or</li>
<li>load-balance flows between hypervisors.</li>
</ul>
<p>The second choice provides a scalable L3 load-balancer. With the above
configuration, for each prefix, route reflectors choose one path and
distribute it. Therefore, only one hypervisor will receive packets. To
get load-balancing, you need to enable advertisement of multiple paths
in <abbr title="Border Gateway Protocol">BGP</abbr> (<a href="https://www.rfc-editor.org/rfc/rfc7911" title="RFC 7911: Advertisement of Multiple Paths in BGP">RFC 7911</a>):<sup id="fnref:addpath"><a class="footnote-ref" href="#fn:addpath">13</a></sup></p>
<div class="language-junos codehilite"><pre><span/><code><span class="k">set</span> protocols bgp group public-v4 family inet  unicast add-path send path-count <span class="m">4</span>
<span class="k">set</span> protocols bgp group public-v6 family inet6 unicast add-path send path-count <span class="m">4</span>
</code></pre></div>

<p>Here is an excerpt of <code>show route</code> exhibiting “simple” routes as well
as an anycast route:</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp">&gt; </span>show<span class="w"> </span>route<span class="w"> </span>protocol<span class="w"> </span>bgp
<span class="go">inet.0: 6 destinations, 7 routes (7 active, 1 holddown, 0 hidden)</span>
<span class="go">+ = Active Route, - = Last Active, * = Both</span>

<span class="go">0.0.0.0/0        *[BGP/170] 00:09:01, localpref 100</span>
<span class="go">                    AS path: I, validation-state: unverified</span>
<span class="go">                  &gt; to 198.51.100.1 via em1.90</span>
<span class="go">192.0.2.15/32    *[BGP/170] 00:09:00, localpref 100</span>
<span class="go">                    AS path: I, validation-state: unverified</span>
<span class="go">                  &gt; to 198.51.100.101 via em1.90</span>
<span class="go">203.0.113.1/32   *[BGP/170] 00:09:00, localpref 100</span>
<span class="go">                    AS path: I, validation-state: unverified</span>
<span class="go">                  &gt; to 198.51.100.101 via em1.90</span>
<span class="go">203.0.113.6/32   *[BGP/170] 00:09:00, localpref 100</span>
<span class="go">                    AS path: I, validation-state: unverified</span>
<span class="go">                  &gt; to 198.51.100.102 via em1.90</span>
<span class="go">203.0.113.18/32  *[BGP/170] 00:09:00, localpref 100</span>
<span class="go">                    AS path: I, validation-state: unverified</span>
<span class="go">                  &gt; to 198.51.100.103 via em1.90</span>
<span class="go">203.0.113.10/32  *[BGP/170] 00:09:00, localpref 100</span>
<span class="go">                    AS path: I, validation-state: unverified</span>
<span class="hll"><span class="go">                  &gt; to 198.51.100.101 via em1.90</span>
</span><span class="go">                  [BGP/170] 00:09:00, localpref 100</span>
<span class="go">                    AS path: I, validation-state: unverified</span>
<span class="hll"><span class="go">                  &gt; to 198.51.100.102 via em1.90</span>
</span></code></pre></div>

<p>Complete configuration is available on
<a href="https://github.com/vincentbernat/network-lab/blob/88b9b875c3c48c2f9e3ad4fb4e2719d6c4b3cc22/lab-l3-hyperv/junos-RR3.conf" title="Junos configuration as a route reflector">GitHub</a>. Configurations for <a href="https://github.com/vincentbernat/network-lab/blob/88b9b875c3c48c2f9e3ad4fb4e2719d6c4b3cc22/lab-l3-hyperv/gobgpd.public-ipv4.yaml" title="GoBGP configuration as a route reflector"><em>GoBGP</em></a>,
<a href="https://github.com/vincentbernat/network-lab/blob/88b9b875c3c48c2f9e3ad4fb4e2719d6c4b3cc22/lab-l3-hyperv/bird.RR1.conf" title="BIRD configuration as a route reflector"><em>BIRD</em></a> or <a href="https://github.com/vincentbernat/network-lab/blob/88b9b875c3c48c2f9e3ad4fb4e2719d6c4b3cc22/lab-l3-hyperv/RR7/etc/quagga/Quagga.conf" title="FRR configuration as a route reflector"><em>FRR</em></a> (running on Cumulus Linux) are
also available.<sup id="fnref:bird"><a class="footnote-ref" href="#fn:bird">14</a></sup> The configuration for the private routing
domain is similar. To avoid dedicated boxes, a solution is to run the
route reflectors on some of the top of the rack switches.</p>
<h3 id="hypervisor-configuration">Hypervisor configuration</h3>
<p>Let’s tackle the last step: the hypervisor configuration. We use
<a href="https://bird.network.cz/" title="The BIRD Internet Routing Daemon">BIRD</a> (1.6.x) as a <abbr title="Border Gateway Protocol">BGP</abbr> daemon. It maintains three internal routing
tables (<code>public</code>, <code>private</code>, and <code>local-out</code>). We use a template with
the common properties to connect to a route reflector:</p>
<div class="language-junos codehilite"><pre><span/><code><span class="k">template</span> bgp rr_client <span class="p">{</span>
  local as <span class="m">65000</span><span class="p">;</span>   <span class="c c-Singleline"># Local ASN matches route reflector ASN</span>
  import all;       <span class="c c-Singleline"># Accept all received routes</span>
  export all;       <span class="c c-Singleline"># Send all routes to remote peer</span>
  next hop self;    <span class="c c-Singleline"># Modify next-hop with the IP used for this BGP session</span>
  bfd yes;          <span class="c c-Singleline"># Enable BFD</span>
  direct;           <span class="c c-Singleline"># Not a multi-hop BGP session</span>
  ttl security yes; <span class="c c-Singleline"># GTSM is enabled</span>
  add paths rx;     <span class="c c-Singleline"># Enable ADD-PATH reception (for anycast)</span>

  <span class="c c-Singleline"># Low timers to establish sessions faster</span>
  connect delay time <span class="m">1</span><span class="p">;</span>
  connect retry time <span class="m">5</span><span class="p">;</span>
  error wait time <span class="m">1</span>,5;
  error forget time <span class="m">10</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">table</span> public;
<span class="k">protocol</span> bgp RR1_public from rr_client <span class="p">{</span>
  neighbor <span class="m">198.51.100.126</span> as <span class="m">65000</span><span class="p">;</span>
  table public;
<span class="p">}</span>
<span class="c c-Singleline"># […]</span>
</code></pre></div>

<p>With the above configuration, all routes in <em>BIRD</em>’s <code>public</code> table
are sent to the route reflector <code>198.51.100.126</code>. Any route from the
route reflector is accepted. We also need to connect <em>BIRD</em>’s <code>public</code>
table to the kernel’s one:<sup id="fnref:rt_tables"><a class="footnote-ref" href="#fn:rt_tables">15</a></sup></p>
<div class="language-junos codehilite"><pre><span/><code><span class="k">protocol</span> kernel kernel_public <span class="p">{</span>
  persist;
  scan time <span class="m">10</span><span class="p">;</span>
  import filter <span class="p">{</span>
    <span class="c c-Singleline"># Take any route from kernel,</span>
    <span class="c c-Singleline"># except our last-resort default route</span>
    if krt_metric &lt; <span class="m">4294967294</span> then accept;
    reject;
  <span class="p">};</span>
  export all;      <span class="c c-Singleline"># Put all routes into kernel</span>
  learn;           <span class="c c-Singleline"># Learn routes not added by BIRD</span>
  merge paths yes; <span class="c c-Singleline"># Build ECMP routes if possible</span>
  table public;    <span class="c c-Singleline"># BIRD's table name</span>
  kernel table <span class="m">90</span><span class="p">;</span> <span class="c c-Singleline"># Kernel table number</span>
<span class="p">}</span>
</code></pre></div>

<p>We also need to enable <abbr title="Bidirectional Forwarding Detection">BFD</abbr> on all interfaces:</p>
<div class="language-junos codehilite"><pre><span/><code><span class="k">protocol</span> bfd <span class="p">{</span>
  interface <span class="s2">"*"</span> <span class="p">{</span>
    interval <span class="m">100</span>ms;
    multiplier <span class="m">5</span><span class="p">;</span>
  <span class="p">};</span>
<span class="p">}</span>
</code></pre></div>

<p>To avoid losing <abbr title="Bidirectional Forwarding Detection">BFD</abbr> packets when the conntrack table is full, it is
safer to disable connection tracking for these datagrams:</p>
<div class="language-bash codehilite"><pre><span/><code>ip46tables<span class="w"> </span>-t<span class="w"> </span>raw<span class="w"> </span>-A<span class="w"> </span>PREROUTING<span class="w"> </span>-p<span class="w"> </span>udp<span class="w"> </span>--dport<span class="w"> </span><span class="m">3784</span><span class="w"> </span><span class="se">\</span>
<span class="w">  </span>-m<span class="w"> </span>addrtype<span class="w"> </span>--dst-type<span class="w"> </span>LOCAL<span class="w"> </span>-j<span class="w"> </span>CT<span class="w"> </span>--notrack
ip46tables<span class="w"> </span>-t<span class="w"> </span>raw<span class="w"> </span>-A<span class="w"> </span>OUTPUT<span class="w"> </span>-p<span class="w"> </span>udp<span class="w"> </span>--dport<span class="w"> </span><span class="m">3784</span><span class="w"> </span><span class="se">\</span>
<span class="w">  </span>-m<span class="w"> </span>addrtype<span class="w"> </span>--src-type<span class="w"> </span>LOCAL<span class="w"> </span>-j<span class="w"> </span>CT<span class="w"> </span>--notrack
</code></pre></div>

<p>Some missing bits are:</p>
<ul>
<li><a href="https://github.com/vincentbernat/network-lab/blob/88b9b875c3c48c2f9e3ad4fb4e2719d6c4b3cc22/lab-l3-hyperv/bird-common/hypervisor.conf#L11-L40" title="local-out table configuration">setup of the <code>local-out</code> table</a>;</li>
<li><a href="https://github.com/vincentbernat/network-lab/blob/88b9b875c3c48c2f9e3ad4fb4e2719d6c4b3cc22/lab-l3-hyperv/bird-common/rr-client-private.conf" title="Private routing configuration">setup of the <code>private</code> table</a>; and</li>
<li><a href="https://github.com/vincentbernat/network-lab/blob/88b9b875c3c48c2f9e3ad4fb4e2719d6c4b3cc22/lab-l3-hyperv/bird-common/rr-client6.conf" title="IPv6 configuration">setup for IPv6</a>.<sup id="fnref:ecmpv6"><a class="footnote-ref" href="#fn:ecmpv6">16</a></sup></li>
</ul>
<p>Once the <abbr title="Border Gateway Protocol">BGP</abbr> sessions have been established, we can query the kernel
for the installed routes:</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp">$ </span>ip<span class="w"> </span>route<span class="w"> </span>show<span class="w"> </span>table<span class="w"> </span>public<span class="w"> </span>proto<span class="w"> </span>bird
<span class="go">default</span>
<span class="go">        nexthop via 198.51.100.1 dev eth0.public weight 1</span>
<span class="go">        nexthop via 198.51.100.254 dev eth1.public weight 1</span>
<span class="go">203.0.113.6</span>
<span class="go">        nexthop via 198.51.100.102 dev eth0.public weight 1</span>
<span class="go">        nexthop via 198.51.100.202 dev eth1.public weight 1</span>
<span class="go">203.0.113.18</span>
<span class="go">        nexthop via 198.51.100.103 dev eth0.public weight 1</span>
<span class="go">        nexthop via 198.51.100.203 dev eth1.public weight 1</span>
</code></pre></div>

<h2 id="performance">Performance</h2>
<p>You may be worried about <strong>how much memory</strong> Linux may use when handling many
routes. Well, don’t:</p>
<ul>
<li>128 MiB can fit 1 million IPv4 routes; and</li>
<li>512 MiB can fit 1 million IPv6 routes.</li>
</ul>
<p><em>BIRD</em> uses about the same amount of memory for its own usage. As for
<strong>lookup times</strong>, performance is also excellent with IPv4 and still
quite good with IPv6:</p>
<ul>
<li>30 ns per lookup with 1 million IPv4 routes; and</li>
<li>1.25 µs per lookup with 1 million IPv6 routes.</li>
</ul>
<p>Therefore, the impact of letting Linux handle many routes is very
low. For more details, see “<a href="/en/blog/2017-ipv4-route-lookup-linux" title="IPv4 route lookup on Linux">IPv4 route lookup on Linux</a>” and “<a href="/en/blog/2017-ipv6-route-lookup-linux" title="IPv6 route lookup on Linux">IPv6
route lookup on Linux</a>.”</p>
<h2 id="reverse-path-filtering">Reverse path filtering</h2>
<p>To avoid spoofing, <em>reverse path filtering</em> is enabled on virtual
guest interfaces: Linux will verify the source address is legit by
checking the answer would use the incoming interface as an outgoing
interface. This effectively prevents any possible spoofing from guests.</p>
<p>For IPv4, reverse path filtering can be enabled either through a
per-interface <em>sysctl</em><sup id="fnref:sysctl"><a class="footnote-ref" href="#fn:sysctl">17</a></sup> or through the <code>rpfilter</code> match of
<em>Netfilter</em>. For IPv6, only the second method is available.</p>
<div class="language-bash codehilite"><pre><span/><code><span class="c1"># For IPv6, use NetFilter</span>
ip6tables<span class="w"> </span>-t<span class="w"> </span>raw<span class="w"> </span>-N<span class="w"> </span>RPFILTER
ip6tables<span class="w"> </span>-t<span class="w"> </span>raw<span class="w"> </span>-A<span class="w"> </span>RPFILTER<span class="w"> </span>-m<span class="w"> </span>rpfilter<span class="w"> </span>-j<span class="w"> </span>RETURN
ip6tables<span class="w"> </span>-t<span class="w"> </span>raw<span class="w"> </span>-A<span class="w"> </span>RPFILTER<span class="w"> </span>-m<span class="w"> </span>rpfilter<span class="w"> </span>--accept-local<span class="w"> </span><span class="se">\</span>
<span class="w">  </span>-m<span class="w"> </span>addrtype<span class="w"> </span>--dst-type<span class="w"> </span>MULTICAST<span class="w"> </span>-j<span class="w"> </span>DROP
ip6tables<span class="w"> </span>-t<span class="w"> </span>raw<span class="w"> </span>-A<span class="w"> </span>RPFILTER<span class="w"> </span>-m<span class="w"> </span>limit<span class="w"> </span>--limit<span class="w"> </span><span class="m">5</span>/s<span class="w"> </span>--limit-burst<span class="w"> </span><span class="m">5</span><span class="w"> </span><span class="se">\</span>
<span class="w">  </span>-j<span class="w"> </span>LOG<span class="w"> </span>--log-prefix<span class="w"> </span><span class="s2">"NF: rpfilter: "</span><span class="w"> </span>--log-level<span class="w"> </span>warning
ip6tables<span class="w"> </span>-t<span class="w"> </span>raw<span class="w"> </span>-A<span class="w"> </span>RPFILTER<span class="w"> </span>-j<span class="w"> </span>DROP
ip6tables<span class="w"> </span>-t<span class="w"> </span>raw<span class="w"> </span>-A<span class="w"> </span>PREROUTING<span class="w"> </span>-i<span class="w"> </span>vnet+<span class="w"> </span>-j<span class="w"> </span>RPFILTER

<span class="c1"># For IPv4, use sysctls</span>
sysctl<span class="w"> </span>-qw<span class="w"> </span>net.ipv4.conf.all.rp_filter<span class="o">=</span><span class="m">0</span>
<span class="k">for</span><span class="w"> </span>iface<span class="w"> </span><span class="k">in</span><span class="w"> </span>/sys/class/net/vnet*<span class="p">;</span><span class="w"> </span><span class="k">do</span>
<span class="w">    </span>sysctl<span class="w"> </span>-qw<span class="w"> </span>net.ipv4.conf.<span class="si">${</span><span class="nv">iface</span><span class="p">##*/</span><span class="si">}</span>.rp_filter<span class="o">=</span><span class="m">1</span>
<span class="k">done</span>
</code></pre></div>

<p>There is no need to prevent L2 spoofing as there is no gain for the
attacker.</p>
<h1 id="keeping-guests-in-the-dark">Keeping guests in the dark</h1>
<p>An important aspect of the solution is to ensure guests believe they
are attached to a classic L2 network (with an IP in a subnet).</p>
<p>The first step is to provide them with a working default gateway. On
the hypervisor, this can be done by assigning the default gateway IP
directly to the guest interface:</p>
<div class="language-bash codehilite"><pre><span/><code>ip<span class="w"> </span>addr<span class="w"> </span>add<span class="w"> </span><span class="m">203</span>.0.113.254/32<span class="w"> </span>dev<span class="w"> </span>vnet5<span class="w"> </span>scope<span class="w"> </span>link
</code></pre></div>

<p>Our main goal is to ensure Linux will answer <abbr title="Address Resolution Protocol">ARP</abbr> requests for the
gateway IP. Configuring a <code>/32</code> is enough for this and we do not want
to configure a larger subnet as, by default, Linux would install a
route for the subnet to this interface, which would be
incorrect.<sup id="fnref:noprefixroute"><a class="footnote-ref" href="#fn:noprefixroute">18</a></sup></p>
<p>For IPv6, this is not needed as we rely on link-local addresses
instead.</p>
<p>A guest may also try to speak with other guests on the same
subnet. The hypervisor will answer <abbr title="Address Resolution Protocol">ARP</abbr> requests on their behalf. Once
it starts receiving IP traffic, it will route it to the appropriate
interface. This can be done by enabling <abbr title="Address Resolution Protocol">ARP</abbr> proxying on the guest
interface:</p>
<div class="language-bash codehilite"><pre><span/><code>sysctl<span class="w"> </span>-qw<span class="w"> </span>net.ipv4.conf.vnet5.proxy_arp<span class="o">=</span><span class="m">1</span>
sysctl<span class="w"> </span>-qw<span class="w"> </span>net.ipv4.neigh.vnet5.proxy_delay<span class="o">=</span><span class="m">0</span>
</code></pre></div>

<p>For IPv6, Linux <abbr title="Neighbor Discovery Protocol">NDP</abbr> proxying is far less convenient. Instead,
<a href="https://github.com/DanielAdolfsson/ndppd/" title="NDP Proxy Daemon">ndppd</a> can handle this task. For each interface, we use the
following configuration snippet:</p>
<div class="language-junos codehilite"><pre><span/><code><span class="k">proxy</span> vnet5 <span class="p">{</span>
  rule <span class="m">2001:db8:cb00:7100::/64</span> <span class="p">{</span>
    static
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>

<p><em>ndppd</em> <a href="https://github.com/DanielAdolfsson/ndppd/issues/41" title="Source address used by ndppd may not be a neighbor address #41">does not spoof the source address</a> and some systems are
<a href="https://github.com/openbsd/src/blob/442f4bb483c9350a9d4c74fd7a2ae45f06c454dd/sys/netinet6/nd6_nbr.c#L155-L159" title="nd6_ns_input() on OpenBSD">pickier about it</a>. Therefore, an appropriate address needs to be
configured on each interface:</p>
<div class="language-bash codehilite"><pre><span/><code>ip<span class="w"> </span>addr<span class="w"> </span>add<span class="w"> </span><span class="m">2001</span>:db8:cb00:7100::1/64<span class="w"> </span>dev<span class="w"> </span>vnet5<span class="w"> </span>noprefixroute<span class="w"> </span>nodad
</code></pre></div>

<p>For <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr>, some daemons may have difficulties to handle this odd
configuration (with the <code>/32</code> IP address on the interface), but
<a href="https://thekelleys.org.uk/dnsmasq/doc.html">dnsmasq</a> accepts such an oddity. For IPv6, assuming the assigned IP
address is a <a href="https://en.wikipedia.org/wiki/IPv6_address#Modified_EUI-64" title="Modified IPv6 EUI-64 on Wikipedia">EUI-64</a> one, <a href="https://radvd.litech.org/" title="Linux IPv6 Router Advertisement Daemon (radvd)">radvd</a> works with the following
configuration on each interface:</p>
<div class="language-junos codehilite"><pre><span/><code><span class="k">interface</span> vnet5 <span class="p">{</span>
  AdvSendAdvert on;
  prefix <span class="m">2001:db8:cb00:7100::/64</span> <span class="p">{</span>
    AdvOnLink on;
    AdvAutonomous on;
    AdvRouterAddr on;
  <span class="p">};</span>
<span class="p">};</span>
</code></pre></div>

<h1 id="conclusion-and-future-work">Conclusion and future work</h1>
<p>This setup should work with <em>BIRD</em> 1.6.3 and a Linux 3.15+
kernel. Compared to legacy L2 networks, it brings flexibility and
resiliency while keeping guests unaware of the change. By handing over
routing to Linux, this design is also cheap as existing equipment can
be reused. Still, operation of such a solution is simple enough once
the basic concepts are understood—IP rules, routing tables, and <abbr title="Border Gateway Protocol">BGP</abbr>
sessions.</p>
<p>There are several potential improvements:</p>
<dl>
<dt>Using <abbr title="Virtual Routing and Forwarding">VRF</abbr></dt>
<dd>Starting from Linux 4.3, L3 <abbr title="Virtual Routing and Forwarding">VRF</abbr> domains enable binding interfaces to
  routing tables. We could have three VRFs: <code>public</code>, <code>private</code>, and
  <code>local-out</code>. This would improve performance by removing most IP
  rules (but until Linux 4.8, performance is crippled due to
  offloading features not enabled, see <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=7889681f4a6c2148e1245604bac751a1cae8f882" title="net: vrf: Update flags and features settings">commit 7889681f4a6c</a>). For
  more information, have a look at the <a href="https://www.kernel.org/doc/Documentation/networking/vrf.txt" title="Virtual Routing and Forwarding (VRF)">kernel documentation</a>.</dd>
<dt>Full L3 routing</dt>
<dd>The <abbr title="Border Gateway Protocol">BGP</abbr> setup can be enhanced and simplified by using an L3 fabric
  and using some autoconfiguration features. Cumulus published a nice
  book, “<a href="https://www.nvidia.com/en-us/networking/border-gateway-protocol/" title="BGP in the data center: a complete guide to BGP for the modern data center"><abbr title="Border Gateway Protocol">BGP</abbr> in the datacenter</a>,” on this topic. However, this
  requires all <abbr title="Border Gateway Protocol">BGP</abbr> speakers to support these features. On the
  hypervisors, this would mean using <em>FRR</em> while the various network
  equipment would need to run <em>Cumulus Linux</em>.</dd>
<dt><abbr title="Border Gateway Protocol">BGP</abbr> resiliency with <abbr title="Border Gateway Protocol">BGP</abbr> <abbr title="Long-Lived Graceful Restart">LLGR</abbr></dt>
<dd>Using short <abbr title="Bidirectional Forwarding Detection">BFD</abbr> timers make our network react fast to any disruption
  by quickly invalidating faulty paths without relying on link status.
  However, under load or congestion, <abbr title="Bidirectional Forwarding Detection">BFD</abbr> packets may be lost, making
  the whole hypervisor unreachable until <abbr title="Border Gateway Protocol">BGP</abbr> sessions can be brought
  up again. Some <abbr title="Border Gateway Protocol">BGP</abbr> implementations support <em>Long-Lived <abbr title="Border Gateway Protocol">BGP</abbr> Graceful
  Restart</em>, an extension allowing stale routes to be retained with a
  lower priority (see <a href="https://datatracker.ietf.org/doc/html/draft-uttaro-idr-bgp-persistence-03" title="Support for Long-lived BGP Graceful Restart">draft-uttaro-idr-bgp-persistence-03</a>). This
  is an ideal solution to our problem: these stale routes are used
  only as last resort, after all links have failed. <del>Currently, no
  open-source implementation supports this draft.</del> See
  “<a href="/en/blog/2018-bgp-llgr" title="BGP LLGR: robust and reactive BGP sessions"><abbr title="Border Gateway Protocol">BGP</abbr> <abbr title="Long-Lived Graceful Restart">LLGR</abbr>: robust and reactive <abbr title="Border Gateway Protocol">BGP</abbr> sessions</a>” for more details.</dd>
</dl>
<div class="admonition">
<p class="admonition-title">Update (2019-08)</p>
<p>For better security, it is possible to reuse
features around the RPKI to validate announcements from each hypervisor.
See “<a href="/en/blog/2019-bgp-host-rpki" title="Securing BGP on the host with origin validation">Securing <abbr title="Border Gateway Protocol">BGP</abbr> on the host with origin validation</a>.”</p>
</div>
<div class="admonition">
<p class="admonition-title">Update (2021-05)</p>
<p>Facebook published “<a href="https://www.usenix.org/system/files/nsdi21-abhashkumar.pdf">Running <abbr title="Border Gateway Protocol">BGP</abbr> in Data
Centers at Scale</a>.” This paper contains some insights on how
Facebook manages <abbr title="Border Gateway Protocol">BGP</abbr> in its datacenters. This is an orthogonal issue
to extending <abbr title="Border Gateway Protocol">BGP</abbr> to the server but it is an interesting read,
notably the use of policies to help operability.</p>
</div>
<div class="admonition">
<p class="admonition-title">Update (2021-10)</p>
<p>We have published the configuration used at
Blade in our San Francisco and Seoul data centers that are using
<abbr title="Border Gateway Protocol">BGP</abbr>-to-the-hosts with an L3 fabric. This includes the configuration
for <a href="https://github.com/jerikan-network/cmdb/blob/generated-public/output/to1-p2.ussfo03.blade-group.net/config.txt">Juniper top-of-the-rack switches</a> and
<a href="https://github.com/jerikan-network/cmdb/tree/generated-public/output/to1-p5.ussfo03.blade-group.net">Cumulus top-of-the-rack switches</a>. These
feature a <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> server for provisioning. The <abbr title="Border Gateway Protocol">BGP</abbr> configuration on the
servers is done via an <a href="https://github.com/jerikan-network/cmdb/blob/generated-public/output/gateway1.ussfo03.blade-group.net/nginx.conf">API served by nginx</a>.</p>
</div>
<div class="footnote">
<hr/>
<ol>
<li id="fn:mclag">
<p><abbr title="Multi-Chassis Link Aggregation">MC-LAG</abbr> has been standardized in <a href="https://sci-hub.fr/10.1109/IEEESTD.2014.7055197" title="IEEE 802.1AX-2014 on Sci-Hub">IEEE 802.1AX-2014</a>. However, most vendors are likely to stick with
their implementations. With <abbr title="Multi-Chassis Link Aggregation">MC-LAG</abbr>, control planes remain
independent. <a class="footnote-backref" href="#fnref:mclag" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:incident">
<p>An incident can stem from an operator mistake, but also
from software bugs, which are more likely to happen in complex
implementations during infrequent operations, like a software
upgrade. <a class="footnote-backref" href="#fnref:incident" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
<li id="fn:nodistribute">
<p>These routes should not be distributed over
<abbr title="Border Gateway Protocol">BGP</abbr>. Hypervisors should receive a default route with a lower
metric from edge routers. <a class="footnote-backref" href="#fnref:nodistribute" title="Jump back to footnote 3 in the text">↩</a></p>
</li>
<li id="fn:ddos">
<p>Do not increase this value too much. An external attacker can
fill the cache easily. The value proposed here corresponds to
256 MiB of memory. See « <a href="/en/blog/2017-ipv6-route-lookup-linux#caching" title="IPv6 route lookup on Linux">IPv6 route lookup on
Linux</a> » for more details. <a class="footnote-backref" href="#fnref:ddos" title="Jump back to footnote 4 in the text">↩</a></p>
</li>
<li id="fn:privacy">
<p>This example assumes the guest does not use privacy
extensions (<a href="https://www.rfc-editor.org/rfc/rfc3041" title="RFC 3041: Privacy Extensions for Stateless Address Autoconfiguration in IPv6">RFC 3041</a>). A workaround is to assign a whole <code>/64</code>
for each guest. This also removes the need to use <abbr title="Neighbor Discovery Protocol">NDP</abbr> proxying. <a class="footnote-backref" href="#fnref:privacy" title="Jump back to footnote 5 in the text">↩</a></p>
</li>
<li id="fn:arp">
<p>A static <abbr title="Address Resolution Protocol">ARP</abbr> entry can also be added with the remote MAC
address. <a class="footnote-backref" href="#fnref:arp" title="Jump back to footnote 6 in the text">↩</a></p>
</li>
<li id="fn:onlink">
<p>The <code>onlink</code> keyword is needed for the <abbr title="Border Gateway Protocol">BGP</abbr> daemon, <em>BIRD</em>,
to accept the next-hop. Otherwise, it would consider there isn’t a
directly connected (non-host) route covering it. <a class="footnote-backref" href="#fnref:onlink" title="Jump back to footnote 7 in the text">↩</a></p>
</li>
<li id="fn:current">
<p>For example, a <a href="https://www.juniper.net/us/en/products/switches/qfx-series/qfx5100-ethernet-switch-datasheet.html" title="QFX5100 10/40GbE data center switches">Juniper QFX5100</a> supports about 200k
IPv4 routes (about US$10,000, with Broadcom Trident II chipset). On
the other hand, an <a href="https://www.arista.com/en/products/7280r-series" title="Arista 7280R Series">Arista 7208SR</a> supports 1.2M IPv4 routes
(about US$20,000, with Broadcom Jericho chipset), through the use of
an external <abbr title="Ternary content-addressable memory">TCAM</abbr>. A <a href="https://web.archive.org/web/2017/https://www.juniper.net/us/en/products/routers/mx-series/mx240-universal-routing-platform.html" title="MX240 edge router">Juniper MX240</a> would support more than 2M
IPv4 routes (about US$30,000 for an empty chassis with two routing
engines, with Juniper Trio chipset) with a lower density. <a class="footnote-backref" href="#fnref:current" title="Jump back to footnote 8 in the text">↩</a></p>
</li>
<li id="fn:scale">
<p>From a scalability point of view, with switches able to
handle 32k MAC addresses, the fabric can host more than 8,000
hypervisors (more than 5 million virtual guests). Therefore,
cost-effective switches can be used as both leaves and
spines. Each hypervisor has to handle all routes, an easy task for
Linux. <a class="footnote-backref" href="#fnref:scale" title="Jump back to footnote 9 in the text">↩</a></p>
</li>
<li id="fn:instances">
<p>Using routing instances would enable hosting several
route reflectors on the same box. This is not used in this example
but should be considered to reduce costs. <a class="footnote-backref" href="#fnref:instances" title="Jump back to footnote 10 in the text">↩</a></p>
</li>
<li id="fn:security">
<p>This prevents the use of any authentication mechanism:
<abbr title="Border Gateway Protocol">BGP</abbr> usually relies on TCP MD5 signature (<a href="https://www.rfc-editor.org/rfc/rfc2385" title="RFC 2385: Protection of BGP Sessions via the TCP MD5 Signature Option">RFC 2385</a>) to
authenticate <abbr title="Border Gateway Protocol">BGP</abbr> sessions. On most OSes, this requires knowing
allowed peers. To tighten a bit the security in absence of
authentication, we use the <em>Generalized TTL Security Mechanism</em>
(<a href="https://www.rfc-editor.org/rfc/rfc5082" title="RFC 5082: The Generalized TTL Security Mechanism (GTSM)">RFC 5082</a>). For <em>Junos</em>, the configuration presented here
(with <code>ttl 255</code>) is incomplete. A <a href="https://www.juniper.net/documentation/us/en/software/junos/cli-reference/topics/ref/statement/multihop-edit-protocols-bgp.html" title="Juniper documentation: ttl (Protocols BGP)">firewall filter is also
needed</a>. <a class="footnote-backref" href="#fnref:security" title="Jump back to footnote 11 in the text">↩</a></p>
</li>
<li id="fn:minimum">
<p>On the QFX5000 Series switches, the minimum interval is
<a href="https://www.juniper.net/documentation/us/en/software/junos/high-availability/topics/topic-map/bfd.html#id-understanding-bfd-for-static-routes-for-faster-network-failure-detection" title="Understanding Bidirectional Forwarding Detection (BFD)">officially</a> one second. This is quite unfortunate as it makes
<abbr title="Bidirectional Forwarding Detection">BFD</abbr> mostly useless. <a class="footnote-backref" href="#fnref:minimum" title="Jump back to footnote 12 in the text">↩</a></p>
</li>
<li id="fn:addpath">
<p>Unfortunately, for no good reason, Junos doesn’t support
the <abbr title="Border Gateway Protocol">BGP</abbr> add-path extension in a routing instance. Such a
configuration is possible with Cumulus Linux. <a class="footnote-backref" href="#fnref:addpath" title="Jump back to footnote 13 in the text">↩</a></p>
</li>
<li id="fn:bird">
<p>Only <em>BIRD</em> comes with <abbr title="Bidirectional Forwarding Detection">BFD</abbr> support out of the box but it does
not support implicit peers. <em>FRR</em> needs Cumulus’s <a href="https://github.com/CumulusNetworks/ptm" title="Prescriptive Topology Manager Daemon">PTMD</a>. If you
don’t care about <abbr title="Bidirectional Forwarding Detection">BFD</abbr>, <em>GoBGP</em> is really nice as a route reflector. <a class="footnote-backref" href="#fnref:bird" title="Jump back to footnote 14 in the text">↩</a></p>
</li>
<li id="fn:rt_tables">
<p>Kernel tables are numbered. <code>ip</code> can use names declared
in <code>/etc/iproute2/rt_tables</code>. <a class="footnote-backref" href="#fnref:rt_tables" title="Jump back to footnote 15 in the text">↩</a></p>
</li>
<li id="fn:ecmpv6">
<p>It should be noted that ECMP with IPv6 only works from
<em>BIRD</em> 1.6.1. Moreover, when using Linux 4.11 or more recent, you
need to apply <a href="https://gitlab.nic.cz/labs/bird/-/commit/98bb80a243b58c43453e9be69d19d0350286549c" title="KRT: Fix IPv6 ECMP handling with Linux 4.11+">commit 98bb80a243b5</a>. <a class="footnote-backref" href="#fnref:ecmpv6" title="Jump back to footnote 16 in the text">↩</a></p>
</li>
<li id="fn:sysctl">
<p>For a given interface, Linux uses the maximum value between
the <em>sysctl</em> for <code>all</code> and the one for the interface. <a class="footnote-backref" href="#fnref:sysctl" title="Jump back to footnote 17 in the text">↩</a></p>
</li>
<li id="fn:noprefixroute">
<p>It is possible to prevent Linux from installing a
connected route by using the <code>noprefixroute</code> flag. However, this
flag is only available since Linux 4.4 for IPv4. Only use this
flag if your <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> server is giving you a hard time as it may
trigger other issues (related to the promotion of secondary
addresses). <a class="footnote-backref" href="#fnref:noprefixroute" title="Jump back to footnote 18 in the text">↩</a></p>
</li>
</ol>
</div>
      </div>
    </content>
  </entry>
  <entry>
    <title type="html">(Micro)​benchmarking Linux kernel functions</title>
    <author><name>Vincent Bernat</name></author>
    <link href="https://vincent.bernat.ch/en/blog/2017-linux-kernel-microbenchmark" rel="alternate"/>
    <link href="https://vincent.bernat.ch/en/blog/2017-linux-kernel-microbenchmark#isso-thread" rel="replies" type="text/html"/>
    <updated>2017-12-28T08:27:30Z</updated>
    <id>http://www.luffy.cx/en/blog/2017-linux-kernel-microbenchmark.html</id>

    <content type="xhtml">
      <div xmlns="http://www.w3.org/1999/xhtml">
<p>Usually, the performance of a Linux subsystem is measured through an
external (local or remote) process stressing it. Depending on the
input point used, a large portion of code may be involved. To
benchmark a single function, one solution is to write a <strong>kernel
module</strong>.</p>
<div class="toc">
<ul>
<li><a href="#minimal-kernel-module">Minimal kernel module</a></li>
<li><a href="#configurable-parameters">Configurable parameters</a></li>
<li><a href="#meaningful-statistics">Meaningful statistics</a></li>
<li><a href="#benchmark-validity">Benchmark validity</a></li>
<li><a href="#alternative">Alternative</a></li>
</ul>
</div>
<h1 id="minimal-kernel-module">Minimal kernel module</h1>
<p>Suppose we want to benchmark the IPv4 route lookup function,
<code>fib_lookup()</code>. The following kernel function executes 1,000 lookups
for 8.8.8.8 and returns the average value.<sup id="fnref:loop"><a class="footnote-ref" href="#fn:loop">1</a></sup> It uses the
<code>get_cycles()</code> function to compute the execution “time.”</p>
<div class="language-c codehilite"><pre><span/><code><span class="cm">/* Execute a benchmark on fib_lookup() and put</span>
<span class="cm">   result into the provided buffer `buf`. */</span>
<span class="k">static</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="nf">do_bench</span><span class="p">(</span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">)</span>
<span class="p">{</span>
<span class="w">    </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="n">t1</span><span class="p">,</span><span class="w"> </span><span class="n">t2</span><span class="p">;</span>
<span class="w">    </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="n">total</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span>
<span class="w">    </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="n">i</span><span class="p">;</span>
<span class="w">    </span><span class="kt">unsigned</span><span class="w"> </span><span class="n">count</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1000</span><span class="p">;</span>
<span class="w">    </span><span class="kt">int</span><span class="w"> </span><span class="n">err</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span>
<span class="w">    </span><span class="k">struct</span><span class="w"> </span><span class="nc">fib_result</span><span class="w"> </span><span class="n">res</span><span class="p">;</span>
<span class="w">    </span><span class="k">struct</span><span class="w"> </span><span class="nc">flowi4</span><span class="w"> </span><span class="n">fl4</span><span class="p">;</span>

<span class="w">    </span><span class="n">memset</span><span class="p">(</span><span class="o">&amp;</span><span class="n">fl4</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="k">sizeof</span><span class="p">(</span><span class="n">fl4</span><span class="p">));</span>
<span class="w">    </span><span class="n">fl4</span><span class="p">.</span><span class="n">daddr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">in_aton</span><span class="p">(</span><span class="s">"8.8.8.8"</span><span class="p">);</span>

<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">count</span><span class="p">;</span><span class="w"> </span><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="n">t1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">get_cycles</span><span class="p">();</span>
<span class="hll"><span class="w">        </span><span class="n">err</span><span class="w"> </span><span class="o">|=</span><span class="w"> </span><span class="n">fib_lookup</span><span class="p">(</span><span class="o">&amp;</span><span class="n">init_net</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">fl4</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">res</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span>
</span><span class="w">        </span><span class="n">t2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">get_cycles</span><span class="p">();</span>
<span class="w">        </span><span class="n">total</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="n">t2</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">t1</span><span class="p">;</span>
<span class="w">    </span><span class="p">}</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">scnprintf</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="n">PAGE_SIZE</span><span class="p">,</span><span class="w"> </span><span class="s">"err=%d msg=</span><span class="se">\"</span><span class="s">lookup error</span><span class="se">\"\n</span><span class="s">"</span><span class="p">,</span><span class="w"> </span><span class="n">err</span><span class="p">);</span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="n">scnprintf</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="n">PAGE_SIZE</span><span class="p">,</span><span class="w"> </span><span class="s">"avg=%llu</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="w"> </span><span class="n">total</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="n">count</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>

<p>Now, we need to embed this function in a kernel module. The following
code registers a <em>sysfs</em> directory containing a pseudo-file
<code>run</code>. When a user queries this file, the module runs the benchmark
function and returns the result as content.</p>
<div class="language-c codehilite"><pre><span/><code><span class="cp">#define pr_fmt(fmt) "kbench: " fmt</span>

<span class="cp">#include</span><span class="w"> </span><span class="cpf">&lt;linux/kernel.h&gt;</span>
<span class="cp">#include</span><span class="w"> </span><span class="cpf">&lt;linux/version.h&gt;</span>
<span class="cp">#include</span><span class="w"> </span><span class="cpf">&lt;linux/module.h&gt;</span>
<span class="cp">#include</span><span class="w"> </span><span class="cpf">&lt;linux/inet.h&gt;</span>
<span class="cp">#include</span><span class="w"> </span><span class="cpf">&lt;linux/timex.h&gt;</span>
<span class="cp">#include</span><span class="w"> </span><span class="cpf">&lt;net/ip_fib.h&gt;</span>

<span class="cm">/* When a user fetches the content of the "run" file, execute the</span>
<span class="cm">   benchmark function. */</span>
<span class="k">static</span><span class="w"> </span><span class="kt">ssize_t</span><span class="w"> </span><span class="nf">run_show</span><span class="p">(</span><span class="k">struct</span><span class="w"> </span><span class="nc">kobject</span><span class="w"> </span><span class="o">*</span><span class="n">kobj</span><span class="p">,</span>
<span class="w">                        </span><span class="k">struct</span><span class="w"> </span><span class="nc">kobj_attribute</span><span class="w"> </span><span class="o">*</span><span class="n">attr</span><span class="p">,</span>
<span class="w">                        </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">)</span>
<span class="p">{</span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="n">do_bench</span><span class="p">(</span><span class="n">buf</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">static</span><span class="w"> </span><span class="k">struct</span><span class="w"> </span><span class="nc">kobj_attribute</span><span class="w"> </span><span class="n">run_attr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">__ATTR_RO</span><span class="p">(</span><span class="n">run</span><span class="p">);</span>
<span class="k">static</span><span class="w"> </span><span class="k">struct</span><span class="w"> </span><span class="nc">attribute</span><span class="w"> </span><span class="o">*</span><span class="n">bench_attributes</span><span class="p">[]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="o">&amp;</span><span class="n">run_attr</span><span class="p">.</span><span class="n">attr</span><span class="p">,</span>
<span class="w">    </span><span class="nb">NULL</span>
<span class="p">};</span>
<span class="k">static</span><span class="w"> </span><span class="k">struct</span><span class="w"> </span><span class="nc">attribute_group</span><span class="w"> </span><span class="n">bench_attr_group</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="p">.</span><span class="n">attrs</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">bench_attributes</span><span class="p">,</span>
<span class="p">};</span>
<span class="k">static</span><span class="w"> </span><span class="k">struct</span><span class="w"> </span><span class="nc">kobject</span><span class="w"> </span><span class="o">*</span><span class="n">bench_kobj</span><span class="p">;</span>

<span class="kt">int</span><span class="w"> </span><span class="nf">init_module</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="w">    </span><span class="kt">int</span><span class="w"> </span><span class="n">rc</span><span class="p">;</span>
<span class="w">    </span><span class="cm">/* ❶ Create a simple kobject named "kbench" in /sys/kernel. */</span>
<span class="hll"><span class="w">    </span><span class="n">bench_kobj</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">kobject_create_and_add</span><span class="p">(</span><span class="s">"kbench"</span><span class="p">,</span><span class="w"> </span><span class="n">kernel_kobj</span><span class="p">);</span>
</span><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="n">bench_kobj</span><span class="p">)</span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="o">-</span><span class="n">ENOMEM</span><span class="p">;</span>

<span class="w">    </span><span class="cm">/* ❷ Create the files associated with this kobject. */</span>
<span class="hll"><span class="w">    </span><span class="n">rc</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">sysfs_create_group</span><span class="p">(</span><span class="n">bench_kobj</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">bench_attr_group</span><span class="p">);</span>
</span><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">rc</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="n">kobject_put</span><span class="p">(</span><span class="n">bench_kobj</span><span class="p">);</span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">rc</span><span class="p">;</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span><span class="w"> </span><span class="nf">cleanup_module</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="w">    </span><span class="n">kobject_put</span><span class="p">(</span><span class="n">bench_kobj</span><span class="p">);</span>
<span class="p">}</span>

<span class="cm">/* Metadata about this module */</span>
<span class="n">MODULE_LICENSE</span><span class="p">(</span><span class="s">"GPL"</span><span class="p">);</span>
<span class="n">MODULE_DESCRIPTION</span><span class="p">(</span><span class="s">"Microbenchmark for fib_lookup()"</span><span class="p">);</span>
</code></pre></div>

<p>In ❶, <code>kobject_create_and_add()</code> creates a new <code>kobject</code> named
<code>kbench</code>. A <code>kobject</code> is the abstraction behind the <em>sysfs</em>
filesystem. This new <code>kobject</code> is visible as the <code>/sys/kernel/kbench/</code>
directory.</p>
<p>In ❷, <code>sysfs_create_group()</code> attaches a set of attributes to
our <code>kobject</code>. These attributes are materialized as files inside
<code>/sys/kernel/kbench/</code>. Currently, we declare only one of them, <code>run</code>,
with the <code>__ATTR_RO</code> macro. The attribute is therefore read-only
(<code>0444</code>) and when a user tries to fetch the content of the file, the
<code>run_show()</code> function is invoked with a buffer of <code>PAGE_SIZE</code> bytes as
last argument and is expected to return the number of bytes written.</p>
<p>For more details, you can look at the <a href="https://elixir.bootlin.com/linux/v4.14.5/source/Documentation/kobject.txt" title="Everything you never wanted to know about kobjects, ksets, and ktypes">documentation in the
kernel</a> and the <a href="https://elixir.bootlin.com/linux/v4.14.5/source/samples/kobject/kobject-example.c" title="Sample kobject implementation">associated example</a>. Beware,
random posts found on the web (including this one) may be outdated.<sup id="fnref:backward"><a class="footnote-ref" href="#fn:backward">2</a></sup></p>
<p>The following <code>Makefile</code> will compile this example:</p>
<div class="language-makefile codehilite"><pre><span/><code><span class="c"># Kernel module compilation</span>
<span class="nv">KDIR</span><span class="w"> </span><span class="o">=</span><span class="w"> </span>/lib/modules/<span class="k">$(</span>shell<span class="w"> </span>uname<span class="w"> </span>-r<span class="k">)</span>/build
<span class="nv">obj-m</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span>kbench_mod.o
<span class="nf">kbench_mod.ko</span><span class="o">:</span><span class="w"> </span><span class="n">kbench_mod</span>.<span class="n">c</span>
<span class="w">    </span>make<span class="w"> </span>-C<span class="w"> </span><span class="k">$(</span>KDIR<span class="k">)</span><span class="w"> </span><span class="nv">M</span><span class="o">=</span><span class="k">$(</span>PWD<span class="k">)</span><span class="w"> </span>modules
</code></pre></div>

<p>After executing <code>make</code>, you should get a <code>kbench_mod.ko</code> file:</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp">$ </span>modinfo<span class="w"> </span>kbench_mod.ko
<span class="go">filename:       /home/bernat/code/…/kbench_mod.ko</span>
<span class="go">description:    Microbenchmark for fib_lookup()</span>
<span class="go">license:        GPL</span>
<span class="go">depends:</span>
<span class="go">name:           kbench_mod</span>
<span class="go">vermagic:       4.14.0-1-amd64 SMP mod_unload modversions</span>
</code></pre></div>

<p>You can load it and execute the benchmark:</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp">$ </span>insmod<span class="w"> </span>./kbench_mod.ko
<span class="gp">$ </span>ls<span class="w"> </span>-l<span class="w"> </span>/sys/kernel/kbench/run
<span class="go">-r--r--r-- 1 root root 4096 déc.  10 16:05 /sys/kernel/kbench/run</span>
<span class="gp">$ </span>cat<span class="w"> </span>/sys/kernel/kbench/run
<span class="go">avg=75</span>
</code></pre></div>

<p>The result is a number of cycles. You can get an approximate time in
nanoseconds if you divide it by the frequency of your processor in
gigahertz (25 ns if you have a 3 GHz processor).<sup id="fnref:frequency"><a class="footnote-ref" href="#fn:frequency">3</a></sup></p>
<h1 id="configurable-parameters">Configurable parameters</h1>
<p>The module hard-codes two constants: the number of loops and the
destination address to test. We can make these parameters
user-configurable by exposing them as attributes of our <code>kobject</code> and
define a pair of functions to read/write them:</p>
<div class="language-c codehilite"><pre><span/><code><span class="k">static</span><span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="n">loop_count</span><span class="w">      </span><span class="o">=</span><span class="w"> </span><span class="mi">5000</span><span class="p">;</span>
<span class="k">static</span><span class="w"> </span><span class="n">u32</span><span class="w">           </span><span class="n">flow_dst_ipaddr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mh">0x08080808</span><span class="p">;</span>

<span class="cm">/* A mutex is used to ensure we are thread-safe when altering attributes. */</span>
<span class="k">static</span><span class="w"> </span><span class="n">DEFINE_MUTEX</span><span class="p">(</span><span class="n">kb_lock</span><span class="p">);</span>

<span class="cm">/* Show the current value for loop count. */</span>
<span class="k">static</span><span class="w"> </span><span class="kt">ssize_t</span><span class="w"> </span><span class="nf">loop_count_show</span><span class="p">(</span><span class="k">struct</span><span class="w"> </span><span class="nc">kobject</span><span class="w"> </span><span class="o">*</span><span class="n">kobj</span><span class="p">,</span>
<span class="w">                               </span><span class="k">struct</span><span class="w"> </span><span class="nc">kobj_attribute</span><span class="w"> </span><span class="o">*</span><span class="n">attr</span><span class="p">,</span>
<span class="w">                               </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">)</span>
<span class="p">{</span>
<span class="w">    </span><span class="kt">ssize_t</span><span class="w"> </span><span class="n">res</span><span class="p">;</span>
<span class="w">    </span><span class="n">mutex_lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">kb_lock</span><span class="p">);</span>
<span class="w">    </span><span class="n">res</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">scnprintf</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="n">PAGE_SIZE</span><span class="p">,</span><span class="w"> </span><span class="s">"%lu</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="w"> </span><span class="n">loop_count</span><span class="p">);</span>
<span class="w">    </span><span class="n">mutex_unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">kb_lock</span><span class="p">);</span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="n">res</span><span class="p">;</span>
<span class="p">}</span>

<span class="cm">/* Store a new value for loop count. */</span>
<span class="k">static</span><span class="w"> </span><span class="kt">ssize_t</span><span class="w"> </span><span class="nf">loop_count_store</span><span class="p">(</span><span class="k">struct</span><span class="w"> </span><span class="nc">kobject</span><span class="w"> </span><span class="o">*</span><span class="n">kobj</span><span class="p">,</span>
<span class="w">                                </span><span class="k">struct</span><span class="w"> </span><span class="nc">kobj_attribute</span><span class="w"> </span><span class="o">*</span><span class="n">attr</span><span class="p">,</span>
<span class="w">                                </span><span class="k">const</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">,</span>
<span class="w">                                </span><span class="kt">size_t</span><span class="w"> </span><span class="n">count</span><span class="p">)</span>
<span class="p">{</span>
<span class="w">    </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="n">val</span><span class="p">;</span>
<span class="w">    </span><span class="kt">int</span><span class="w"> </span><span class="n">err</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">kstrtoul</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">val</span><span class="p">);</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">err</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">err</span><span class="p">;</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">val</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="o">-</span><span class="n">EINVAL</span><span class="p">;</span>
<span class="w">    </span><span class="n">mutex_lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">kb_lock</span><span class="p">);</span>
<span class="w">    </span><span class="n">loop_count</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">val</span><span class="p">;</span>
<span class="w">    </span><span class="n">mutex_unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">kb_lock</span><span class="p">);</span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="n">count</span><span class="p">;</span>
<span class="p">}</span>

<span class="cm">/* Show the current value for destination address. */</span>
<span class="k">static</span><span class="w"> </span><span class="kt">ssize_t</span><span class="w"> </span><span class="nf">flow_dst_ipaddr_show</span><span class="p">(</span><span class="k">struct</span><span class="w"> </span><span class="nc">kobject</span><span class="w"> </span><span class="o">*</span><span class="n">kobj</span><span class="p">,</span>
<span class="w">                                    </span><span class="k">struct</span><span class="w"> </span><span class="nc">kobj_attribute</span><span class="w"> </span><span class="o">*</span><span class="n">attr</span><span class="p">,</span>
<span class="w">                                    </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">)</span>
<span class="p">{</span>
<span class="w">    </span><span class="kt">ssize_t</span><span class="w"> </span><span class="n">res</span><span class="p">;</span>
<span class="w">    </span><span class="n">mutex_lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">kb_lock</span><span class="p">);</span>
<span class="w">    </span><span class="n">res</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">scnprintf</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="n">PAGE_SIZE</span><span class="p">,</span><span class="w"> </span><span class="s">"%pI4</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">flow_dst_ipaddr</span><span class="p">);</span>
<span class="w">    </span><span class="n">mutex_unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">kb_lock</span><span class="p">);</span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="n">res</span><span class="p">;</span>
<span class="p">}</span>

<span class="cm">/* Store a new value for destination address. */</span>
<span class="k">static</span><span class="w"> </span><span class="kt">ssize_t</span><span class="w"> </span><span class="nf">flow_dst_ipaddr_store</span><span class="p">(</span><span class="k">struct</span><span class="w"> </span><span class="nc">kobject</span><span class="w"> </span><span class="o">*</span><span class="n">kobj</span><span class="p">,</span>
<span class="w">                                     </span><span class="k">struct</span><span class="w"> </span><span class="nc">kobj_attribute</span><span class="w"> </span><span class="o">*</span><span class="n">attr</span><span class="p">,</span>
<span class="w">                                     </span><span class="k">const</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">,</span>
<span class="w">                                     </span><span class="kt">size_t</span><span class="w"> </span><span class="n">count</span><span class="p">)</span>
<span class="p">{</span>
<span class="w">    </span><span class="n">mutex_lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">kb_lock</span><span class="p">);</span>
<span class="w">    </span><span class="n">flow_dst_ipaddr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">in_aton</span><span class="p">(</span><span class="n">buf</span><span class="p">);</span>
<span class="w">    </span><span class="n">mutex_unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">kb_lock</span><span class="p">);</span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="n">count</span><span class="p">;</span>
<span class="p">}</span>

<span class="cm">/* Define the new set of attributes. They are read/write attributes. */</span>
<span class="k">static</span><span class="w"> </span><span class="k">struct</span><span class="w"> </span><span class="nc">kobj_attribute</span><span class="w"> </span><span class="n">loop_count_attr</span><span class="w">      </span><span class="o">=</span><span class="w"> </span><span class="n">__ATTR_RW</span><span class="p">(</span><span class="n">loop_count</span><span class="p">);</span>
<span class="k">static</span><span class="w"> </span><span class="k">struct</span><span class="w"> </span><span class="nc">kobj_attribute</span><span class="w"> </span><span class="n">flow_dst_ipaddr_attr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">__ATTR_RW</span><span class="p">(</span><span class="n">flow_dst_ipaddr</span><span class="p">);</span>
<span class="k">static</span><span class="w"> </span><span class="k">struct</span><span class="w"> </span><span class="nc">kobj_attribute</span><span class="w"> </span><span class="n">run_attr</span><span class="w">             </span><span class="o">=</span><span class="w"> </span><span class="n">__ATTR_RO</span><span class="p">(</span><span class="n">run</span><span class="p">);</span>
<span class="k">static</span><span class="w"> </span><span class="k">struct</span><span class="w"> </span><span class="nc">attribute</span><span class="w"> </span><span class="o">*</span><span class="n">bench_attributes</span><span class="p">[]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="o">&amp;</span><span class="n">loop_count_attr</span><span class="p">.</span><span class="n">attr</span><span class="p">,</span>
<span class="w">    </span><span class="o">&amp;</span><span class="n">flow_dst_ipaddr_attr</span><span class="p">.</span><span class="n">attr</span><span class="p">,</span>
<span class="w">    </span><span class="o">&amp;</span><span class="n">run_attr</span><span class="p">.</span><span class="n">attr</span><span class="p">,</span>
<span class="w">    </span><span class="nb">NULL</span>
<span class="p">};</span>
</code></pre></div>

<p>The IPv4 address is stored as a 32-bit integer but displayed and
parsed using the dotted quad notation. The kernel provides the
appropriate helpers for this task.</p>
<p>After this change, we have two new files in <code>/sys/kernel/kbench</code>.  We
can read the current values and modify them:</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp"># </span><span class="nb">cd</span><span class="w"> </span>/sys/kernel/kbench
<span class="gp"># </span>ls<span class="w"> </span>-l
<span class="go">-rw-r--r-- 1 root root 4096 déc.  10 19:10 flow_dst_ipaddr</span>
<span class="go">-rw-r--r-- 1 root root 4096 déc.  10 19:10 loop_count</span>
<span class="go">-r--r--r-- 1 root root 4096 déc.  10 19:10 run</span>
<span class="gp"># </span>cat<span class="w"> </span>loop_count
<span class="go">5000</span>
<span class="gp"># </span>cat<span class="w"> </span>flow_dst_ipaddr
<span class="go">8.8.8.8</span>
<span class="gp"># </span><span class="nb">echo</span><span class="w"> </span><span class="m">9</span>.9.9.9<span class="w"> </span>&gt;<span class="w"> </span>flow_dst_ipaddr
<span class="gp"># </span>cat<span class="w"> </span>flow_dst_ipaddr
<span class="go">9.9.9.9</span>
</code></pre></div>

<p>We still need to alter the <code>do_bench()</code> function to make use of these
parameters:</p>
<div class="language-c codehilite"><pre><span/><code><span class="k">static</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="nf">do_bench</span><span class="p">(</span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">)</span>
<span class="p">{</span>
<span class="w">    </span><span class="cm">/* … */</span>
<span class="w">    </span><span class="n">mutex_lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">kb_lock</span><span class="p">);</span>
<span class="w">    </span><span class="n">count</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">loop_count</span><span class="p">;</span>
<span class="w">    </span><span class="n">fl4</span><span class="p">.</span><span class="n">daddr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">flow_dst_ipaddr</span><span class="p">;</span>
<span class="w">    </span><span class="n">mutex_unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">kb_lock</span><span class="p">);</span>

<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">count</span><span class="p">;</span><span class="w"> </span><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="cm">/* … */</span>
</code></pre></div>

<h1 id="meaningful-statistics">Meaningful statistics</h1>
<p>Currently, we only compute the average lookup time. This value is
usually inadequate:</p>
<ul>
<li>
<p>A small number of <strong>outliers</strong> can raise this value quite
   significantly. An outlier can happen because we were preempted out
   of CPU while executing the benchmarked function. This does not
   happen often if the function execution time is short (less than a
   millisecond), but when this happens, the outliers can be off by
   several milliseconds, which is enough to make the average
   inadequate when most values are several orders of magnitude
   smaller. For this reason, the median usually gives a better
   view.<sup id="fnref:preempt"><a class="footnote-ref" href="#fn:preempt">4</a></sup></p>
</li>
<li>
<p>The <strong>distribution</strong> may be asymmetrical or have several local
   maxima. It’s better to keep several percentiles or even a
   distribution graph.</p>
</li>
</ul>
<p>To be able to extract meaningful statistics, we store the results in
an array.</p>
<div class="language-c codehilite"><pre><span/><code><span class="k">static</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="nf">do_bench</span><span class="p">(</span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">)</span>
<span class="p">{</span>
<span class="w">    </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="o">*</span><span class="n">results</span><span class="p">;</span>
<span class="w">    </span><span class="cm">/* … */</span>

<span class="w">    </span><span class="n">results</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">kmalloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="o">*</span><span class="n">results</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">count</span><span class="p">,</span><span class="w"> </span><span class="n">GFP_KERNEL</span><span class="p">);</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="n">results</span><span class="p">)</span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">scnprintf</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="n">PAGE_SIZE</span><span class="p">,</span><span class="w"> </span><span class="s">"msg=</span><span class="se">\"</span><span class="s">no memory</span><span class="se">\"\n</span><span class="s">"</span><span class="p">);</span>

<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">count</span><span class="p">;</span><span class="w"> </span><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="n">t1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">get_cycles</span><span class="p">();</span>
<span class="w">        </span><span class="n">err</span><span class="w"> </span><span class="o">|=</span><span class="w"> </span><span class="n">fib_lookup</span><span class="p">(</span><span class="o">&amp;</span><span class="n">init_net</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">fl4</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">res</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span>
<span class="w">        </span><span class="n">t2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">get_cycles</span><span class="p">();</span>
<span class="hll"><span class="w">        </span><span class="n">results</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">t2</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">t1</span><span class="p">;</span>
</span><span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="n">kfree</span><span class="p">(</span><span class="n">results</span><span class="p">);</span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">scnprintf</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="n">PAGE_SIZE</span><span class="p">,</span><span class="w"> </span><span class="s">"err=%d msg=</span><span class="se">\"</span><span class="s">lookup error</span><span class="se">\"\n</span><span class="s">"</span><span class="p">,</span><span class="w"> </span><span class="n">err</span><span class="p">);</span>
<span class="w">    </span><span class="p">}</span>
<span class="w">    </span><span class="cm">/* Compute and display statistics */</span>
<span class="w">    </span><span class="n">display_statistics</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="n">results</span><span class="p">,</span><span class="w"> </span><span class="n">count</span><span class="p">);</span>

<span class="w">    </span><span class="n">kfree</span><span class="p">(</span><span class="n">results</span><span class="p">);</span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="n">strnlen</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="n">PAGE_SIZE</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>

<p>Then, we need a helper function to be able to compute percentiles:</p>
<div class="language-c codehilite"><pre><span/><code><span class="k">static</span><span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="nf">percentile</span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">p</span><span class="p">,</span>
<span class="w">                                     </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="o">*</span><span class="n">sorted</span><span class="p">,</span>
<span class="w">                                     </span><span class="kt">unsigned</span><span class="w"> </span><span class="n">count</span><span class="p">)</span>
<span class="p">{</span>
<span class="w">    </span><span class="kt">int</span><span class="w"> </span><span class="n">index</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">p</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">count</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mi">100</span><span class="p">;</span>
<span class="w">    </span><span class="kt">int</span><span class="w"> </span><span class="n">index2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">index</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">p</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">count</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="mi">100</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">sorted</span><span class="p">[</span><span class="n">index</span><span class="p">];</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">index2</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="n">count</span><span class="p">)</span>
<span class="w">        </span><span class="n">index2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">index</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">index2</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span>
<span class="w">        </span><span class="n">index2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">index</span><span class="p">;</span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="p">(</span><span class="n">sorted</span><span class="p">[</span><span class="n">index</span><span class="p">]</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">sorted</span><span class="p">[</span><span class="n">index</span><span class="o">+</span><span class="mi">1</span><span class="p">])</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mi">2</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>

<p>This function needs a sorted array as input. The kernel provides a
<a href="https://elixir.bootlin.com/linux/v4.14.5/source/lib/sort.c#L62" title="Kernel sort() function">heapsort function</a>, <code>sort()</code>, for this purpose. Another useful
value to have is the deviation from the median. Here is a function to
compute the <a href="https://en.wikipedia.org/wiki/Median_absolute_deviation" title="Median absolute deviation on Wikipedia">median absolute deviation</a>:<sup id="fnref:mad"><a class="footnote-ref" href="#fn:mad">5</a></sup></p>
<div class="language-c codehilite"><pre><span/><code><span class="k">static</span><span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="nf">mad</span><span class="p">(</span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="o">*</span><span class="n">sorted</span><span class="p">,</span>
<span class="w">                              </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="n">median</span><span class="p">,</span>
<span class="w">                              </span><span class="kt">unsigned</span><span class="w"> </span><span class="n">count</span><span class="p">)</span>
<span class="p">{</span>
<span class="w">    </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="o">*</span><span class="n">dmedian</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">kmalloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="kt">long</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">count</span><span class="p">,</span>
<span class="w">                                          </span><span class="n">GFP_KERNEL</span><span class="p">);</span>
<span class="w">    </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="n">res</span><span class="p">;</span>
<span class="w">    </span><span class="kt">unsigned</span><span class="w"> </span><span class="n">i</span><span class="p">;</span>

<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="n">dmedian</span><span class="p">)</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span>
<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">count</span><span class="p">;</span><span class="w"> </span><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">sorted</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="n">median</span><span class="p">)</span>
<span class="w">            </span><span class="n">dmedian</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">sorted</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">median</span><span class="p">;</span>
<span class="w">        </span><span class="k">else</span>
<span class="w">            </span><span class="n">dmedian</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">median</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">sorted</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="w">    </span><span class="p">}</span>
<span class="w">    </span><span class="n">sort</span><span class="p">(</span><span class="n">dmedian</span><span class="p">,</span><span class="w"> </span><span class="n">count</span><span class="p">,</span><span class="w"> </span><span class="k">sizeof</span><span class="p">(</span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="kt">long</span><span class="p">),</span><span class="w"> </span><span class="n">compare_ull</span><span class="p">,</span><span class="w"> </span><span class="nb">NULL</span><span class="p">);</span>
<span class="w">    </span><span class="n">res</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">percentile</span><span class="p">(</span><span class="mi">50</span><span class="p">,</span><span class="w"> </span><span class="n">dmedian</span><span class="p">,</span><span class="w"> </span><span class="n">count</span><span class="p">);</span>
<span class="w">    </span><span class="n">kfree</span><span class="p">(</span><span class="n">dmedian</span><span class="p">);</span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="n">res</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>

<p>With these two functions, we can provide additional statistics:</p>
<div class="language-c codehilite"><pre><span/><code><span class="k">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">display_statistics</span><span class="p">(</span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">,</span>
<span class="w">                               </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="o">*</span><span class="n">results</span><span class="p">,</span>
<span class="w">                               </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="n">count</span><span class="p">)</span>
<span class="p">{</span>
<span class="w">    </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="kt">long</span><span class="w"> </span><span class="n">p95</span><span class="p">,</span><span class="w"> </span><span class="n">p90</span><span class="p">,</span><span class="w"> </span><span class="n">p50</span><span class="p">;</span>

<span class="w">    </span><span class="n">sort</span><span class="p">(</span><span class="n">results</span><span class="p">,</span><span class="w"> </span><span class="n">count</span><span class="p">,</span><span class="w"> </span><span class="k">sizeof</span><span class="p">(</span><span class="o">*</span><span class="n">results</span><span class="p">),</span><span class="w"> </span><span class="n">compare_ull</span><span class="p">,</span><span class="w"> </span><span class="nb">NULL</span><span class="p">);</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">count</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="n">scnprintf</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="n">PAGE_SIZE</span><span class="p">,</span><span class="w"> </span><span class="s">"msg=</span><span class="se">\"</span><span class="s">no match</span><span class="se">\"\n</span><span class="s">"</span><span class="p">);</span>
<span class="w">        </span><span class="k">return</span><span class="p">;</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="n">p95</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">percentile</span><span class="p">(</span><span class="mi">95</span><span class="p">,</span><span class="w"> </span><span class="n">results</span><span class="p">,</span><span class="w"> </span><span class="n">count</span><span class="p">);</span>
<span class="w">    </span><span class="n">p90</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">percentile</span><span class="p">(</span><span class="mi">90</span><span class="p">,</span><span class="w"> </span><span class="n">results</span><span class="p">,</span><span class="w"> </span><span class="n">count</span><span class="p">);</span>
<span class="w">    </span><span class="n">p50</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">percentile</span><span class="p">(</span><span class="mi">50</span><span class="p">,</span><span class="w"> </span><span class="n">results</span><span class="p">,</span><span class="w"> </span><span class="n">count</span><span class="p">);</span>
<span class="w">    </span><span class="n">scnprintf</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="n">PAGE_SIZE</span><span class="p">,</span>
<span class="w">          </span><span class="s">"min=%llu max=%llu count=%lu 95th=%llu 90th=%llu 50th=%llu mad=%llu</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
<span class="w">          </span><span class="n">results</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span>
<span class="w">          </span><span class="n">results</span><span class="p">[</span><span class="n">count</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">1</span><span class="p">],</span>
<span class="w">          </span><span class="n">count</span><span class="p">,</span>
<span class="w">          </span><span class="n">p95</span><span class="p">,</span>
<span class="w">          </span><span class="n">p90</span><span class="p">,</span>
<span class="w">          </span><span class="n">p50</span><span class="p">,</span>
<span class="w">          </span><span class="n">mad</span><span class="p">(</span><span class="n">results</span><span class="p">,</span><span class="w"> </span><span class="n">p50</span><span class="p">,</span><span class="w"> </span><span class="n">count</span><span class="p">));</span>
<span class="p">}</span>
</code></pre></div>

<div class="admonition">
<p class="admonition-title">Update (2018-01)</p>
<p><a href="https://web.archive.org/web/2017/https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/ia-32-ia-64-benchmark-code-execution-paper.pdf" title="How to Benchmark Code Execution Times on Intel ® IA-32 and IA-64 Instruction Set Architectures">Intel</a> recommends looking at the minimum if
its variance is small.</p>
</div>
<p>We can also append a <a href="https://github.com/vincentbernat/network-lab/blob/7cd27509497f19250c34f38af90178bf781fc163/lab-routes-ipv4/kbench_mod.c#L165-L207">graph of the distribution function</a> (and of
the cumulative distribution function):</p>
<div class="language-text-only codehilite"><pre><span/><code>min=72 max=33364 count=100000 95th=154 90th=142 50th=112 mad=6
    value │                      ┊                         count
       72 │                                                   51
       77 │▒                                                3548
       82 │▒▒░░                                             4773
       87 │▒▒░░░░░                                          5918
       92 │░░░░░░░                                          1207
       97 │░░░░░░░                                           437
      102 │▒▒▒▒▒▒░░░░░░░░                                  12164
      107 │▒▒▒▒▒▒▒░░░░░░░░░░░░░░                           15508
      112 │▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░               23014
      117 │▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░             6297
      122 │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░              905
      127 │▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░           3845
      132 │▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░       6687
      137 │▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░     4884
      142 │▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░   4133
      147 │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  1015
      152 │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  1123
</code></pre></div>

<h1 id="benchmark-validity">Benchmark validity</h1>
<p>While the benchmark produces some figures, we may question their
validity. There are several traps when writing a microbenchmark:</p>
<dl>
<dt>Dead code</dt>
<dd>Compiler may optimize away our benchmark because the result is not
  used. In our example, we ensure to combine the result in a variable
  to avoid this.</dd>
<dt>Warmup phase</dt>
<dd>One-time initializations may affect negatively the benchmark. This
  is less likely to happen with C code since there is no
  <abbr title="Just-in-time (compiler)">JIT</abbr>. Nonetheless, you may want to add a small <a href="https://github.com/vincentbernat/network-lab/blob/7cd27509497f19250c34f38af90178bf781fc163/lab-routes-ipv4/kbench_mod.c#L258-L265">warmup phase</a>.</dd>
<dt>Too small dataset</dt>
<dd>If the benchmark is running using the same input parameters over and
  over, the input data may fit entirely in the L1 cache. This affects
  positively the benchmark. Therefore, it is important to
  <a href="https://github.com/vincentbernat/network-lab/blob/7cd27509497f19250c34f38af90178bf781fc163/lab-routes-ipv6/kbench_mod.c#L278-L309">iterate over a large dataset</a>.</dd>
<dt>Too regular dataset</dt>
<dd>A regular dataset may still affect positively the benchmark despite
  its size. While the whole dataset will not fit into L1/L2 cache, the
  previous run may have loaded most of the data needed for the current
  run. In the route lookup example, as route entries are organized in
  a tree, it’s important to not linearly scan the address
  space. Address space could be explored randomly (a
  <a href="https://github.com/vincentbernat/network-lab/blob/7cd27509497f19250c34f38af90178bf781fc163/lab-routes-ipv6/kbench_mod.c#L113-L116">simple linear congruential generator</a> brings reproducible
  randomness).</dd>
<dt>Large overhead</dt>
<dd>If the benchmarked function runs in a few nanoseconds, the overhead
  of the benchmark infrastructure may be too high. Typically, the
  overhead of the method presented here is around 5
  nanoseconds. <code>get_cycles()</code> is a thin wrapper around the
  <a href="https://en.wikipedia.org/wiki/Time_Stamp_Counter" title="Time Stamp Counter on Wikipedia">RDTSC instruction</a>: it returns the number of cycles for the current
  processor since last reset. It’s also virtualized with low-overhead
  in case you run the benchmark in a virtual machine. If you want to
  measure a function with a greater precision, you need to wrap it in
  a loop. However, the loop itself adds to the overhead, notably if
  you need to compute a large input set (in this case, the input can
  be prepared). Compilers also like to mess with loops. At last, a loop
  hides the result distribution.</dd>
<dt>Preemption</dt>
<dd>While the benchmark is running, the thread executing it can be
  preempted (or when running in a virtual machine, the whole virtual
  machine can be preempted by the host). When the function takes less
  than a millisecond to execute, one can assume preemption is rare
  enough to be filtered out by using a percentile function.</dd>
<dt>Noise</dt>
<dd>When running the benchmark, noise from unrelated processes (or
  sibling hosts when benchmarking in a virtual machine) needs to be
  avoided as it may change from one run to another. Therefore, it is
  not a good idea to benchmark in a public cloud. On the other hand,
  adding controlled noise to the benchmark may lead to less artificial
  results: in our example, route lookup is only a small part of
  routing a packet and measuring it alone in a tight loop affects
  positively the benchmark.</dd>
<dt>Syncing parallel benchmarks</dt>
<dd>While it is possible (and safe) to run several benchmarks in
  parallel, it may be difficult to ensure they really run in parallel:
  some invocations may work in better conditions because other threads
  are not running yet, skewing the result. Ideally, each run should
  execute bogus iterations and start measures only when all runs are
  present. This doesn’t seem a trivial addition.</dd>
</dl>
<p>As a conclusion, the benchmark module presented here is quite
primitive (notably compared to a framework like <a href="https://openjdk.org/projects/code-tools/jmh/" title="OpenJDK jmh">JMH</a> for Java) but,
with care, can deliver some conclusive results like in these posts:
“<a href="/en/blog/2017-ipv4-route-lookup-linux" title="IPv4 route lookup on Linux">IPv4 route lookup on Linux</a>” and “<a href="/en/blog/2017-ipv6-route-lookup-linux" title="IPv6 route lookup on Linux">IPv6 route lookup on Linux</a>.”</p>
<div class="admonition">
<p class="admonition-title">Update (2018-01)</p>
<p><a href="https://web.archive.org/web/2017/https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/ia-32-ia-64-benchmark-code-execution-paper.pdf" title="How to Benchmark Code Execution Times on Intel ® IA-32 and IA-64 Instruction Set Architectures">Intel</a> also recommends disabling all power
optimizations, notably frequency scaling (<code>cpupower frequency-set -g
performance</code>) and turbo mode functionalities (<code>echo 1 &gt;
/sys/devices/system/cpu/intel_pstate/no_turbo</code>) as the <abbr title="Time Stamp Counter">TSC</abbr> is
<a href="https://en.wikipedia.org/wiki/Time_Stamp_Counter#Implementation_in_various_processors" title="Time Stamp Counter: Implementation in various processors">independent of the frequency</a>.</p>
</div>
<h1 id="alternative">Alternative</h1>
<p>Use of a tracing tool is an alternative approach. For example, if we
want to benchmark IPv4 route lookup times, we can use the following
process:</p>
<div class="language-bash codehilite"><pre><span/><code><span class="k">while</span><span class="w"> </span>true<span class="p">;</span><span class="w"> </span><span class="k">do</span>
<span class="w">  </span>ip<span class="w"> </span>route<span class="w"> </span>get<span class="w"> </span><span class="k">$((</span><span class="nv">RANDOM</span><span class="o">%</span><span class="m">100</span><span class="k">))</span>.<span class="k">$((</span><span class="nv">RANDOM</span><span class="o">%</span><span class="m">100</span><span class="k">))</span>.<span class="k">$((</span><span class="nv">RANDOM</span><span class="o">%</span><span class="m">100</span><span class="k">))</span>.5
<span class="w">  </span>sleep<span class="w"> </span><span class="m">0</span>.1
<span class="k">done</span>
</code></pre></div>

<p>Then, we instrument the <code>__fib_lookup()</code> function with <abbr title="Extended Berkeley Packet Filter">eBPF</abbr> (through
<a href="https://github.com/iovisor/bcc" title="BCC - Tools for BPF-based Linux IO analysis, networking, monitoring, and more">BCC</a>):</p>
<div class="language-bash-session codehilite"><pre><span/><code><span class="gp">$ </span>sudo<span class="w"> </span>funclatency-bpfcc<span class="w"> </span>__fib_lookup
<span class="go">Tracing 1 functions for "__fib_lookup"... Hit Ctrl-C to end.</span>
<span class="go">^C</span>
<span class="go">     nsecs               : count     distribution</span>
<span class="go">         0 -&gt; 1          : 0        |                    |</span>
<span class="go">         2 -&gt; 3          : 0        |                    |</span>
<span class="go">         4 -&gt; 7          : 0        |                    |</span>
<span class="go">         8 -&gt; 15         : 0        |                    |</span>
<span class="go">        16 -&gt; 31         : 0        |                    |</span>
<span class="go">        32 -&gt; 63         : 0        |                    |</span>
<span class="go">        64 -&gt; 127        : 0        |                    |</span>
<span class="go">       128 -&gt; 255        : 0        |                    |</span>
<span class="go">       256 -&gt; 511        : 3        |*                   |</span>
<span class="go">       512 -&gt; 1023       : 1        |                    |</span>
<span class="go">      1024 -&gt; 2047       : 2        |*                   |</span>
<span class="go">      2048 -&gt; 4095       : 13       |******              |</span>
<span class="go">      4096 -&gt; 8191       : 42       |********************|</span>
</code></pre></div>

<p>Currently, the overhead is quite high, as a route lookup on an empty
routing table is less than 100 ns. <del>Once Linux supports <a href="https://lwn.net/Articles/740831/" title="tracing: Inter-event (e.g. latency) support">inter-event
tracing</a>, the overhead of this solution may be reduced to be usable
for such microbenchmarks.</del></p>
<div class="footnote">
<hr/>
<ol>
<li id="fn:loop">
<p>In this simple case, it may be more accurate to use:</p>
<div class="language-c codehilite"><pre><span/><code><span class="n">t1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">get_cycles</span><span class="p">();</span>
<span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">count</span><span class="p">;</span><span class="w"> </span><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="n">err</span><span class="w"> </span><span class="o">|=</span><span class="w"> </span><span class="n">fib_lookup</span><span class="p">(</span><span class="err">…</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">t2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">get_cycles</span><span class="p">();</span>
<span class="n">total</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">t2</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">t1</span><span class="p">;</span>
</code></pre></div>

<p>However, this prevents us to compute more statistics. Moreover,
when you need to provide a non-constant input to the <code>fib_lookup()</code>
function, the first way is likely to be more accurate. <a class="footnote-backref" href="#fnref:loop" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:backward">
<p>In-kernel API backward compatibility is a <a href="https://elixir.bootlin.com/linux/v4.14.5/source/Documentation/process/stable-api-nonsense.rst" title="The Linux Kernel Driver Interface">non-goal</a> of
the Linux kernel. <a class="footnote-backref" href="#fnref:backward" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
<li id="fn:frequency">
<p>You can get the current frequency with <code>cpupower
frequency-info</code>. As the frequency may vary (even when using the
<code>performance</code> governor), this may not be accurate but this still
provides an easier representation (comparable results should use the
same frequency). <a class="footnote-backref" href="#fnref:frequency" title="Jump back to footnote 3 in the text">↩</a></p>
</li>
<li id="fn:preempt">
<p>It is possible to disable kernel preemption before running
the benchmark:</p>
<div class="language-c codehilite"><pre><span/><code><span class="n">preempt_disable</span><span class="p">();</span>
<span class="n">raw_local_irq_save</span><span class="p">(</span><span class="n">flags</span><span class="p">);</span>
<span class="cm">/* […] */</span>
<span class="n">raw_local_irq_restore</span><span class="p">(</span><span class="n">flags</span><span class="p">);</span>
<span class="n">preempt_enable</span><span class="p">();</span>
</code></pre></div>

<p>However, on physical machines, management code may still steal the CPU
and on virtual machines, there is no way to ensure the guest is not
preempted. <a class="footnote-backref" href="#fnref:preempt" title="Jump back to footnote 4 in the text">↩</a></p>
</li>
<li id="fn:mad">
<p>Only integer arithmetic is available in the kernel. While it
is possible to approximate a standard deviation using only
integers, the median absolute deviation just reuses the
<code>percentile()</code> function defined above. <a class="footnote-backref" href="#fnref:mad" title="Jump back to footnote 5 in the text">↩</a></p>
</li>
</ol>
</div>
      </div>
    </content>
  </entry>
</feed>