<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Dhcp on inetshell</title><link>https://inet.sh/en/tags/dhcp/</link><description>Recent content in Dhcp on inetshell</description><generator>Hugo</generator><language>en</language><lastBuildDate>Sat, 26 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://inet.sh/en/tags/dhcp/index.xml" rel="self" type="application/rss+xml"/><item><title>DHCP option 121: classless static routes (RFC 3442), how to encode them in hex and set them on ISC dhcpd, Kea, pfSense and MikroTik</title><link>https://inet.sh/en/posts/network/dhcp-option-121-classless-static-routes/</link><pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/en/posts/network/dhcp-option-121-classless-static-routes/</guid><description>To let DHCP clients reach another network through a router other than the gateway (a site-to-site VPN, a second link) without touching every machine, the DHCP server hands out routes with option 121. The format is binary: one octet with the prefix length, only the significant octets of the destination and the four of the router, all concatenated. A Python script that produces it in the four formats ISC dhcpd, Kea, pfSense and MikroTik expect, and the rule that breaks the network if you forget it: when you send 121, the client ignores option 3 and you must include the default route.</description><content:encoded><![CDATA[<h2 id="tldr">TL;DR</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;Encode classless routes (RFC 3442) for DHCP option 121.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">ipaddress</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">encode</span><span class="p">(</span><span class="n">routes</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">out</span> <span class="o">=</span> <span class="nb">bytearray</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">dest</span><span class="p">,</span> <span class="n">via</span> <span class="ow">in</span> <span class="n">routes</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">net</span> <span class="o">=</span> <span class="n">ipaddress</span><span class="o">.</span><span class="n">ip_network</span><span class="p">(</span><span class="n">dest</span><span class="p">,</span> <span class="n">strict</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">n</span> <span class="o">=</span> <span class="p">(</span><span class="n">net</span><span class="o">.</span><span class="n">prefixlen</span> <span class="o">+</span> <span class="mi">7</span><span class="p">)</span> <span class="o">//</span> <span class="mi">8</span>                 <span class="c1"># significant destination octets</span>
</span></span><span class="line"><span class="cl">        <span class="n">out</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">net</span><span class="o">.</span><span class="n">prefixlen</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">out</span> <span class="o">+=</span> <span class="n">net</span><span class="o">.</span><span class="n">network_address</span><span class="o">.</span><span class="n">packed</span><span class="p">[:</span><span class="n">n</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="n">out</span> <span class="o">+=</span> <span class="n">ipaddress</span><span class="o">.</span><span class="n">ip_address</span><span class="p">(</span><span class="n">via</span><span class="p">)</span><span class="o">.</span><span class="n">packed</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">bytes</span><span class="p">(</span><span class="n">out</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">routes</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="s2">&#34;192.0.2.0/24&#34;</span><span class="p">,</span> <span class="s2">&#34;192.168.1.254&#34;</span><span class="p">),</span>   <span class="c1"># the other site, through the VPN router</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="s2">&#34;0.0.0.0/0&#34;</span><span class="p">,</span>    <span class="s2">&#34;192.168.1.1&#34;</span><span class="p">),</span>     <span class="c1"># the default route: mandatory when you send 121</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">raw</span> <span class="o">=</span> <span class="n">encode</span><span class="p">(</span><span class="n">routes</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;hex      &#34;</span><span class="p">,</span> <span class="n">raw</span><span class="o">.</span><span class="n">hex</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;mikrotik &#34;</span><span class="p">,</span> <span class="s2">&#34;0x&#34;</span> <span class="o">+</span> <span class="n">raw</span><span class="o">.</span><span class="n">hex</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;isc      &#34;</span><span class="p">,</span> <span class="s2">&#34;, &#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">b</span><span class="p">)</span> <span class="k">for</span> <span class="n">b</span> <span class="ow">in</span> <span class="n">raw</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;pfsense  &#34;</span><span class="p">,</span> <span class="s2">&#34;:&#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">b</span><span class="si">:</span><span class="s2">02x</span><span class="si">}</span><span class="s2">&#34;</span> <span class="k">for</span> <span class="n">b</span> <span class="ow">in</span> <span class="n">raw</span><span class="p">))</span>
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">hex       18c00002c0a801fe00c0a80101
</span></span><span class="line"><span class="cl">mikrotik  0x18c00002c0a801fe00c0a80101
</span></span><span class="line"><span class="cl">isc       24, 192, 0, 2, 192, 168, 1, 254, 0, 192, 168, 1, 1
</span></span><span class="line"><span class="cl">pfsense   18:c0:00:02:c0:a8:01:fe:00:c0:a8:01:01
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="the-format">The format</h2>
<p>Every route in option 121 is three fields glued together, no separators:</p>
<table>
  <thead>
      <tr>
          <th>Field</th>
          <th>Size</th>
          <th>Example <code>192.0.2.0/24 via 192.168.1.254</code></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Prefix length</td>
          <td>1 octet</td>
          <td><code>18</code> (24)</td>
      </tr>
      <tr>
          <td>Destination, <strong>significant octets only</strong>: ⌈prefix / 8⌉</td>
          <td>0 to 4 octets</td>
          <td><code>c0 00 02</code> (192.0.2)</td>
      </tr>
      <tr>
          <td>Router</td>
          <td>4 octets</td>
          <td><code>c0 a8 01 fe</code></td>
      </tr>
  </tbody>
</table>
<p>A <code>/24</code> carries three destination octets, a <code>/16</code> two, a <code>/32</code> four, and the default route <code>/0</code> <strong>none</strong>: <code>00</code> followed directly by the router. Several routes are concatenated; the whole option fits in 255 octets (about 28 <code>/24</code> routes).</p>
<h2 id="the-rule-that-breaks-the-network">The rule that breaks the network</h2>
<p>RFC 3442, section 3: <em>&ldquo;If the DHCP server returns both a Classless Static Routes option and a Router option, the DHCP client MUST ignore the Router option.&rdquo;</em> If you start sending 121 with a single route to the other site, clients that understand it <strong>lose the gateway</strong> from option 3 and are left with no way out to the Internet. The default route goes inside 121, always, as in the example. Clients that do not understand 121 keep using option 3, so leave that configured too.</p>
<h2 id="where-it-goes">Where it goes</h2>
<p><strong>ISC dhcpd</strong> (<code>dhcpd.conf</code>): the option has to be declared, and it is worth sending it as 249 as well, which is what older Windows ask for:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
</span></span><span class="line"><span class="cl">option ms-classless-static-routes code 249 = array of unsigned integer 8;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">subnet 192.168.1.0 netmask 255.255.255.0 {
</span></span><span class="line"><span class="cl">  range 192.168.1.100 192.168.1.199;
</span></span><span class="line"><span class="cl">  option routers 192.168.1.1;
</span></span><span class="line"><span class="cl">  option rfc3442-classless-static-routes 24, 192,0,2, 192,168,1,254, 0, 192,168,1,1;
</span></span><span class="line"><span class="cl">  option ms-classless-static-routes     24, 192,0,2, 192,168,1,254, 0, 192,168,1,1;
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></td></tr></table>
</div>
</div><p><strong>Kea</strong> (2.4 or later) understands the option by name with a readable notation, no hex:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="s2">&#34;option-data&#34;</span><span class="err">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span> <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;classless-static-route&#34;</span><span class="p">,</span> <span class="nt">&#34;code&#34;</span><span class="p">:</span> <span class="mi">121</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;data&#34;</span><span class="p">:</span> <span class="s2">&#34;192.0.2.0/24 - 192.168.1.254, 0.0.0.0/0 - 192.168.1.1&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><strong>pfSense / OPNsense</strong>: with the ISC backend, <em>Services → DHCP Server → Additional BOOTP/DHCP Options</em>, number <code>121</code>, type <em>String</em>, value in colon-separated hex (<code>18:c0:00:02:…</code>); add another with <code>249</code> for Windows. With the <strong>Kea</strong> backend, option 121 is not in the UI yet (as of this post there are open requests in both projects); if your version allows custom Kea options, use the JSON above; if not, that is a reason to stay on ISC.</p>
<p><strong>MikroTik RouterOS</strong>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">/ip dhcp-server option add name=classless code=121 value=0x18c00002c0a801fe00c0a80101
</span></span><span class="line"><span class="cl">/ip dhcp-server option add name=classless-ms code=249 value=0x18c00002c0a801fe00c0a80101
</span></span><span class="line"><span class="cl">/ip dhcp-server option sets add name=routes options=classless,classless-ms
</span></span><span class="line"><span class="cl">/ip dhcp-server network set [find address=192.168.1.0/24] dhcp-option-set=routes
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="which-clients-understand-it">Which clients understand it</h2>
<table>
  <thead>
      <tr>
          <th>Client</th>
          <th>Option 121</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Windows (Vista and later)</td>
          <td>Yes; also accepts 249</td>
      </tr>
      <tr>
          <td>Linux with NetworkManager or systemd-networkd</td>
          <td>Yes, nothing to configure</td>
      </tr>
      <tr>
          <td>Linux with <code>dhclient</code></td>
          <td>Only if <code>rfc3442-classless-static-routes</code> is in the <code>request</code> list of <code>dhclient.conf</code> and a hook installs the routes (Debian and Ubuntu ship one in <code>/etc/dhcp/dhclient-exit-hooks.d/</code>)</td>
      </tr>
      <tr>
          <td>macOS and iOS</td>
          <td>Yes</td>
      </tr>
      <tr>
          <td>Android</td>
          <td>Historically ignores it; do not count on it</td>
      </tr>
      <tr>
          <td>FreeBSD <code>dhclient</code></td>
          <td>Not without a custom <code>dhclient-script</code>; <code>dhcpcd</code> does</td>
      </tr>
  </tbody>
</table>
<p>Check on the client with <code>ip route</code> (Linux), <code>route print</code> (Windows) or <code>netstat -rn</code> (macOS) after renewing the lease.</p>
<h2 id="traps">Traps</h2>
<ul>
<li><strong>A <code>/32</code> with a router in another subnet</strong>: valid in the encoding, but the client needs a prior route to that router; order routes from most to least specific and put the local segment&rsquo;s first if needed.</li>
<li><strong>Routes to networks the DHCP server does not control</strong> (a DHCP server on one VLAN advertising routes through another) are accepted anyway: 121 is a list of bytes, nobody validates it. A one-octet mistake becomes a route to a made-up network.</li>
<li><strong>Clients do not revisit routes until they renew</strong> the lease; after changing the option, <code>T1</code> (half the lease) is how long they take to see it, or force a renewal.</li>
<li><strong>Windows and 249</strong>: if you only send 121, modern Windows uses it; 249 is for compatibility and carries exactly the same bytes.</li>
<li><strong><code>/0</code> advertisements from a rogue DHCP</strong>: any DHCP server on the LAN can rewrite every client&rsquo;s default route with 121; DHCP snooping on the switch is the only real defense.</li>
</ul>
<p>Reference: <a href="https://datatracker.ietf.org/doc/rfc3442/">RFC 3442</a>.</p>
<h2 id="see-also">See also</h2>
<ul>
<li><a href="/en/posts/gcp/cloud-vpn-not-up-ike-logs/">GCP Cloud VPN will not come up</a> — the VPN these routes usually point at.</li>
<li><a href="/en/posts/pfsense/nat-reflection-port-forward-from-lan/">pfSense: NAT reflection and split DNS</a> — the other LAN setting clients receive without noticing.</li>
</ul>
]]></content:encoded></item></channel></rss>