<?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>Opnsense on inetshell</title><link>https://inet.sh/es/tags/opnsense/</link><description>Recent content in Opnsense on inetshell</description><generator>Hugo</generator><language>es</language><lastBuildDate>Mon, 05 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://inet.sh/es/tags/opnsense/index.xml" rel="self" type="application/rss+xml"/><item><title>OPNsense: port-forward inalcanzable desde la misma subred WAN (reply-to)</title><link>https://inet.sh/es/posts/opnsense/reply-to-misma-subred-wan/</link><pubDate>Mon, 05 Oct 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/es/posts/opnsense/reply-to-misma-subred-wan/</guid><description>Por qué un port-forward de OPNsense funciona desde internet pero da timeout desde una IP de la misma /24 que la WAN, y cómo lo arregla «Disable reply-to on WAN rules».</description><content:encoded><![CDATA[<h2 id="en-corto">En corto</h2>
<p><strong>Firewall → Settings → Advanced → Disable reply-to on WAN rules</strong> → marcar → <strong>Save</strong> → <strong>Apply</strong>.</p>
<p>Síntoma: un port-forward o una regla WAN responde desde cualquier IP de internet, pero desde un equipo que vive en <strong>la misma subred que la WAN del firewall</strong> todo da timeout (SSH, HTTPS, incluso <code>ping</code>), aunque la regla diga <code>from any</code>.</p>
<p>Con una sola WAN es seguro: el firewall deja de forzar el regreso por el gateway y vuelve a usar su tabla de rutas.</p>
<h2 id="el-escenario">El escenario</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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">                203.0.113.0/24  (mismo segmento L2)
</span></span><span class="line"><span class="cl">   ┌────────────────────┬────────────────────┬────────────────────┐
</span></span><span class="line"><span class="cl">   │                    │                    │                    │
</span></span><span class="line"><span class="cl"> gateway             fw01 (OPNsense)      vecino               internet
</span></span><span class="line"><span class="cl"> 203.0.113.1         WAN 203.0.113.40     203.0.113.50         (vía gateway)
</span></span><span class="line"><span class="cl">                        │ rdr :22 → 192.168.1.10
</span></span><span class="line"><span class="cl">                        ▼
</span></span><span class="line"><span class="cl">                     servidor LAN
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>Desde internet: <code>ssh -p 22 203.0.113.40</code> entra al servidor de la LAN. Bien.</li>
<li>Desde <code>vecino</code> (203.0.113.50): timeout en todos los puertos.</li>
</ul>
<h2 id="qué-hace-reply-to">Qué hace <code>reply-to</code></h2>
<p>OPNsense genera las reglas de las interfaces tipo WAN (las que tienen gateway) con <code>reply-to</code> apuntando a ese gateway:</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">pfctl -sr <span class="p">|</span> grep reply-to
</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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">pass in quick on vtnet0 reply-to (vtnet0 203.0.113.1) inet proto tcp from any to 192.168.1.10 port = ssh flags S/SA keep state
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>reply-to</code> es una opción de <strong>pf</strong>. Cuando un paquete entra y crea un estado, pf guarda en ese estado que <strong>todas las respuestas</strong> salgan por <code>vtnet0</code> hacia <code>203.0.113.1</code>. Esas respuestas no pasan por la tabla de rutas: pf decide el siguiente salto por su cuenta.</p>
<p>Existe por <strong>multi-WAN</strong>. Si tienes dos proveedores y un cliente entra por la WAN2, la respuesta tiene que salir por la WAN2. Si sale por la ruta por defecto (WAN1), lleva una IP de origen que el proveedor 1 no reconoce y se pierde. <code>reply-to</code> garantiza el regreso simétrico sin tener que pensar en rutas.</p>
<h2 id="por-qué-rompe-a-los-vecinos">Por qué rompe a los vecinos</h2>
<p>Para un cliente en internet, «responder al gateway» es justo lo que haría la tabla de rutas, así que no se nota nada.</p>
<p>Para el vecino es distinto. Su IP (203.0.113.50) está en una <strong>red conectada</strong> del firewall. Sin <code>reply-to</code>, el firewall resolvería su MAC por ARP y le respondería directo. Con <code>reply-to</code>, el SYN-ACK sale con la <strong>MAC del gateway</strong> como destino:</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"><span class="c1"># en el OPNsense (shell csh: envuelve en sh -c)</span>
</span></span><span class="line"><span class="cl">sh -c <span class="s2">&#34;tcpdump -lnei vtnet0 host 203.0.113.50 and tcp port 22&#34;</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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">&lt;mac-vecino&gt; &gt; &lt;mac-fw01&gt;          203.0.113.50.49612 &gt; 203.0.113.40.22: Flags [S]    ← llega directo
</span></span><span class="line"><span class="cl">&lt;mac-fw01&gt;   &gt; 00:00:5e:00:01:&lt;vrid&gt; 203.0.113.40.22 &gt; 203.0.113.50.49612: Flags [S.]   ← sale al gateway
</span></span></code></pre></td></tr></table>
</div>
</div><p>(<code>00:00:5e:00:01:xx</code> es la MAC virtual de VRRP: el gateway era un par redundante.)</p>
<p>A partir de ahí depende del gateway. Para él, el paquete entra por la misma interfaz por la que tendría que salir, y si tiene un filtro con estado ve un SYN-ACK de una conexión cuyo SYN nunca vio. En el lab, el gateway simplemente no lo devolvía al segmento. En el vecino sólo se ven SYN reintentándose y ningún <code>[S.]</code>.</p>
<p>La huella en el firewall es un estado a medias:</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">pfctl -ss <span class="p">|</span> grep 203.0.113.50
</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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">all tcp 192.168.1.10:22 (203.0.113.40:22) &lt;- 203.0.113.50:57792   SYN_SENT:ESTABLISHED
</span></span></code></pre></td></tr></table>
</div>
</div><p>El servidor ya respondió (<code>ESTABLISHED</code>) y el cliente sigue esperando (<code>SYN_SENT</code>): <strong>la respuesta se pierde de regreso</strong>, no a la ida. El filtro no está bloqueando nada.</p>
<h2 id="qué-cambia-el-fix">Qué cambia el fix</h2>
<p>Con <strong>Disable reply-to on WAN rules</strong> activado, OPNsense regenera las reglas sin <code>reply-to</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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">pass in quick on vtnet0 inet proto tcp from any to 192.168.1.10 port = ssh flags S/SA keep state
</span></span></code></pre></td></tr></table>
</div>
</div><p>El estado ya no fija el siguiente salto, así que las respuestas pasan por la tabla de rutas normal:</p>
<table>
  <thead>
      <tr>
          <th>Destino de la respuesta</th>
          <th>Con <code>reply-to</code></th>
          <th>Sin <code>reply-to</code></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Cliente en internet</td>
          <td>gateway</td>
          <td>gateway (ruta por defecto)</td>
      </tr>
      <tr>
          <td>Vecino en 203.0.113.0/24</td>
          <td>gateway ✗</td>
          <td>directo por ARP ✓</td>
      </tr>
      <tr>
          <td>Cliente que entró por otra WAN (multi-WAN)</td>
          <td>su gateway ✓</td>
          <td>ruta por defecto ✗</td>
      </tr>
  </tbody>
</table>
<p>La última fila es el costo. Con multi-WAN no lo desactives global. En vez de eso, quita el <code>reply-to</code> sólo en la regla del port-forward afectado: en la regla, <strong>Advanced → reply-to</strong>. Revisa las opciones que ofrece tu versión.</p>
<h2 id="verificar">Verificar</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></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"># en el OPNsense</span>
</span></span><span class="line"><span class="cl">sh -c <span class="s2">&#34;pfctl -sr | grep -c reply-to; grep -n disablereplyto /conf/config.xml&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># esperado: 0  y  &lt;disablereplyto&gt;yes&lt;/disablereplyto&gt;</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></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"># desde el vecino</span>
</span></span><span class="line"><span class="cl">nc -z -w5 203.0.113.40 <span class="m">22</span> <span class="o">&amp;&amp;</span> <span class="nb">echo</span> abierto
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="trampas">Trampas</h2>
<ul>
<li><strong>«Disable force gateway» no es esto.</strong> Está en la misma pantalla y tiene un nombre parecido, pero sólo afecta el tráfico que <strong>origina el propio firewall</strong> (DNS, NTP, actualizaciones). No toca las respuestas a tráfico entrante.</li>
<li><strong>Una ruta estática en el gateway no ayuda.</strong> El gateway ya tiene la subred como conectada; el problema es que no regresa el paquete al mismo segmento, no que no sepa llegar.</li>
<li><strong>Shell csh.</strong> Por SSH, OPNsense entra a <code>csh</code> y cosas como <code>2&gt;/dev/null</code> fallan con <code>Ambiguous output redirect</code>. Usa <code>sh -c &quot;...&quot;</code> o <code>ssh root@fw01 sh &lt;&lt;'EOF'</code>.</li>
<li><strong>Revisa todos los puertos, no uno.</strong> Si falla sólo un puerto es otro problema (regla, NAT, servicio). Si fallan todos, incluido <code>ping</code>, y sólo desde la misma subred, sospecha de <code>reply-to</code>.</li>
</ul>
<h2 id="ver-también">Ver también</h2>
<ul>
<li>Documentación de OPNsense, configuración del firewall: <a href="https://docs.opnsense.org/manual/firewall_settings.html">https://docs.opnsense.org/manual/firewall_settings.html</a></li>
<li><code>pf.conf(5)</code>, opciones <code>route-to</code> / <code>reply-to</code>: <a href="https://man.freebsd.org/cgi/man.cgi?query=pf.conf">https://man.freebsd.org/cgi/man.cgi?query=pf.conf</a></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></channel></rss>