<?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>Iptables on Tarragon</title><link>https://tarrragon.github.io/blog/tags/iptables/</link><description>Recent content in Iptables on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Mon, 26 Oct 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/iptables/index.xml" rel="self" type="application/rss+xml"/><item><title>DOCKER-USER（Docker 留給使用者的轉送規則鏈）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/docker-user/</link><pubDate>Mon, 26 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/docker-user/</guid><description>&lt;p>DOCKER-USER 是 Docker 在 iptables 的 filter 表裡建的一條自訂鏈：FORWARD 鏈的第一條規則無條件跳到它，Docker 自己不在裡面寫任何規則，啟動與重啟時也不清它的內容，讓使用者在 Docker 放行容器流量之前插入自己的限制。它管的是轉送路徑：Docker 發佈的連接埠經過 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/dnat/" data-link-title="DNAT（目的位址轉換）" data-link-desc="容器發佈的連接埠沒被主機防火牆擋下、port forwarding 的限制規則要寫在位址轉換之前還是之後、或要比對位址轉換之前的連接埠時，查 DNAT 改寫封包目的地的時間點與它對防火牆規則的影響">DNAT（目的位址轉換）&lt;/a> 改寫成容器位址之後，封包由主機轉送給容器，寫在主機入站規則（ufw 的入站規則、firewalld 的 zone）上的限制碰不到它，DOCKER-USER 是這條路上第一個可以寫規則的位置。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>FORWARD 鏈的跳轉順序是 DOCKER-USER、Docker 自己的 &lt;code>DOCKER-FORWARD&lt;/code>、然後才是 ufw 的 &lt;code>ufw-*&lt;/code> 鏈；&lt;code>DOCKER-FORWARD&lt;/code> 接受送往發佈連接埠的連線，所以寫在 ufw route 規則裡的限制輪不到這些封包，寫在 DOCKER-USER 的輪得到。封包到這條鏈時目的地已經是容器的位址與連接埠，限制發佈的連接埠要用 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/conntrack/" data-link-title="conntrack（連線追蹤）" data-link-desc="防火牆規則寫了 established、related 或 ctorigdstport、有狀態防火牆為什麼不必寫回程規則、改了規則卻像沒生效、或位址轉換之後要比對原本的位址時，查 Linux 核心記錄連線狀態的表與它在規則裡的用法">conntrack（連線追蹤）&lt;/a> 記下的原始目的連接埠比對：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">iptables -I DOCKER-USER -p tcp -m conntrack --ctstate DNAT --ctorigdstport &lt;span class="m">443&lt;/span> --ctdir ORIGINAL ! -s 173.245.48.0/20 -j DROP&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>三個 conntrack 條件各管一件事：&lt;code>--ctorigdstport 443&lt;/code> 比對位址轉換之前的連接埠；&lt;code>--ctdir ORIGINAL&lt;/code> 只比對發起方向的封包，容器回覆的封包不拿來比對來源；&lt;code>--ctstate DNAT&lt;/code> 只比對經過位址轉換的連線，也就是送往發佈連接埠的那些。少了最後一個時，容器自己往外連 443、同一台主機上容器之間連 80 的封包也進這條鏈、原始目的連接埠一樣是 443 或 80，一起被丟掉（2026-10-26 在 Ubuntu 24.04 與 Docker 29.9.2 上實測）。&lt;/p>
&lt;p>這條鏈只存在 iptables 後端：Docker 改用 nftables 後端時沒有它（Docker 的〈firewall-nftables〉文件）。它也只管 IPv4 那一份發佈：容器網路沒開 IPv6 時，IPv6 那一份由主機上的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/userland-proxy/" data-link-title="Userland Proxy（使用者空間的連接埠轉送程式）" data-link-desc="ss 列出 docker-proxy、rootlesskit 或 rootlessport 聽著容器發佈的連接埠、主機防火牆的入站規則對容器的連接埠有效、或容器的存取紀錄只看得到閘道位址而沒有真實來源時，查這類轉送程式接收連線的路徑與它換掉來源位址的代價">Userland Proxy（使用者空間的連接埠轉送程式）&lt;/a> 接收，不經過轉送路徑。主機上同時裝了 Docker 與以 root 執行的 Podman 時，Podman 的轉送封包也經過 DOCKER-USER（實測）。&lt;/p>
&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>&lt;code>iptables -S FORWARD&lt;/code> 的前兩行&lt;/strong>：&lt;code>-A FORWARD -j DOCKER-USER&lt;/code>、&lt;code>-A FORWARD -j DOCKER-FORWARD&lt;/code>。主機也裝了 Podman 時，最前面還有一條 &lt;code>-j NETAVARK_FORWARD&lt;/code>。&lt;/li>
&lt;li>&lt;strong>規則加了、從外面量過擋得住，過一陣子又失效&lt;/strong>：防火牆重新載入或重開機清掉了它，&lt;code>iptables -S DOCKER-USER&lt;/code> 只剩 &lt;code>-N DOCKER-USER&lt;/code> 這一行。&lt;/li>
&lt;li>&lt;strong>加了限制之後容器呼叫外部 API 逾時&lt;/strong>：規則只比對原始目的連接埠，沒有 &lt;code>--ctstate DNAT&lt;/code>，容器的出站連線也被丟掉。&lt;/li>
&lt;/ul>
&lt;h2 id="判讀方式">判讀方式&lt;/h2>
&lt;p>DOCKER-USER 的弱點在規則的存續，用 iptables 指令加的規則只存在執行中的核心裡，各種情形下的結果不同（2026-10-25 到 26 日在 Ubuntu 24.04 的 ufw 0.36.2、Oracle Linux 10.2 的 firewalld 2.4.3 上實測）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>情形&lt;/th>
 &lt;th>規則&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>任何主機重開機&lt;/td>
 &lt;td>不在&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>systemctl restart docker&lt;/code>&lt;/td>
 &lt;td>還在&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>ufw 的 &lt;code>ufw reload&lt;/code>，after.rules 沒有宣告這條鏈&lt;/td>
 &lt;td>還在&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>ufw 的 after.rules 宣告這條鏈&lt;/td>
 &lt;td>每次 reload 先清空、再寫入檔案裡的規則；用 iptables 指令另外加的不在&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>firewalld 的 reload，nftables 後端、沒有 direct 設定&lt;/td>
 &lt;td>還在&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>firewalld 的 reload，nftables 後端、有 direct rule 或 passthrough&lt;/td>
 &lt;td>不在（Oracle Cloud 的 Oracle Linux 平台映像自帶 passthrough）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>firewalld 的 reload，iptables 後端&lt;/td>
 &lt;td>不在&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>要讓規則留過重開機，ufw 主機把它寫進 &lt;code>/etc/ufw/after.rules&lt;/code>，同一台主機上的 DOCKER-USER 規則都要寫在那裡；firewalld 主機上改用掛在位址轉換之前的自建 nftables 表，或把限制放在雲端防火牆。direct 設定是主機當下的狀態，之後裝的軟體可能再加進來，用&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/configuration-management/" data-link-title="Configuration Management（組態管理）" data-link-desc="要讓好幾台主機的套件、設定檔與服務維持同一個狀態、讀到 Ansible 的 inventory 與 group_vars、或要判斷主機上的設定該交給 Ansible 這類工具還是 Terraform 這類 IaC 管理時">組態管理&lt;/a>工具定期 reload 的主機上，規則被清掉之後直連就重新開著而沒有告警。實測的指令、輸出與 after.rules 的整份清單寫法見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/hands-on/ufw-docker-walkthrough/" data-link-title="ufw 主機上 Docker 發佈連接埠的實測紀錄：ufw 規則的效果、DOCKER-USER 規則的存續與 after.rules、IPv6 的 docker-proxy、compose 內建 DNS 的出站路徑" data-link-desc="在 Ubuntu 的 ufw 主機上用 Docker 發佈連接埠、想只放行 Cloudflare 的位址、或想限制容器送出的 DNS 查詢時，查 ufw 規則對發佈連接埠的效果、DOCKER-USER 規則在 reload 與重開機之後還在不在、IPv6 那一份發佈由誰接收，以及 compose 容器的 DNS 封包從哪裡送出">ufw 主機上 Docker 發佈連接埠的實測紀錄：ufw 規則的效果、DOCKER-USER 規則的存續與 after.rules、IPv6 的 docker-proxy、compose 內建 DNS 的出站路徑&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/hands-on/origin-firewall-walkthrough/" data-link-title="源站防火牆只放行 Cloudflare 位址的實測紀錄：firewalld、Docker 發佈的連接埠、DOCKER-USER、nftables 表與出站 DNS" data-link-desc="要在 Linux 主機上設定只放行 Cloudflare 位址的防火牆、確認 Docker 發佈的連接埠有沒有繞過 firewalld、在 DOCKER-USER、StrictForwardPorts 與 nftables 表之間選一種限制容器的來源、或限制出站 DNS 時，查 Oracle Linux 10 上實際跑過的指令、輸出，以及規則在重新載入、dockerd 重啟與重開機之後還在不在">源站防火牆只放行 Cloudflare 位址的實測紀錄：firewalld、Docker 發佈的連接埠、DOCKER-USER、nftables 表與出站 DNS&lt;/a>；各種限制寫法的取捨見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/origin-host-firewall/" data-link-title="CDN 後面的源站主機防火牆：只放行 CDN 來源的位置與套用時機、Docker 發佈連接埠的封包路徑、出站 DNS 的限制與地理封鎖" data-link-desc="網站放在 CDN（例如 Cloudflare）後面時，源站主機的入站與出站限制：請求避開 CDN 直連源站的成因、來源限制放在雲端防火牆、主機防火牆或 CDN 端驗證的差別、只放行 CDN 位址的套用時機、Docker 發佈的連接埠繞過 ufw 與 firewalld 的路徑與各種限制寫法、CDN 位址送來的請求為什麼不等於 CDN 替自己的網域轉送、出站 DNS 限制的效果與漏洞、地理封鎖放在 CDN 邊緣的理由">CDN 後面的源站主機防火牆：只放行 CDN 來源的位置與套用時機、Docker 發佈連接埠的封包路徑、出站 DNS 的限制與地理封鎖&lt;/a> 的〈Docker 發佈的連接埠：封包路徑與限制的寫法〉。&lt;/p></description><content:encoded><![CDATA[<p>DOCKER-USER 是 Docker 在 iptables 的 filter 表裡建的一條自訂鏈：FORWARD 鏈的第一條規則無條件跳到它，Docker 自己不在裡面寫任何規則，啟動與重啟時也不清它的內容，讓使用者在 Docker 放行容器流量之前插入自己的限制。它管的是轉送路徑：Docker 發佈的連接埠經過 <a href="/blog/devops/infra/knowledge-cards/dnat/" data-link-title="DNAT（目的位址轉換）" data-link-desc="容器發佈的連接埠沒被主機防火牆擋下、port forwarding 的限制規則要寫在位址轉換之前還是之後、或要比對位址轉換之前的連接埠時，查 DNAT 改寫封包目的地的時間點與它對防火牆規則的影響">DNAT（目的位址轉換）</a> 改寫成容器位址之後，封包由主機轉送給容器，寫在主機入站規則（ufw 的入站規則、firewalld 的 zone）上的限制碰不到它，DOCKER-USER 是這條路上第一個可以寫規則的位置。</p>
<h2 id="概念位置">概念位置</h2>
<p>FORWARD 鏈的跳轉順序是 DOCKER-USER、Docker 自己的 <code>DOCKER-FORWARD</code>、然後才是 ufw 的 <code>ufw-*</code> 鏈；<code>DOCKER-FORWARD</code> 接受送往發佈連接埠的連線，所以寫在 ufw route 規則裡的限制輪不到這些封包，寫在 DOCKER-USER 的輪得到。封包到這條鏈時目的地已經是容器的位址與連接埠，限制發佈的連接埠要用 <a href="/blog/devops/infra/knowledge-cards/conntrack/" data-link-title="conntrack（連線追蹤）" data-link-desc="防火牆規則寫了 established、related 或 ctorigdstport、有狀態防火牆為什麼不必寫回程規則、改了規則卻像沒生效、或位址轉換之後要比對原本的位址時，查 Linux 核心記錄連線狀態的表與它在規則裡的用法">conntrack（連線追蹤）</a> 記下的原始目的連接埠比對：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">iptables -I DOCKER-USER -p tcp -m conntrack --ctstate DNAT --ctorigdstport <span class="m">443</span> --ctdir ORIGINAL ! -s 173.245.48.0/20 -j DROP</span></span></code></pre></div><p>三個 conntrack 條件各管一件事：<code>--ctorigdstport 443</code> 比對位址轉換之前的連接埠；<code>--ctdir ORIGINAL</code> 只比對發起方向的封包，容器回覆的封包不拿來比對來源；<code>--ctstate DNAT</code> 只比對經過位址轉換的連線，也就是送往發佈連接埠的那些。少了最後一個時，容器自己往外連 443、同一台主機上容器之間連 80 的封包也進這條鏈、原始目的連接埠一樣是 443 或 80，一起被丟掉（2026-10-26 在 Ubuntu 24.04 與 Docker 29.9.2 上實測）。</p>
<p>這條鏈只存在 iptables 後端：Docker 改用 nftables 後端時沒有它（Docker 的〈firewall-nftables〉文件）。它也只管 IPv4 那一份發佈：容器網路沒開 IPv6 時，IPv6 那一份由主機上的 <a href="/blog/devops/infra/knowledge-cards/userland-proxy/" data-link-title="Userland Proxy（使用者空間的連接埠轉送程式）" data-link-desc="ss 列出 docker-proxy、rootlesskit 或 rootlessport 聽著容器發佈的連接埠、主機防火牆的入站規則對容器的連接埠有效、或容器的存取紀錄只看得到閘道位址而沒有真實來源時，查這類轉送程式接收連線的路徑與它換掉來源位址的代價">Userland Proxy（使用者空間的連接埠轉送程式）</a> 接收，不經過轉送路徑。主機上同時裝了 Docker 與以 root 執行的 Podman 時，Podman 的轉送封包也經過 DOCKER-USER（實測）。</p>
<h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<ul>
<li><strong><code>iptables -S FORWARD</code> 的前兩行</strong>：<code>-A FORWARD -j DOCKER-USER</code>、<code>-A FORWARD -j DOCKER-FORWARD</code>。主機也裝了 Podman 時，最前面還有一條 <code>-j NETAVARK_FORWARD</code>。</li>
<li><strong>規則加了、從外面量過擋得住，過一陣子又失效</strong>：防火牆重新載入或重開機清掉了它，<code>iptables -S DOCKER-USER</code> 只剩 <code>-N DOCKER-USER</code> 這一行。</li>
<li><strong>加了限制之後容器呼叫外部 API 逾時</strong>：規則只比對原始目的連接埠，沒有 <code>--ctstate DNAT</code>，容器的出站連線也被丟掉。</li>
</ul>
<h2 id="判讀方式">判讀方式</h2>
<p>DOCKER-USER 的弱點在規則的存續，用 iptables 指令加的規則只存在執行中的核心裡，各種情形下的結果不同（2026-10-25 到 26 日在 Ubuntu 24.04 的 ufw 0.36.2、Oracle Linux 10.2 的 firewalld 2.4.3 上實測）：</p>
<table>
  <thead>
      <tr>
          <th>情形</th>
          <th>規則</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>任何主機重開機</td>
          <td>不在</td>
      </tr>
      <tr>
          <td><code>systemctl restart docker</code></td>
          <td>還在</td>
      </tr>
      <tr>
          <td>ufw 的 <code>ufw reload</code>，after.rules 沒有宣告這條鏈</td>
          <td>還在</td>
      </tr>
      <tr>
          <td>ufw 的 after.rules 宣告這條鏈</td>
          <td>每次 reload 先清空、再寫入檔案裡的規則；用 iptables 指令另外加的不在</td>
      </tr>
      <tr>
          <td>firewalld 的 reload，nftables 後端、沒有 direct 設定</td>
          <td>還在</td>
      </tr>
      <tr>
          <td>firewalld 的 reload，nftables 後端、有 direct rule 或 passthrough</td>
          <td>不在（Oracle Cloud 的 Oracle Linux 平台映像自帶 passthrough）</td>
      </tr>
      <tr>
          <td>firewalld 的 reload，iptables 後端</td>
          <td>不在</td>
      </tr>
  </tbody>
</table>
<p>要讓規則留過重開機，ufw 主機把它寫進 <code>/etc/ufw/after.rules</code>，同一台主機上的 DOCKER-USER 規則都要寫在那裡；firewalld 主機上改用掛在位址轉換之前的自建 nftables 表，或把限制放在雲端防火牆。direct 設定是主機當下的狀態，之後裝的軟體可能再加進來，用<a href="/blog/devops/infra/knowledge-cards/configuration-management/" data-link-title="Configuration Management（組態管理）" data-link-desc="要讓好幾台主機的套件、設定檔與服務維持同一個狀態、讀到 Ansible 的 inventory 與 group_vars、或要判斷主機上的設定該交給 Ansible 這類工具還是 Terraform 這類 IaC 管理時">組態管理</a>工具定期 reload 的主機上，規則被清掉之後直連就重新開著而沒有告警。實測的指令、輸出與 after.rules 的整份清單寫法見 <a href="/blog/devops/infra/03-network-foundation/hands-on/ufw-docker-walkthrough/" data-link-title="ufw 主機上 Docker 發佈連接埠的實測紀錄：ufw 規則的效果、DOCKER-USER 規則的存續與 after.rules、IPv6 的 docker-proxy、compose 內建 DNS 的出站路徑" data-link-desc="在 Ubuntu 的 ufw 主機上用 Docker 發佈連接埠、想只放行 Cloudflare 的位址、或想限制容器送出的 DNS 查詢時，查 ufw 規則對發佈連接埠的效果、DOCKER-USER 規則在 reload 與重開機之後還在不在、IPv6 那一份發佈由誰接收，以及 compose 容器的 DNS 封包從哪裡送出">ufw 主機上 Docker 發佈連接埠的實測紀錄：ufw 規則的效果、DOCKER-USER 規則的存續與 after.rules、IPv6 的 docker-proxy、compose 內建 DNS 的出站路徑</a> 與 <a href="/blog/devops/infra/03-network-foundation/hands-on/origin-firewall-walkthrough/" data-link-title="源站防火牆只放行 Cloudflare 位址的實測紀錄：firewalld、Docker 發佈的連接埠、DOCKER-USER、nftables 表與出站 DNS" data-link-desc="要在 Linux 主機上設定只放行 Cloudflare 位址的防火牆、確認 Docker 發佈的連接埠有沒有繞過 firewalld、在 DOCKER-USER、StrictForwardPorts 與 nftables 表之間選一種限制容器的來源、或限制出站 DNS 時，查 Oracle Linux 10 上實際跑過的指令、輸出，以及規則在重新載入、dockerd 重啟與重開機之後還在不在">源站防火牆只放行 Cloudflare 位址的實測紀錄：firewalld、Docker 發佈的連接埠、DOCKER-USER、nftables 表與出站 DNS</a>；各種限制寫法的取捨見 <a href="/blog/devops/infra/03-network-foundation/origin-host-firewall/" data-link-title="CDN 後面的源站主機防火牆：只放行 CDN 來源的位置與套用時機、Docker 發佈連接埠的封包路徑、出站 DNS 的限制與地理封鎖" data-link-desc="網站放在 CDN（例如 Cloudflare）後面時，源站主機的入站與出站限制：請求避開 CDN 直連源站的成因、來源限制放在雲端防火牆、主機防火牆或 CDN 端驗證的差別、只放行 CDN 位址的套用時機、Docker 發佈的連接埠繞過 ufw 與 firewalld 的路徑與各種限制寫法、CDN 位址送來的請求為什麼不等於 CDN 替自己的網域轉送、出站 DNS 限制的效果與漏洞、地理封鎖放在 CDN 邊緣的理由">CDN 後面的源站主機防火牆：只放行 CDN 來源的位置與套用時機、Docker 發佈連接埠的封包路徑、出站 DNS 的限制與地理封鎖</a> 的〈Docker 發佈的連接埠：封包路徑與限制的寫法〉。</p>
]]></content:encoded></item></channel></rss>