<?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>Pfsense on inetshell</title><link>https://inet.sh/es/tags/pfsense/</link><description>Recent content in Pfsense on inetshell</description><generator>Hugo</generator><language>es</language><lastBuildDate>Sat, 26 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://inet.sh/es/tags/pfsense/index.xml" rel="self" type="application/rss+xml"/><item><title>Cloud VPN de GCP no levanta: leer los logs IKE (peer not responding, traffic selectors narrowed) y el BGP con pfSense</title><link>https://inet.sh/es/posts/gcp/cloud-vpn-no-levanta-logs-ike/</link><pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/es/posts/gcp/cloud-vpn-no-levanta-logs-ike/</guid><description>Cuando un túnel de Cloud VPN se queda en &amp;#39;No incoming packets&amp;#39; o sube y baja, la respuesta está en Cloud Logging: las consultas por fase (IKE_SA, CHILD_SA, rekey, DELETE) y qué significa cada mensaje: peer not responding, traffic selectors narrowed, received DELETE. Más el caso de HA VPN con BGP contra pfSense/FRR, donde las rutas no entran hasta que añades route maps.</description><content:encoded><![CDATA[<h2 id="en-corto">En corto</h2>
<p>En <strong>Logs Explorer</strong>, cada fase del túnel tiene su consulta:</p>
<table>
  <thead>
      <tr>
          <th>Qué buscas</th>
          <th>Consulta</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Cloud VPN no llega al peer</td>
          <td><code>resource.type=&quot;vpn_gateway&quot; &quot;establishing IKE_SA failed, peer not responding&quot;</code></td>
      </tr>
      <tr>
          <td>Fase 1 arranca</td>
          <td><code>resource.type=&quot;vpn_gateway&quot; (&quot;initiating IKE_SA&quot; OR &quot;generating IKE_SA_INIT request&quot;)</code></td>
      </tr>
      <tr>
          <td>Autenticación IKE</td>
          <td><code>resource.type=&quot;vpn_gateway&quot; (&quot;generating IKE_AUTH request&quot; OR &quot;parsed IKE_AUTH response&quot;)</code></td>
      </tr>
      <tr>
          <td>PSK aceptada</td>
          <td><code>resource.type=&quot;vpn_gateway&quot; (&quot;authentication of&quot; AND &quot;with pre-shared key successful&quot;)</code></td>
      </tr>
      <tr>
          <td>Fase 1 establecida</td>
          <td><code>resource.type=&quot;vpn_gateway&quot; (&quot;IKE_SA&quot; AND &quot;established between&quot;)</code></td>
      </tr>
      <tr>
          <td>Todo lo de fase 2 (incluye rekeys)</td>
          <td><code>resource.type=&quot;vpn_gateway&quot; &quot;CHILD_SA&quot;</code></td>
      </tr>
      <tr>
          <td>Las subredes no coinciden</td>
          <td><code>resource.type=&quot;vpn_gateway&quot; (&quot;Remote traffic selectors narrowed&quot; OR &quot;Local traffic selectors narrowed&quot;)</code></td>
      </tr>
      <tr>
          <td>Alguien cierra la fase 2</td>
          <td><code>resource.type=&quot;vpn_gateway&quot; (&quot;received DELETE for ESP CHILD_SA&quot; OR &quot;sending DELETE for ESP CHILD_SA&quot;)</code></td>
      </tr>
  </tbody>
</table>
<p>Cloud VPN corre strongSwan por debajo, así que los mensajes son los de strongSwan: si conoces <code>charon</code>, ya sabes leerlos.</p>
<h2 id="qué-significa-cada-mensaje">Qué significa cada mensaje</h2>
<p><strong><code>establishing IKE_SA failed, peer not responding</code>.</strong> Cloud VPN manda IKE_SA_INIT y nadie contesta. En orden de probabilidad: el peer está en IKEv1 y el túnel en IKEv2 (o al revés), la IP del peer configurada no es la pública real (NAT delante del firewall), UDP 500 y 4500 no llegan al peer, o el peer ni siquiera tiene el túnel activo. La PSK <strong>no</strong> produce este mensaje: con PSK incorrecta la fase 1 arranca y falla en <code>IKE_AUTH</code>.</p>
<p><strong><code>IKE_SA … established between</code> y después nada.</strong> Fase 1 bien, fase 2 no. Busca <code>CHILD_SA</code> y <code>traffic selectors</code>.</p>
<p><strong><code>Remote traffic selectors narrowed</code> / <code>Local traffic selectors narrowed</code>.</strong> Las redes de fase 2 no coinciden en los dos lados. En HA VPN (y en Classic VPN basada en rutas) Cloud VPN propone <strong><code>0.0.0.0/0</code> a ambos lados</strong> y espera lo mismo; un peer <em>policy-based</em> con subredes concretas hace que strongSwan estreche los selectores y solo pase tráfico entre lo que casó, o nada. En el peer: fase 2 con <code>0.0.0.0/0</code> local y remoto (en pfSense, modo <em>Routed</em>, que crea una interfaz de túnel <code>ipsecN</code>), y las rutas se resuelven por BGP o estáticas.</p>
<p><strong><code>received DELETE for ESP CHILD_SA</code> en bucle.</strong> El peer tumba la fase 2 cada pocos minutos: lifetimes de fase 2 distintos con rekey mal negociado, o DPD agresivo. Iguala lifetimes (Cloud VPN usa 3 horas en fase 2 y 10 en fase 1) y deja que el rekey lo inicie un solo lado.</p>
<p><strong>Sube, pasa tráfico un rato y muere con NAT-T.</strong> Un peer detrás de NAT con UDP 4500 y un middlebox que &ldquo;ayuda&rdquo; (ALG de IPsec en el router) descarta paquetes ESP encapsulados. Desactiva el ALG o pasa el firewall a IP pública directa.</p>
<h2 id="ha-vpn-con-bgp-contra-pfsense-frr">HA VPN con BGP contra pfSense (FRR)</h2>
<p>El túnel sube, <code>bgp</code> se establece, y <strong>ninguna ruta entra ni sale</strong>. FRR en pfSense arranca con una política que rechaza todo lo que no esté permitido explícitamente:</p>
<ol>
<li><strong>Services → FRR → BGP → Route Maps</strong>: crea <code>ALLOW-ALL</code>, <em>Action</em> Permit, <em>Sequence</em> 100, sin criterios.</li>
<li><strong>BGP → Neighbors</strong> → el peer de Cloud Router: <em>Remote AS</em> el ASN del Cloud Router, y en <em>Peer Filtering</em> asigna <code>ALLOW-ALL</code> como route map <strong>in</strong> y <strong>out</strong>.</li>
<li><strong>BGP → Network Distribution</strong>: lista las redes locales que quieres anunciar (o <em>Default Originate</em> si todo va por el túnel).</li>
<li>En GCP, el Cloud Router con <code>--advertisement-mode custom</code> si quieres controlar qué prefijos salen; por defecto anuncia las subredes de la VPC.</li>
</ol>
<p>Sin el paso 2, <code>vtysh -c &quot;show ip bgp summary&quot;</code> muestra la sesión <em>Established</em> con 0 prefijos en ambas direcciones, que es exactamente el síntoma.</p>
<h2 id="trampas">Trampas</h2>
<ul>
<li><strong>IKEv2</strong> salvo que el peer no pueda: IKEv1 sigue soportado, pero sin IPv6 y con menos cifrados. Classic VPN sigue existiendo (99,9 %), pero parte de su funcionalidad está retirada; lo nuevo va en HA VPN (99,99 %).</li>
<li><strong>Dos túneles por gateway</strong> en HA VPN para el 99.99 %: si solo levantas uno, el estado del gateway queda en aviso aunque funcione.</li>
<li><strong>MTU</strong>: 1460 por defecto en la VPC de GCP; con IPsec encima, deja que TCP haga MSS clamping en el peer (pfSense lo hace en la interfaz <code>ipsecN</code> del túnel) o verás transferencias que se cuelgan tras el handshake.</li>
<li><strong>Los logs tardan</strong>: un minuto o dos entre el evento y Logs Explorer. Refresca antes de cambiar la configuración otra vez.</li>
<li><strong>PSK con caracteres especiales</strong> copiada de un gestor de contraseñas: comprueba en el peer que no se comió nada; el síntoma es <code>IKE_AUTH</code> que falla, no <em>peer not responding</em>.</li>
</ul>
<h2 id="ver-también">Ver también</h2>
<ul>
<li><a href="/es/posts/pfsense/nat-reflection-port-forward-desde-lan/">pfSense: NAT reflection y split DNS</a> — el otro clásico de &ldquo;el túnel está bien y aun así no llego&rdquo;.</li>
<li><a href="https://docs.cloud.google.com/network-connectivity/docs/vpn/support/troubleshooting">Cloud VPN troubleshooting</a> en la documentación de Google Cloud.</li>
</ul>
]]></content:encoded></item><item><title>DHCP opción 121: rutas estáticas classless (RFC 3442), cómo codificarlas en hex y ponerlas en ISC dhcpd, Kea, pfSense y MikroTik</title><link>https://inet.sh/es/posts/network/dhcp-opcion-121-rutas-estaticas-classless/</link><pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/es/posts/network/dhcp-opcion-121-rutas-estaticas-classless/</guid><description>Para que los clientes DHCP lleguen a otra red por un router distinto del gateway (una VPN site-to-site, un segundo enlace) sin tocar cada equipo, el servidor DHCP entrega rutas con la opción 121. El formato es binario: un octeto con la longitud del prefijo, solo los octetos significativos del destino y los cuatro del router, todo concatenado. Un script Python que lo genera en los cuatro formatos que piden ISC dhcpd, Kea, pfSense y MikroTik, y la regla que rompe la red si la olvidas: si mandas la 121, el cliente ignora la opción 3 y tienes que incluir la ruta por defecto.</description><content:encoded><![CDATA[<h2 id="en-corto">En corto</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;Codifica rutas classless (RFC 3442) para la opción DHCP 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"># octetos significativos del destino</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"># la otra sede, por el router de la VPN</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"># la ruta por defecto: obligatoria si mandas la 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="el-formato">El formato</h2>
<p>Cada ruta de la opción 121 son tres campos pegados, sin separadores:</p>
<table>
  <thead>
      <tr>
          <th>Campo</th>
          <th>Tamaño</th>
          <th>Ejemplo <code>192.0.2.0/24 via 192.168.1.254</code></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Longitud del prefijo</td>
          <td>1 octeto</td>
          <td><code>18</code> (24)</td>
      </tr>
      <tr>
          <td>Destino, <strong>solo los octetos significativos</strong>: ⌈prefijo / 8⌉</td>
          <td>0 a 4 octetos</td>
          <td><code>c0 00 02</code> (192.0.2)</td>
      </tr>
      <tr>
          <td>Router</td>
          <td>4 octetos</td>
          <td><code>c0 a8 01 fe</code></td>
      </tr>
  </tbody>
</table>
<p>Un <code>/24</code> lleva tres octetos de destino, un <code>/16</code> dos, un <code>/32</code> cuatro, y la ruta por defecto <code>/0</code> <strong>ninguno</strong>: <code>00</code> seguido directamente del router. Varias rutas se concatenan; la opción entera cabe en 255 octetos (unas 28 rutas <code>/24</code>).</p>
<h2 id="la-regla-que-rompe-la-red">La regla que rompe la red</h2>
<p>RFC 3442, sección 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> Si empiezas a mandar la 121 con una sola ruta a la otra sede, los clientes que la entienden <strong>pierden la puerta de enlace</strong> de la opción 3 y se quedan sin salida a Internet. La ruta por defecto va dentro de la 121, siempre, como en el ejemplo. Los clientes que no entienden la 121 siguen usando la opción 3, así que déjala configurada también.</p>
<h2 id="dónde-se-pone">Dónde se pone</h2>
<p><strong>ISC dhcpd</strong> (<code>dhcpd.conf</code>): hay que declarar la opción, y conviene mandarla también como 249, que es la que piden los Windows antiguos:</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 o posterior) entiende la opción por nombre y con notación legible, sin 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>: con el backend ISC, <em>Services → DHCP Server → Additional BOOTP/DHCP Options</em>, número <code>121</code>, tipo <em>String</em>, valor en hex con dos puntos (<code>18:c0:00:02:…</code>); añade otra con <code>249</code> para Windows. Con el backend <strong>Kea</strong>, la opción 121 todavía no está en la interfaz (a fecha de este post hay peticiones abiertas en los dos proyectos); si tu versión permite opciones personalizadas de Kea, usa el JSON de arriba; si no, es el motivo para seguir en 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="qué-clientes-la-entienden">Qué clientes la entienden</h2>
<table>
  <thead>
      <tr>
          <th>Cliente</th>
          <th>Opción 121</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Windows (Vista y posteriores)</td>
          <td>Sí; también acepta la 249</td>
      </tr>
      <tr>
          <td>Linux con NetworkManager o systemd-networkd</td>
          <td>Sí, sin configurar nada</td>
      </tr>
      <tr>
          <td>Linux con <code>dhclient</code></td>
          <td>Solo si <code>rfc3442-classless-static-routes</code> está en la lista <code>request</code> de <code>dhclient.conf</code> y hay un hook que instale las rutas (Debian y Ubuntu lo traen en <code>/etc/dhcp/dhclient-exit-hooks.d/</code>)</td>
      </tr>
      <tr>
          <td>macOS e iOS</td>
          <td>Sí</td>
      </tr>
      <tr>
          <td>Android</td>
          <td>Históricamente la ignora; no cuentes con ella</td>
      </tr>
      <tr>
          <td>FreeBSD <code>dhclient</code></td>
          <td>No sin <code>dhclient-script</code> a medida; <code>dhcpcd</code> sí</td>
      </tr>
  </tbody>
</table>
<p>Compruébalo en el cliente con <code>ip route</code> (Linux), <code>route print</code> (Windows) o <code>netstat -rn</code> (macOS) tras renovar el lease.</p>
<h2 id="trampas">Trampas</h2>
<ul>
<li><strong><code>/32</code> con router en otra subred</strong>: válido en la codificación, pero el cliente necesita una ruta previa al router; ordena las rutas de más específica a menos y pon primero la del propio segmento si hace falta.</li>
<li><strong>Rutas hacia redes que el DHCP no controla</strong> (un servidor DHCP en una VLAN que anuncia rutas por otra) se aceptan igual: la 121 es una lista de texto, nadie la valida. Un error de un octeto se convierte en una ruta hacia una red inventada.</li>
<li><strong>Los clientes no revisan las rutas hasta renovar</strong> el lease; tras cambiar la opción, <code>T1</code> (la mitad del lease) es lo que tardarán en verla, o fuerza la renovación.</li>
<li><strong>Windows y la 249</strong>: si solo mandas la 121, los Windows modernos la usan; la 249 es por compatibilidad y contiene exactamente los mismos bytes.</li>
<li><strong>Anuncios <code>/0</code> desde un DHCP ajeno</strong>: cualquier servidor DHCP de la LAN puede reescribir la ruta por defecto de todos los clientes con la 121; DHCP snooping en el switch es la única defensa real.</li>
</ul>
<p>Referencia: <a href="https://datatracker.ietf.org/doc/rfc3442/">RFC 3442</a>.</p>
<h2 id="ver-también">Ver también</h2>
<ul>
<li><a href="/es/posts/gcp/cloud-vpn-no-levanta-logs-ike/">Cloud VPN de GCP no levanta</a> — la VPN por la que suelen ir estas rutas.</li>
<li><a href="/es/posts/pfsense/nat-reflection-port-forward-desde-lan/">pfSense: NAT reflection y split DNS</a> — el otro ajuste de LAN que los clientes reciben sin enterarse.</li>
</ul>
]]></content:encoded></item><item><title>pfctl: cheat sheet de tablas en pfSense, OPNsense y FreeBSD</title><link>https://inet.sh/es/posts/pfsense/pfctl-tablas-cheat-sheet/</link><pubDate>Fri, 25 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/es/posts/pfsense/pfctl-tablas-cheat-sheet/</guid><description>Comandos pfctl -t/-T para tablas de PF: listar, ver, añadir, borrar, probar, vaciar, recargar desde archivo y expirar entradas. Con las tablas internas de pfSense (sshguard, virusprot, snort2c, bogons, aliases y pfBlockerNG) y lo que se pierde al recargar el filtro.</description><content:encoded><![CDATA[<h2 id="en-corto">En corto</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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pfctl -sT                                    <span class="c1"># tablas cargadas</span>
</span></span><span class="line"><span class="cl">pfctl -t &lt;tabla&gt; -T show                     <span class="c1"># contenido</span>
</span></span><span class="line"><span class="cl">pfctl -t &lt;tabla&gt; -T add 192.168.1.10         <span class="c1"># añadir IP o red (también 10.20.0.0/16)</span>
</span></span><span class="line"><span class="cl">pfctl -t &lt;tabla&gt; -T delete 192.168.1.10
</span></span><span class="line"><span class="cl">pfctl -t &lt;tabla&gt; -T <span class="nb">test</span> 192.168.1.10        <span class="c1"># ¿está dentro? (match / no match)</span>
</span></span><span class="line"><span class="cl">pfctl -t &lt;tabla&gt; -T flush                    <span class="c1"># vaciar la tabla</span>
</span></span><span class="line"><span class="cl">pfctl -t &lt;tabla&gt; -T replace -f /ruta/lista   <span class="c1"># reemplazar todo el contenido desde un archivo</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">pfctl -t sshguard -T flush                   <span class="c1"># pfSense: quitar el bloqueo por intentos de login fallidos</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="qué-es-una-tabla">Qué es una tabla</h2>
<p>Una <strong>tabla</strong> de PF es un conjunto de direcciones y redes al que las reglas hacen referencia como <code>&lt;nombre&gt;</code>. Se cambia en caliente, sin recargar el ruleset, y aguanta miles de entradas con búsqueda rápida; por eso PF la usa para listas de bloqueo, bogons y cualquier cosa que cambie más a menudo que las reglas.</p>
<p>En <code>pf.conf</code>:</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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">table &lt;blocklist&gt; persist file &#34;/etc/pf/blocklist.txt&#34;
</span></span><span class="line"><span class="cl">block in quick from &lt;blocklist&gt;
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>persist</code> mantiene la tabla aunque ninguna regla la use en ese momento; <code>file</code> la carga desde un archivo (una IP o red por línea).</p>
<h2 id="comandos">Comandos</h2>
<p>Todos con la forma <code>pfctl -t &lt;tabla&gt; -T &lt;comando&gt;</code>:</p>
<table>
  <thead>
      <tr>
          <th>Comando</th>
          <th>Qué hace</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>show</code></td>
          <td>Lista las entradas. Con <code>-v</code>, contadores por entrada (paquetes/bytes in/out)</td>
      </tr>
      <tr>
          <td><code>add &lt;ip|red&gt; …</code></td>
          <td>Añade una o varias entradas</td>
      </tr>
      <tr>
          <td><code>delete &lt;ip|red&gt; …</code></td>
          <td>Quita entradas</td>
      </tr>
      <tr>
          <td><code>test &lt;ip&gt; …</code></td>
          <td>Dice si la dirección cae dentro de la tabla (sin tocarla)</td>
      </tr>
      <tr>
          <td><code>flush</code></td>
          <td>Vacía la tabla; la tabla sigue existiendo</td>
      </tr>
      <tr>
          <td><code>kill</code></td>
          <td>Borra la tabla entera (si no la usa ninguna regla)</td>
      </tr>
      <tr>
          <td><code>replace -f &lt;archivo&gt;</code></td>
          <td>Sustituye el contenido completo por el del archivo, en una sola operación</td>
      </tr>
      <tr>
          <td><code>expire &lt;segundos&gt;</code></td>
          <td>Borra las entradas cuyos contadores llevan más de N segundos sin actividad</td>
      </tr>
      <tr>
          <td><code>zero</code></td>
          <td>Pone a cero los contadores de la tabla (o de una entrada, si la indicas)</td>
      </tr>
  </tbody>
</table>
<p>Y sin <code>-t</code>:</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-shell" data-lang="shell"><span class="line"><span class="cl">pfctl -sT                    <span class="c1"># nombres de todas las tablas</span>
</span></span><span class="line"><span class="cl">pfctl -vvsT                  <span class="c1"># info global por tabla: entradas, evaluaciones, referencias</span>
</span></span><span class="line"><span class="cl">pfctl -T load -f /etc/pf.conf   <span class="c1"># recarga solo las definiciones de tabla del pf.conf</span>
</span></span><span class="line"><span class="cl">pfctl -sr <span class="p">|</span> grep <span class="s1">&#39;&lt;nombre&gt;&#39;</span>  <span class="c1"># qué reglas usan una tabla</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="tablas-en-pfsense">Tablas en pfSense</h2>
<ul>
<li><strong>Los aliases son tablas.</strong> Un alias de tipo Host, Network o URL Table llamado <code>Admins</code> existe en PF como <code>&lt;Admins&gt;</code>; <code>pfctl -t Admins -T show</code> te enseña lo que resolvió pfSense, incluidos los FQDN que ya convirtió a IPs.</li>
<li><strong><code>sshguard</code></strong>: donde caen las IPs bloqueadas por intentos de login fallidos a SSH o a la webGUI. Si te bloqueaste a ti mismo, <code>pfctl -t sshguard -T flush</code> (documentado por Netgate en <a href="https://docs.netgate.com/pfsense/en/latest/troubleshooting/locked-out.html">Locked out of the WebGUI</a>).</li>
<li><strong><code>virusprot</code></strong>: hosts bloqueados por superar los límites <em>Max. src. conn. rate</em> o <em>Max. states</em> de una regla. <code>pfctl -t virusprot -T show</code> y <code>-T delete &lt;ip&gt;</code> para soltar a uno.</li>
<li><strong><code>snort2c</code></strong>: bloqueos de Snort/Suricata en modo legacy. <code>-T flush</code> para desbloquear todo.</li>
<li><strong><code>bogons</code> / <code>bogonsv6</code></strong>: las listas que se actualizan solas cuando activas <em>Block bogon networks</em> en una interfaz.</li>
<li><strong>pfBlockerNG</strong>: cada lista es un alias <code>pfB_*</code>. Para saber si una IP está bloqueada por alguna: <code>pfctl -t pfB_Top_v4 -T test 203.0.113.7</code>.</li>
</ul>
<p>En OPNsense la mecánica es la misma (<code>pfctl -sT</code> para ver los nombres); la tabla de bloqueo por login se llama <code>sshlockout</code>.</p>
<h2 id="lo-que-se-pierde-al-recargar">Lo que se pierde al recargar</h2>
<p>Cualquier <code>-T add</code>, <code>-T delete</code> o <code>-T flush</code> vive hasta la siguiente <strong>recarga del filtro</strong>: guardar casi cualquier cosa en la GUI, <code>Filter Reload</code> en Status → Filter Reload, o el cron de actualización de aliases. En ese momento pfSense regenera cada tabla desde su alias y tus cambios a mano desaparecen. Para que algo sea permanente, edita el alias; para pruebas rápidas y desbloqueos, <code>pfctl</code> es perfecto.</p>
<h2 id="más-allá-de-las-tablas">Más allá de las tablas</h2>
<p>Lo que más se usa junto a lo anterior:</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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pfctl -sr                    <span class="c1"># reglas cargadas (con -vv, contadores y números de regla)</span>
</span></span><span class="line"><span class="cl">pfctl -ss                    <span class="c1"># tabla de estados</span>
</span></span><span class="line"><span class="cl">pfctl -si                    <span class="c1"># estadísticas globales</span>
</span></span><span class="line"><span class="cl">pfctl -k 192.168.1.10        <span class="c1"># matar los estados de un host</span>
</span></span><span class="line"><span class="cl">pfctl -F states              <span class="c1"># matar todos los estados (corta todas las conexiones)</span>
</span></span><span class="line"><span class="cl">pfctl -d / pfctl -e          <span class="c1"># desactivar / activar PF (sin PF no hay NAT: úsalo solo para salir de un bloqueo)</span>
</span></span><span class="line"><span class="cl">pfctl -nf /etc/pf.conf       <span class="c1"># comprobar sintaxis sin cargar</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Referencia completa: <a href="https://www.openbsdhandbook.com/pf/cheat_sheet/">pfctl cheat sheet</a> del OpenBSD Handbook y <code>man pfctl</code> en FreeBSD.</p>
]]></content:encoded></item><item><title>pfSense virtualizado con VirtIO: sin tráfico hasta desactivar hardware checksum offload</title><link>https://inet.sh/es/posts/pfsense/virtio-desactivar-hardware-checksum-offload/</link><pubDate>Fri, 25 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/es/posts/pfsense/virtio-desactivar-hardware-checksum-offload/</guid><description>pfSense u OPNsense en Proxmox, KVM o bhyve con NICs VirtIO (vtnet): el firewall arranca pero no pasa tráfico, o no se llega a los equipos detrás de él desde el host y otras VMs. Es el checksum offload de vtnet en FreeBSD. System &amp;gt; Advanced &amp;gt; Networking &amp;gt; Disable hardware checksum offload y reinicio obligatorio.</description><content:encoded><![CDATA[<h2 id="en-corto">En corto</h2>
<p><strong>System → Advanced → Networking → Disable hardware checksum offload</strong> ✔ → <strong>Save</strong> → <strong>reinicia la VM</strong>. La GUI no avisa, pero el cambio no aplica a las interfaces <code>vtnet</code> hasta el reinicio.</p>
<p>Para probar sin reiniciar (se pierde al arrancar):</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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">ifconfig vtnet0 -rxcsum -txcsum -tso -lro
</span></span><span class="line"><span class="cl">ifconfig vtnet1 -rxcsum -txcsum -tso -lro
</span></span></code></pre></td></tr></table>
</div>
</div><p>En OPNsense: <strong>Interfaces → Settings → Hardware CRC → Disable hardware checksum offload</strong>, y el mismo reinicio.</p>
<h2 id="síntomas">Síntomas</h2>
<p>La VM arranca, las interfaces están <code>UP</code>, el DHCP de la LAN reparte direcciones y aun así:</p>
<ul>
<li>Los equipos detrás del firewall no llegan a Internet, o la conexión se queda a medias (el ping pasa, TCP se cuelga).</li>
<li>Desde el propio host de virtualización o desde otras VMs del mismo host no se llega a nada protegido por pfSense, ni a la GUI.</li>
<li>Cambiar la NIC de la VM a <code>e1000</code> lo arregla al momento, lo que confirma el diagnóstico.</li>
</ul>
<h2 id="por-qué-pasa">Por qué pasa</h2>
<p>El driver <code>vtnet</code> de FreeBSD, con el offload de checksums activado, entrega a la pila de red paquetes cuyo checksum nunca llegó a calcularse: entre VMs del mismo host, y entre el host y la VM, el tráfico no pasa por una NIC física que lo haga. FreeBSD ve checksums inválidos y descarta (bug 165059 de FreeBSD). Netgate lo documenta en <a href="https://docs.netgate.com/pfsense/en/latest/virtualization/virtio-driver-support.html">VirtIO Driver Support</a>.</p>
<p>pfSense intenta marcar la opción solo cuando detecta interfaces <code>vtnet</code> <strong>en la instalación</strong>. Si la NIC VirtIO se añadió después, si restauraste el <code>config.xml</code> de un firewall físico, o si migraste la VM desde otro hipervisor con otro tipo de NIC, la opción sigue apagada.</p>
<h2 id="procedimiento">Procedimiento</h2>
<ol>
<li>
<p><strong>System → Advanced → Networking</strong>, sección <em>Network Interfaces</em>:</p>
<ul>
<li><strong>Disable hardware checksum offload</strong>: marcar.</li>
<li><strong>Disable hardware TCP segmentation offload</strong> y <strong>Disable hardware large receive offload</strong>: ya vienen marcadas por defecto; déjalas así.</li>
</ul>
</li>
<li>
<p><strong>Save</strong>.</p>
</li>
<li>
<p><strong>Reboot</strong> desde Diagnostics → Reboot. Sin este paso, nada cambia en <code>vtnet</code>.</p>
</li>
<li>
<p>Comprueba:</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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">ifconfig vtnet0 <span class="p">|</span> grep options
</span></span></code></pre></td></tr></table>
</div>
</div><p>No deben aparecer <code>RXCSUM</code> ni <code>TXCSUM</code> (ni <code>TSO4</code>, <code>LRO</code>).</p>
</li>
</ol>
<h2 id="si-sigue-igual">Si sigue igual</h2>
<ul>
<li><strong>Todas las NICs de la VM tienen que ser del mismo estilo.</strong> Una <code>vtnet</code> con offload y otra <code>e1000</code> se comportan distinto; revisa cada <code>ifconfig</code>.</li>
<li><strong>Offload en el host.</strong> Netgate señala que a veces también hay que apagar checksums y TSO en el hipervisor. En Proxmox/KVM, sobre la interfaz <code>tap</code> de la VM: <code>ethtool -K tap100i0 tx off</code> (el número es el VMID y <code>i0</code> la NIC). Es raro necesitarlo; pruébalo el último.</li>
<li><strong>Plan B</strong>: modelo de NIC <code>e1000</code> en la VM. Menos rendimiento que VirtIO sin offload, pero sin este bug.</li>
</ul>
<h2 id="trampas">Trampas</h2>
<ul>
<li><strong>El reinicio no es opcional</strong> y la GUI no lo pide. Es la causa número uno de &ldquo;lo marqué y sigue sin funcionar&rdquo;.</li>
<li><strong>No es un problema de reglas.</strong> Con el offload activo, los paquetes se descartan antes de llegar a <code>pf</code>; el log del firewall no muestra nada.</li>
<li><strong><code>e1000</code> también en OPNsense</strong> como plan B; el ajuste equivalente está en <em>Interfaces → Settings</em>.</li>
<li><strong>Multiqueue</strong> (<code>queues=N</code> en Proxmox) funciona bien con <code>vtnet</code> una vez desactivado el offload; no lo confundas con la causa.</li>
</ul>
]]></content:encoded></item><item><title>pfSense y OPNsense: el port forward no funciona desde la propia LAN (NAT reflection y split DNS)</title><link>https://inet.sh/es/posts/pfsense/nat-reflection-port-forward-desde-lan/</link><pubDate>Fri, 25 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/es/posts/pfsense/nat-reflection-port-forward-desde-lan/</guid><description>Desde fuera el port forward funciona, pero desde la LAN la IP pública o el dominio te llevan a la GUI del firewall o a un error de certificado. Dos arreglos: split DNS con host overrides en el DNS Resolver (el recomendado) o NAT reflection en modo Pure NAT con outbound NAT automático. Incluye el equivalente en OPNsense y la trampa del DNS rebinding.</description><content:encoded><![CDATA[<h2 id="en-corto">En corto</h2>
<p><strong>Opción limpia (split DNS):</strong> Services → DNS Resolver → <em>Host Overrides</em> → <code>app.example.com</code> → IP interna del servidor (<code>192.168.1.50</code>). Los clientes de la LAN tienen que usar el firewall como DNS.</p>
<p><strong>Opción NAT reflection:</strong> System → Advanced → <em>Firewall &amp; NAT</em>:</p>
<ul>
<li><em>NAT Reflection mode for port forwards</em>: <strong>Pure NAT</strong></li>
<li><em>Enable NAT Reflection for 1:1 NAT</em>: ✔</li>
<li><em>Enable automatic outbound NAT for Reflection</em>: ✔</li>
</ul>
<p>Save y prueba desde un cliente de la LAN contra la IP pública.</p>
<h2 id="el-problema">El problema</h2>
<p>El port forward <code>WAN:443 → 192.168.1.50:443</code> funciona desde Internet. Desde un equipo de la misma LAN, <code>https://app.example.com</code> (que resuelve a la IP pública) responde con la GUI de pfSense, un error de certificado, o <code>Potential DNS Rebind attack detected</code>.</p>
<p>El paquete sale del cliente hacia la IP pública, llega al firewall por la interfaz LAN y ahí se queda: la regla de port forward escucha en la WAN, no en la LAN, y la IP de destino es una dirección del propio firewall. Nunca &ldquo;sale y vuelve a entrar&rdquo;.</p>
<h2 id="opción-1-split-dns">Opción 1: split DNS</h2>
<p>Es la que recomienda Netgate: el nombre resuelve a la IP interna dentro y a la pública fuera. El tráfico va directo, el servidor ve la IP real del cliente y el firewall no toca nada.</p>
<ol>
<li>Services → DNS Resolver → <em>Host Overrides</em> → <strong>Add</strong>: host <code>app</code>, dominio <code>example.com</code>, IP <code>192.168.1.50</code>. Si el servicio tiene varios nombres, añade <em>Additional Names</em>.</li>
<li>Los clientes deben usar el firewall como DNS (lo normal si el DHCP lo entrega). Un cliente con <code>8.8.8.8</code> fijo seguirá yendo a la IP pública.</li>
<li><code>nslookup app.example.com</code> desde la LAN → <code>192.168.1.50</code>.</li>
</ol>
<p>Requisitos: el puerto interno y el externo tienen que ser el mismo (si fuera reenvías <code>8443 → 443</code>, el cliente interno tendrá que usar <code>443</code> directamente).</p>
<p>Si usas DNS Forwarder (dnsmasq) en vez del Resolver, el ajuste equivalente está en Services → DNS Forwarder → <em>Host Overrides</em>.</p>
<h2 id="opción-2-nat-reflection">Opción 2: NAT reflection</h2>
<p>Cuando no controlas el DNS de los clientes, o hay un montón de puertos, el firewall puede &ldquo;reflejar&rdquo; la conexión: reescribe el destino a la IP interna y, con el outbound NAT automático, también el origen, para que la respuesta vuelva por el firewall.</p>
<p>System → Advanced → <em>Firewall &amp; NAT</em>, sección <em>Network Address Translation</em>:</p>
<table>
  <thead>
      <tr>
          <th>Ajuste</th>
          <th>Valor</th>
          <th>Por qué</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>NAT Reflection mode for port forwards</td>
          <td><strong>Pure NAT</strong></td>
          <td>Solo reglas de PF: todos los protocolos, sin límite de puertos. <em>NAT + Proxy</em> usa un daemon, solo TCP y máximo 500 puertos por regla</td>
      </tr>
      <tr>
          <td>Enable NAT Reflection for 1:1 NAT</td>
          <td>✔</td>
          <td>Si tienes 1:1 NAT además de port forwards</td>
      </tr>
      <tr>
          <td>Enable automatic outbound NAT for Reflection</td>
          <td>✔</td>
          <td>Sin esto, si cliente y servidor están en la misma subred el servidor responde directo al cliente, que no reconoce la conexión y la corta</td>
      </tr>
      <tr>
          <td>Reflection Timeout</td>
          <td>(solo NAT + Proxy)</td>
          <td>Ignóralo en Pure NAT</td>
      </tr>
  </tbody>
</table>
<p>También se puede activar por regla: Firewall → NAT → Port Forward → editar → <em>NAT reflection</em> → <em>Enable (Pure NAT)</em>, dejando el global en <em>Disable</em>.</p>
<p>Costes: el servidor ve como origen la IP LAN del firewall (no la del cliente), y todo ese tráfico pasa por el firewall aunque cliente y servidor estén en el mismo switch.</p>
<h2 id="opnsense">OPNsense</h2>
<p>Misma idea, otros menús:</p>
<ul>
<li>Split DNS: Services → Unbound DNS → <em>Overrides</em> → Host Overrides.</li>
<li>Reflection: Firewall → Settings → <em>Advanced</em> → <strong>Reflection for port forwards</strong>, <strong>Reflection for 1:1</strong>, <strong>Automatic outbound NAT for Reflection</strong>.</li>
</ul>
<h2 id="trampas">Trampas</h2>
<ul>
<li><strong>DNS rebinding.</strong> Si en vez de un host override apuntas el Resolver a un DNS externo que devuelve IPs privadas (un DNS interno de otra sede, un proveedor con vistas), Unbound descarta esas respuestas por protección contra rebinding. No desmarques <em>Disable DNS Rebinding Checks</em> en System → Advanced → Admin Access: añade en Services → DNS Resolver → <em>Custom options</em> <code>private-domain: &quot;example.com&quot;</code> y solo ese dominio queda exento.</li>
<li><strong>El cliente con DNS fijo</strong> se salta el split DNS. O lo obligas por DHCP, o lo capturas con una regla de NAT de puerto 53, o usas reflection.</li>
<li><strong>Pure NAT necesita saber la interfaz y el gateway</strong> en el momento de cargar reglas; con WANs dinámicas que cambian de IP, un <em>Filter Reload</em> después del cambio de IP lo arregla.</li>
<li><strong>Puertos distintos dentro y fuera</strong> solo funcionan con reflection; split DNS no traduce puertos.</li>
<li><strong>Certificados</strong>: con split DNS el navegador sigue viendo <code>app.example.com</code>, así que el certificado del servidor sirve igual. Si reflejas por IP pública, también. El error de certificado del principio era el de la GUI del firewall, no el del servidor.</li>
</ul>
<p>Referencias: <a href="https://docs.netgate.com/pfsense/en/latest/recipes/port-forwards-from-local-networks.html">Port Forwards from Local Networks</a> y <a href="https://docs.netgate.com/pfsense/en/latest/nat/reflection.html">NAT Reflection</a> en la documentación de Netgate.</p>
]]></content:encoded></item><item><title>pfSense: certificado Let's Encrypt con ACME y DNS de Cloudflare, y usarlo en HAProxy</title><link>https://inet.sh/es/posts/pfsense/haproxy-acme-letsencrypt-cloudflare/</link><pubDate>Fri, 25 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/es/posts/pfsense/haproxy-acme-letsencrypt-cloudflare/</guid><description>Emitir un certificado de Let&amp;#39;s Encrypt en pfSense con el paquete ACME y validación DNS-01 en Cloudflare (solo API token, Account ID y Zone ID; sin la Global API Key), incluido wildcard, y colgarlo del frontend de HAProxy con reinicio automático en cada renovación. Con la trampa del filtro de IP del token y del DNS Sleep.</description><content:encoded><![CDATA[<h2 id="en-corto">En corto</h2>
<ol>
<li><strong>Cloudflare</strong> → My Profile → API Tokens → <em>Create Token</em> → plantilla <em>Edit zone DNS</em>: permisos <strong>Zone → Zone → Read</strong> y <strong>Zone → DNS → Edit</strong>, <em>Zone Resources</em> limitado a tu zona. Copia el token; el <strong>Account ID</strong> y el <strong>Zone ID</strong> están en la página <em>Overview</em> de la zona.</li>
<li><strong>pfSense</strong> → System → Package Manager → instalar <strong>acme</strong> y <strong>haproxy</strong>.</li>
<li>Services → ACME → <em>Account keys</em> → Add: <em>ACME Server</em> <strong>Let&rsquo;s Encrypt Production ACME v2</strong> (o <em>Staging</em> para probar) → <em>Create new account key</em> → <em>Register</em> → Save.</li>
<li>Services → ACME → <em>Certificates</em> → Add: nombre, la cuenta, <em>Domain SAN list</em>: <code>app.example.com</code> (y <code>*.example.com</code> si quieres wildcard) con método <strong>DNS-Cloudflare</strong>, y en sus campos solo <strong>Token</strong>, <strong>Account ID</strong> y <strong>Zone ID</strong>. <em>Actions list</em>: <strong>Restart Local Service</strong> → <code>haproxy</code>. Save → <strong>Issue</strong>.</li>
<li>Services → HAProxy → <em>Frontend</em> → editar → <em>SSL Offloading</em> ✔ en la dirección de escucha → <em>Certificate</em>: el que acaba de crear ACME → Save → <em>Apply Changes</em>.</li>
</ol>
<p>Renovación automática (cron diario del paquete) y HAProxy reiniciado en cada renovación.</p>
<h2 id="cloudflare-el-token">Cloudflare: el token</h2>
<p>La <em>Global API Key</em> que piden las guías antiguas da acceso a toda la cuenta; un <strong>API token</strong> con permisos mínimos es lo correcto y es lo que el método <em>DNS-Cloudflare</em> del paquete usa cuando rellenas <code>Token</code>:</p>
<table>
  <thead>
      <tr>
          <th>Campo del paquete</th>
          <th>Valor</th>
          <th>Dónde</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Token</td>
          <td>El API token</td>
          <td>My Profile → API Tokens</td>
      </tr>
      <tr>
          <td>Account ID</td>
          <td>ID de la cuenta</td>
          <td>Zona → Overview → <em>API</em> (columna derecha)</td>
      </tr>
      <tr>
          <td>Zone ID</td>
          <td>ID de la zona</td>
          <td>Mismo sitio</td>
      </tr>
      <tr>
          <td>Key / Email</td>
          <td>Vacíos</td>
          <td>Solo para la Global API Key (no la uses)</td>
      </tr>
  </tbody>
</table>
<p>Con <code>Token</code> basta uno de los dos IDs; poner ambos no molesta. Permisos del token: <strong>Zone:Read</strong> y <strong>DNS:Edit</strong> sobre la zona concreta (<em>Include → Specific zone</em>).</p>
<p>Si activas <em>Client IP Address Filtering</em> en el token, tiene que incluir la IP pública <strong>IPv4 y también IPv6</strong> del firewall: pfSense sale por la que tenga a mano, y un token limitado a la v4 falla en cuanto la llamada a la API va por v6.</p>
<h2 id="acme-cuenta-y-certificado">ACME: cuenta y certificado</h2>
<p><strong>Cuenta.</strong> Crea dos: una contra <em>Staging</em> para probar y otra contra <em>Production</em>. Staging no tiene límites de emisión pero sus certificados no son de confianza; cuando todo funcione, cambia la cuenta del certificado a la de producción y vuelve a emitir.</p>
<p><strong>Certificado.</strong> En Services → ACME → <em>Certificates</em> → Add:</p>
<ul>
<li><em>Domain SAN list</em>: una entrada por nombre. Para un wildcard <code>*.example.com</code> la validación tiene que ser DNS-01 (HTTP-01 no lo permite). Un wildcard no cubre el apex: añade <code>example.com</code> como segunda entrada.</li>
<li><em>Method</em>: <strong>DNS-Cloudflare</strong> en cada entrada, con los tres campos.</li>
<li><em>DNS Sleep</em>: Cloudflare propaga en segundos; el valor por defecto sobra. Si la emisión falla por <code>TXT not found</code>, súbelo a <code>120</code>.</li>
<li><em>Key</em>: RSA 2048 o ECDSA 256; ambos van bien con HAProxy.</li>
<li><em>Actions list</em>: <strong>Restart Local Service</strong> → <strong>haproxy</strong>. Sin esto, HAProxy sigue sirviendo el certificado viejo hasta que alguien lo reinicie, aunque ACME ya haya renovado.</li>
</ul>
<p><strong>Issue</strong> desde la lista de certificados. El resultado aparece en System → Certificates con el nombre que le diste; ahí lo verá HAProxy.</p>
<h2 id="haproxy-usar-el-certificado">HAProxy: usar el certificado</h2>
<p>Services → HAProxy:</p>
<ol>
<li><em>Settings</em>: activar HAProxy, y en <em>Max SSL Diffie-Hellman size</em> deja 2048 o más.</li>
<li><em>Frontend</em> → tu frontend HTTPS → <em>External address</em>: puerto <code>443</code>, <strong>SSL Offloading</strong> ✔ → sección <em>SSL Offloading</em> → <strong>Certificate</strong>: el de ACME. Para varios dominios en el mismo frontend, <em>Additional certificates</em> con los demás; HAProxy elige por SNI.</li>
<li>Save → <strong>Apply Changes</strong>.</li>
</ol>
<p>Prueba desde fuera:</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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">openssl s_client -connect app.example.com:443 -servername app.example.com &lt;/dev/null 2&gt;/dev/null <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  <span class="p">|</span> openssl x509 -noout -issuer -dates
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>issuer=C = US, O = Let's Encrypt</code> y <code>notAfter</code> a 90 días.</p>
<p>De regalo, la GUI de pfSense con el mismo certificado: System → Advanced → <em>Admin Access</em> → <em>SSL/TLS Certificate</em>, y una segunda acción en el certificado ACME: <strong>Shell Command</strong> <code>/etc/rc.restart_webgui</code>.</p>
<h2 id="trampas">Trampas</h2>
<ul>
<li><strong>Cloudflare en modo proxy (nube naranja)</strong> no afecta a DNS-01, pero sí a cómo llega el tráfico: si el registro <code>A</code> está proxificado, HAProxy ve la IP de Cloudflare como origen. Para un servicio interno detrás de pfSense, nube gris.</li>
<li><strong><code>Issue</code> falla con <code>Invalid request headers</code> o <code>Authentication error</code></strong>: token sin permiso sobre esa zona, filtro de IP del token, o Zone ID de otra zona.</li>
<li><strong>Rate limits de producción</strong>: cinco certificados por conjunto de nombres a la semana. Prueba en staging.</li>
<li><strong>El certificado se renueva; HAProxy no lo recarga solo.</strong> La acción de reinicio es obligatoria. Comprueba en Services → ACME que la última renovación tiene fecha y en <code>openssl s_client</code> que <code>notAfter</code> avanzó.</li>
<li><strong>Los ACME se renuevan a los 2/3 de vida</strong> (unos 60 días). Si ves el cron ejecutándose y nada cambia antes de eso, es normal.</li>
</ul>
<p>Referencia: <a href="https://docs.netgate.com/pfsense/en/latest/packages/acme/index.html">ACME package</a> en la documentación de Netgate y la <a href="https://github.com/acmesh-official/acme.sh/wiki/dnsapi">lista de proveedores DNS de acme.sh</a> (sección <code>dns_cf</code>).</p>
]]></content:encoded></item><item><title>pfSense: ld-elf.so.1: Shared object "libssl.so.30" not found, required by "pkg"</title><link>https://inet.sh/es/posts/pfsense/libssl-so-30-not-found-pkg/</link><pubDate>Fri, 25 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/es/posts/pfsense/libssl-so-30-not-found-pkg/</guid><description>pkg update y el Package Manager de pfSense mueren con &amp;#39;Shared object libssl.so.30 not found, required by pkg&amp;#39;: el binario pkg quedó enlazado contra un OpenSSL que el sistema base todavía no tiene. Recuperación con pkg-static (certctl rehash, reinstalar pkg, pfSense-repo y pfSense-upgrade) y cómo terminar la actualización bien.</description><content:encoded><![CDATA[<h2 id="en-corto">En corto</h2>
<p>Desde la consola (opción 8) o <strong>Diagnostics → Command Prompt</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><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">certctl rehash
</span></span><span class="line"><span class="cl">pkg-static clean -ay
</span></span><span class="line"><span class="cl">pkg-static install -fy pkg
</span></span><span class="line"><span class="cl">pkg-static install -xfy pfSense-repo pfSense-upgrade
</span></span><span class="line"><span class="cl">pkg-static update -f
</span></span><span class="line"><span class="cl">pfSense-upgrade -d -c        <span class="c1"># muestra qué versión ofrece la rama configurada</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Con <code>pkg</code> vivo otra vez, termina la actualización pendiente por <strong>System → Update</strong> (o <code>pfSense-upgrade -d</code>), no con <code>pkg upgrade</code>.</p>
<h2 id="el-error">El error</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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ld-elf.so.1: Shared object &#34;libssl.so.30&#34; not found, required by &#34;pkg&#34;
</span></span></code></pre></td></tr></table>
</div>
</div><p>Lo verás al hacer <code>pkg update</code>, al instalar un paquete, o de forma indirecta: <strong>System → Package Manager</strong> aparece vacío y <strong>System → Update</strong> no puede comprobar versiones. A veces el binario que se queja es <code>pfSense-repoc</code> en vez de <code>pkg</code>; es el mismo problema.</p>
<h2 id="por-qué-pasa">Por qué pasa</h2>
<p>pfSense es FreeBSD, y <code>pkg</code> es un binario enlazado dinámicamente contra la <code>libssl</code> del sistema. Cuando el repositorio configurado corresponde a una versión de pfSense construida sobre un FreeBSD más nuevo, sus paquetes vienen enlazados contra un OpenSSL más nuevo (<code>libssl.so.30</code> = OpenSSL 3). Si <code>pkg</code>, o <code>pfSense-repoc</code>, se actualiza desde ese repo <strong>antes</strong> que el sistema base, el binario nuevo pide una biblioteca que en tu instalación todavía no existe.</p>
<p>Cómo se llega ahí:</p>
<ul>
<li>Un <code>pkg upgrade</code> a mano desde la shell. En pfSense no se hace nunca.</li>
<li>Instalar o reinstalar un paquete después de cambiar la <strong>Branch</strong> en System → Update a una versión mayor, sin haber actualizado.</li>
<li>Una actualización interrumpida a medias (fue típico en el salto 2.7.0 → 2.7.1/2.7.2, cuando la base cambió a OpenSSL 3).</li>
<li>En pfSense Plus, volver a un boot environment anterior con el repo apuntando al nuevo (Redmine #15070).</li>
</ul>
<p>El nombre de la biblioteca cambiará con las versiones; la mecánica y el arreglo son los mismos.</p>
<h2 id="arreglo">Arreglo</h2>
<p><code>pkg-static</code> es la copia de <code>pkg</code> enlazada estáticamente: no necesita <code>libssl</code> y por eso funciona cuando <code>pkg</code> no.</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><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># 1. Que pkg-static pueda hablar TLS con el repo (vale hasta el siguiente reinicio)</span>
</span></span><span class="line"><span class="cl">certctl rehash
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 2. Tirar la caché y reinstalar pkg y la configuración del repo</span>
</span></span><span class="line"><span class="cl">pkg-static clean -ay
</span></span><span class="line"><span class="cl">pkg-static install -fy pkg
</span></span><span class="line"><span class="cl">pkg-static install -xfy pfSense-repo pfSense-upgrade   <span class="c1"># -x: regex, así entra también pfSense-repoc</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 3. Refrescar metadatos y comprobar</span>
</span></span><span class="line"><span class="cl">pkg-static update -f
</span></span><span class="line"><span class="cl">pfSense-upgrade -d -c
</span></span></code></pre></td></tr></table>
</div>
</div><p>Si <code>pkg-static update</code> sigue fallando, <code>pkg-static -d update</code> da la traza completa (certificados, DNS, rama equivocada).</p>
<p>Antes del paso 3 revisa la <strong>Branch</strong> en System → Update: debe ser la versión a la que quieres ir, no una intermedia. Y termina el trabajo por la GUI o con <code>pfSense-upgrade -d</code>: un <code>pkg upgrade</code> suelto te deja a medio camino otra vez.</p>
<p>Si el sistema base está tan mezclado que ni así arranca, el último recurso documentado por Netgate es <code>pkg-static upgrade -f</code> (reinstala todo), y tras eso reiniciar.</p>
<h2 id="trampas">Trampas</h2>
<ul>
<li><strong><code>pkg upgrade</code> no existe en pfSense.</strong> El único camino soportado es System → Update / <code>pfSense-upgrade</code>. Toda esta situación nace de saltárselo.</li>
<li><strong>Respaldo antes</strong>: Diagnostics → Backup &amp; Restore. Con ZFS, además un boot environment (System → Boot Environments) te deja volver atrás en un reinicio.</li>
<li><strong>No copies <code>libssl.so.30</code> de otro FreeBSD.</strong> Circula como truco; deja el sistema con bibliotecas de dos versiones y rompe la siguiente actualización.</li>
<li><strong>Paquetes primero, no.</strong> Con una actualización pendiente, actualiza el sistema y luego instala paquetes, no al revés.</li>
<li>El repo y el upgrade usan HTTPS: si <code>certctl rehash</code> no basta, revisa hora del sistema y DNS del firewall.</li>
</ul>
<p>Referencia: <a href="https://docs.netgate.com/pfsense/en/latest/troubleshooting/upgrades.html">Upgrade Troubleshooting</a> en la documentación de Netgate.</p>
]]></content:encoded></item><item><title>pfSense: OpenVPN deja de aceptar clientes con 'CRL has expired'</title><link>https://inet.sh/es/posts/pfsense/openvpn-crl-has-expired/</link><pubDate>Fri, 25 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/es/posts/pfsense/openvpn-crl-has-expired/</guid><description>Todos los clientes OpenVPN de un pfSense fallan a la vez con &amp;#39;VERIFY ERROR: depth=0, error=CRL has expired&amp;#39;. La CRL interna caducó, o nació caducada por el bug de los 9999 días (Redmine #13424, fecha más allá de 2050 codificada como 1950). Cómo regenerarla desde Cert Manager, el parche para versiones viejas y cómo comprobarlo con openssl.</description><content:encoded><![CDATA[<h2 id="en-corto">En corto</h2>
<p><strong>System → Certificates → Certificate Revocation</strong> → lápiz sobre la CRL que usa el servidor OpenVPN → <strong>Lifetime (Days)</strong> = <code>730</code> → <strong>Save</strong>. Guardar regenera la CRL con fechas válidas. Después, <strong>Status → Services → OpenVPN → Restart</strong>. Los clientes vuelven a entrar sin cambiar nada de su lado.</p>
<p>Si la GUI no te deja poner una vida sensata (versiones anteriores a 2.7 / 23.01), primero <strong>System → Patches</strong> (paquete <em>System Patches</em>) → aplica <em>Fix for CRL expiration lifetime default and maximum values</em> (<code>a3c1589086ea67d25a28ec14ab95d7fd9ab25fa2</code>), y luego el paso anterior. O actualiza pfSense: las versiones actuales ya lo llevan.</p>
<h2 id="el-error">El error</h2>
<p>En el log del servidor (Status → System Logs → OpenVPN) y en el cliente:</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">VERIFY ERROR: depth=0, error=CRL has expired: CN=client1, serial=3
</span></span><span class="line"><span class="cl">OpenSSL: error:0A000086:SSL routines::certificate verify failed
</span></span><span class="line"><span class="cl">TLS_ERROR: BIO read tls_read_plaintext error
</span></span><span class="line"><span class="cl">TLS Error: TLS handshake failed
</span></span></code></pre></td></tr></table>
</div>
</div><p>La pista es que <strong>fallan todos los clientes a la vez</strong>. Un certificado caducado tumba a un cliente; una CRL caducada tumba al servidor entero, porque la revisa en cada handshake.</p>
<h2 id="por-qué-pasa">Por qué pasa</h2>
<p>Dos caminos llevan aquí:</p>
<ol>
<li><strong>La CRL caducó de verdad.</strong> Cada CRL tiene un <code>nextUpdate</code>; pasada esa fecha OpenSSL la trata como inválida y OpenVPN rechaza a todos. Una CRL creada hace años con vida corta y nunca tocada acaba así.</li>
<li><strong>La CRL nació caducada.</strong> pfSense generaba CRLs con <strong>9999 días</strong> por defecto, lo que las llevaba más allá de <strong>2050</strong>. X.509 codifica las fechas hasta 2049 como <code>UTCTime</code> con año de dos cifras, y pfSense escribía <code>50</code> donde tocaba <code>GeneralizedTime</code>: OpenSSL lo lee como <strong>1950</strong> y la CRL &ldquo;expiró&rdquo; hace 75 años. Es el <a href="https://redmine.pfsense.org/issues/13424">bug #13424</a> de Redmine, y explica por qué instalaciones sanas empezaron a fallar en 2022–2023 sin que nadie tocara nada: las CRLs regeneradas por cualquier cambio cruzaron el 2050.</li>
</ol>
<p>El arreglo de Netgate bajó la vida por defecto a <strong>730 días</strong>, limitó el máximo (2050 en 64 bits, 2038 en 32 bits) y va incluido en las versiones actuales; para las viejas quedó como parche del paquete <em>System Patches</em>.</p>
<h2 id="comprobarlo">Comprobarlo</h2>
<p>pfSense escribe la CRL de cada servidor OpenVPN en su directorio de configuración:</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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">ls /var/etc/openvpn/server1/
</span></span><span class="line"><span class="cl">openssl crl -in /var/etc/openvpn/server1/crl-verify -noout -lastupdate -nextupdate
</span></span></code></pre></td></tr></table>
</div>
</div><p>Un <code>nextUpdate</code> en el pasado, o un año 1950, es el diagnóstico completo.</p>
<h2 id="arreglo">Arreglo</h2>
<ol>
<li><strong>Identifica qué CRL usa el servidor</strong>: VPN → OpenVPN → Servers → editar → <em>Peer Certificate Revocation list</em>.</li>
<li><strong>Regenérala</strong>: System → Certificates → Certificate Revocation → lápiz sobre esa CRL → <strong>Lifetime (Days)</strong> <code>730</code> → Save. Cualquier guardado la reescribe con <code>lastUpdate</code> = ahora.</li>
<li><strong>Reinicia OpenVPN</strong>: Status → Services. OpenVPN relee <code>crl-verify</code> al detectar el cambio, pero el reinicio evita esperar a que lo note.</li>
<li><strong>Vuelve a comprobar</strong> con el <code>openssl crl</code> de arriba.</li>
</ol>
<p>En una versión sin el fix, entre el paso 1 y el 2: System → Package Manager → instalar <em>System_Patches</em> → System → Patches → el parche aparece en la lista de recomendados → <strong>Apply</strong>. Sin él, la GUI vuelve a escribir 9999 y el problema se repite en cuanto guardes.</p>
<h2 id="trampas">Trampas</h2>
<ul>
<li><strong>No borres la CRL para &ldquo;arreglarlo&rdquo;</strong>: sin CRL, los certificados que revocaste vuelven a ser válidos. Edítala.</li>
<li><strong>Una CRL por CA</strong>: si tienes varios servidores OpenVPN con CAs distintas, revisa cada una.</li>
<li><strong>Las CRLs importadas</strong> (de una CA externa) no se pueden regenerar desde pfSense; pide una nueva a tu CA y vuelve a importarla.</li>
<li><strong>730 días también se acaban.</strong> Cualquier revocación, edición de la CRL o cambio en el servidor OpenVPN la regenera; si no tocas nada en dos años, vuelves aquí. Un aviso en el calendario cuesta poco.</li>
<li><strong>Pasado 2038 en hardware de 32 bits</strong> aplica el otro límite; en la práctica, ninguno de esos equipos debería seguir siendo tu firewall.</li>
</ul>
<p>Referencias: <a href="https://docs.netgate.com/pfsense/en/latest/certificates/crl.html">Certificate Revocation List Management</a> en la documentación de Netgate y el <a href="https://blog.pasqualefiorillo.it/how-to-fix-crl-has-expired-openvpn-error-on-pfsense/">análisis de Pasquale Fiorillo</a>.</p>
]]></content:encoded></item></channel></rss>