<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>技術 on 亂筆</title>
        <link>https://blog.l3zc.com/zh-hant-tw/categories/tech/</link>
        <description>Recent content in 技術 on 亂筆</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh-hant-tw</language><atom:link href="https://blog.l3zc.com/zh-hant-tw/categories/tech/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>關於 DNS 洩露及其相關誤解的說明</title>
            <link>https://blog.l3zc.com/zh-hant-tw/2026/05/dns-leak-misunderstanding/</link>
            <pubDate>Sun, 17 May 2026 16:46:00 +0000</pubDate>
            <guid>https://blog.l3zc.com/zh-hant-tw/2026/05/dns-leak-misunderstanding/</guid>
            <description>&lt;p&gt;點進這篇文章的人想必基本都對 DNS 洩露這一概念有所耳聞了，其中也不乏談 DNS 洩露色變者，由於評論區和 Issue 裡提出相關問題的人已經有數位，鄙人這次寫一篇說明，儘可能將這個問題講清楚，澄清一些大家對這個概念可能的誤解，也為各位編寫自己的代理配置提供一個參考。&lt;/p&gt;&#xA;&lt;p&gt;這個概念已經被一些 VPN 廠商和一大堆 YouTuber 講爛了，基本的意思是：當你在代理時，電腦卻通過本地網絡向本地 DNS 請求你正在訪問的網址，使得運營商可以依據你的 DNS 請求記錄得知你的瀏覽記錄。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart LR&#xA;    U[用戶設備&lt;br/&gt;瀏覽器 / App] --&gt; VPN[VPN 客戶端&lt;br/&gt;加密隧道]&#xA;&#xA;    VPN --&gt;|正常情況：DNS 請求走 VPN| VPNDNS[VPN 提供的 DNS 服務器]&#xA;    VPNDNS --&gt; NET[目標網站 / 互聯網]&#xA;&#xA;    U -.-&gt;|異常情況：DNS 洩露| ISP[本地 ISP DNS 服務器]&#xA;    ISP -.-&gt; LOG[ISP / 第三方可記錄&lt;br/&gt;訪問過的域名]&#xA;&#xA;    NET --&gt; SITE[example.com]&#xA;&#xA;    subgraph Normal[正常 DNS 解析路徑]&#xA;        VPN&#xA;        VPNDNS&#xA;        NET&#xA;    end&#xA;&#xA;    subgraph Leak[DNS 洩露路徑]&#xA;        ISP&#xA;        LOG&#xA;    end&#xA;&#xA;    classDef safe fill:#e8f7ee,stroke:#2e7d32,stroke-width:2px,color:#1b5e20;&#xA;    classDef danger fill:#fdecea,stroke:#c62828,stroke-width:2px,color:#7f0000;&#xA;    classDef user fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#0d47a1;&#xA;&#xA;    class U user;&#xA;    class VPN,VPNDNS,NET,SITE safe;&#xA;    class ISP,LOG danger;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;搞清楚需求&#34;&gt;搞清楚需求&#xA;&lt;/h2&gt;&lt;p&gt;首先需要想明白的是，我們煞費苦心地防止 DNS 洩露究竟是為了什麼——大家的第一反應可能都是：「那還用說！肯定是為了不讓審查者看到我們通過代理訪問了哪些網站啊！」&lt;/p&gt;&#xA;&lt;p&gt;其實這個說法並不完全準確，具體還得看情況。不妨想一想：讓審查者知道你在刷抖音、聊微信會有什麼實質性的危害嗎？恐怕沒有。但如果讓審查者發現你在訪問維基百科、瀏覽 X（推特）或者其他帶有明確政治傾向的敏感內容，那問題就有點大了。防止 DNS 洩露的目的，從來不是為了讓你在所謂的「DNS 洩露測試」網站上拿到一個全綠的滿分，也不是為了隱藏你使用國內常用服務的事實，而是為了不讓審查者知道你在訪問那些被封禁、被視為敏感的網站和服務。&lt;/p&gt;&#xA;&lt;p&gt;對於生活在牆內的我們來說，區分什麼是敏感內容其實非常簡單：直接參考 GFWList&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; 即可——能讓 GFW 專門將其重點關照的，自然就是敏感內容。&lt;/p&gt;&#xA;&lt;p&gt;那些 DNS 洩露測試的網站，測試用的隨機域名甚至連 GFWList 的邊都沾不上，本身就毫無敏感性可言。對於一些經過優化的正常代理配置來說，這類域名連看都不想多看一眼，通常會直接被略過並扔給最後的兜底規則（比如 &lt;code&gt;MATCH, DIRECT&lt;/code&gt;）去處理，這個過程自然而然地觸發了本地 DNS 解析。於是，測試網站便如獲至寶般地亮起紅燈，警告你「存在 DNS 洩露」。但明白機制的我們自然清楚，很多時候這種所謂的洩露完全是無關痛癢的，只要配置得當，你真正需要保護的敏感訪問記錄，早就匹配到前面的分流規則被安全地送進加密隧道了。&lt;/p&gt;&#xA;&lt;p&gt;更有甚者，諸如 Surfshark、ExpressVPN 這類海外商業 VPN 廠商，以及一眾跟著恰飯的無良 YouTuber，成天拿這種毫無實際意義的測試結果製造隱私焦慮。可笑的是，這些傳統的商業 VPN 客戶端往往連最基礎的域名分流規則功能都不具備，全靠粗暴的一把梭將所有網絡請求強行塞進虛擬網卡。而更諷刺的是，即便是這種簡單粗暴的全局代理，他們過去還曾因為自身客戶端軟件的缺陷，真真切切地導致過 DNS 洩露問題。所以，與其聽信這些販賣焦慮的恰飯營銷，不如自己弄懂代理軟件的運作原理。&lt;/p&gt;&#xA;&lt;h2 id=&#34;出現洩露的環節&#34;&gt;出現洩露的環節&#xA;&lt;/h2&gt;&lt;p&gt;其實現在主流的代理軟件都比較成熟，配置得當完全不需要擔心 DNS 洩露的問題。問題出就出在很多配置根本就不得當。接下來我將會簡要地解釋代理軟件在系統代理模式和透明代理（虛擬網卡）模式下可能出現 DNS 洩露的環節。&lt;/p&gt;&#xA;&lt;h3 id=&#34;代理軟件本身&#34;&gt;代理軟件本身&#xA;&lt;/h3&gt;&lt;p&gt;在使用系統代理的情況下（假設代理地址是&lt;code&gt;socks5://127.0.0.1:7890&lt;/code&gt;&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;）用瀏覽器訪問&lt;code&gt;https://www.example.com&lt;/code&gt;，會經歷如下的請求過程：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;第一步：瀏覽器會把網絡請求打包發送給&lt;code&gt;socks5://127.0.0.1:7890&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;第二步：代理軟件收到請求後根據配置的分流規則匹配節點&lt;/li&gt;&#xA;&lt;li&gt;第三步：代理軟件根據匹配結果向節點轉發整個網絡請求&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;sequenceDiagram&#xA;    participant B as 瀏覽器&#xA;    participant P as 代理軟件&lt;br/&gt;(127.0.0.1:7890)&#xA;    participant R as 分流規則引擎&#xA;    participant N as 遠端代理節點&#xA;&#xA;    B-&gt;&gt;P: ① 將請求打包發送&lt;br/&gt;(攜帶域名, 不做 DNS 解析)&#xA;    Note over B,P: 瀏覽器不發起 DNS 查詢&lt;br/&gt;不會洩露&#xA;    P-&gt;&gt;R: ② 根據域名等信息&lt;br/&gt;匹配分流規則&#xA;    Note over P,R: 規則匹配過程&lt;br/&gt;可能觸發 DNS 查詢 ⚠️&#xA;    R--&gt;&gt;P: 返回匹配結果&lt;br/&gt;(PROXY / DIRECT / REJECT)&#xA;    P-&gt;&gt;N: ③ 加密轉發請求至節點&#xA;    Note over P,N: 加密隧道傳輸&lt;br/&gt;不會洩露&#xA;    N--&gt;&gt;P: 返回響應&#xA;    P--&gt;&gt;B: 返回響應&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在整個發起請求的過程中，瀏覽器將網絡請求打包交給代理，不會向本地 DNS 發出請求，這一環節不會產生 DNS 洩露；代理軟件將網絡請求轉發給服務器，整個過程加密，也不會產生 DNS 洩露；真正產生 DNS 洩露的環節，是根據分流規則匹配的過程。&lt;/p&gt;&#xA;&lt;p&gt;向本地 DNS 發起域名查詢這件事情本身並不一定是壞事，但配置不當的分流規則會讓代理軟件在拿到域名後過早地向本地 DNS 發起查詢，導致洩露不應洩露的隱私信息。假設在極端情況下，蓋世太保從運營商調取所有人的 DNS 查詢記錄，凡是查詢過域名包含 GFW 封鎖列表中的項目者都拉出去槍斃，在這種情況下，為了保全自身，我們需要恰當地配置分流規則。&lt;/p&gt;&#xA;&lt;p&gt;以使用廣泛的 Mihomo 內核為例，在作為客戶端開啟系統代理模式、使用 Chrome 進行到前文所述的第二步時，內核已知的信息有：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;主機&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;：&lt;code&gt;example.com&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;目標端口：443&lt;/li&gt;&#xA;&lt;li&gt;網絡協議：TCP&lt;/li&gt;&#xA;&lt;li&gt;發起請求的進程的名稱和具體路徑：&lt;code&gt;C:\Application\chrome.exe&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;入站 IP：&lt;code&gt;127.0.0.1&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;入站名稱：&lt;code&gt;DEFAULT-MIXED&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;入站端口：隨機的高位端口&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;內核不會進行多餘的 DNS 請求，根據已知的信息，內核可以在不發起 DNS 解析的情況下，匹配所有基於域名、端口、進程名稱以及網絡協議匹配的路由規則：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;rules:&#xA;  # 基於域名的匹配規則&#xA;  - DOMAIN,ad.com,REJECT&#xA;  - DOMAIN-SUFFIX,google.com,auto&#xA;  - DOMAIN-KEYWORD,google,auto&#xA;  - DOMAIN-WILDCARD,*.google.com,auto&#xA;  - DOMAIN-REGEX,^abc.*com,PROXY&#xA;  - GEOSITE,youtube,PROXY&#xA;  # 基於端口的匹配規則&#xA;  - DST-PORT,80,DIRECT&#xA;  - SRC-PORT,7777,DIRECT&#xA;  # 基於進程名稱的匹配規則&#xA;  - PROCESS-PATH,/usr/bin/wget,PROXY&#xA;  - PROCESS-PATH,C:\Application\chrome.exe,PROXY&#xA;  - PROCESS-NAME,curl,PROXY&#xA;  - PROCESS-NAME,chrome.exe,PROXY&#xA;  # 基於網絡協議的匹配規則&#xA;  - NETWORK,UDP,REJECT&#xA;  # 不依賴任何信息的匹配規則&#xA;  - MATCH,DIRECT&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;因此，上述這些種類的規則不會產生 DNS 洩露，因為內核匹配它們的時候根本沒必要發起 DNS 解析。&lt;/p&gt;&#xA;&lt;p&gt;對於剩下種類的規則，需要在第二步獲知目標 IP 信息才能讓匹配進行下去：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;rules:&#xA;  - GEOIP,cn,DIRECT&#xA;  - IP-CIDR,127.0.0.0/8,DIRECT&#xA;  - IP-SUFFIX,8.8.8.8/24,PROXY&#xA;  - IP-ASN,13335,DIRECT&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在默認情況下，為了確保匹配的準確性，進行到第二步時，內核不知道目標 IP，只知道目標主機地址&lt;code&gt;www.example.com&lt;/code&gt;，此時內核不得不對&lt;code&gt;www.example.com&lt;/code&gt;的 IP 地址進行查詢，這也就造成了 DNS 洩露。如果在規則的末尾加上&lt;code&gt;no-resolve&lt;/code&gt;&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;，則匹配到基於 IP 的規則時會直接跳過，這就使得&lt;code&gt;no-resolve&lt;/code&gt;很適合用來分流 Telegram 等軟件的裸 IP 請求。&lt;/p&gt;&#xA;&lt;h3 id=&#34;透明代理的問題tun-模式&#34;&gt;透明代理的問題（TUN 模式&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;）&#xA;&lt;/h3&gt;&lt;p&gt;透明代理顧名思義即被代理的系統/軟件感知不到自己被代理了的代理方式。這種方式非常省事，TUN 模式一開，所有軟件通通都能用，無須繁瑣的逐一配置不遵循系統代理的軟件。代價就是這種省事本身——目標系統/軟件都不知道自己被代理了，他們在正常網絡上該有的行為都會照常進行，其中自然也包括 DNS 解析，可是目標系統/軟件並不知道自己被代理了，他們想要和遠端建立連接，總得要先拿到一個 IP 才行吧。為了解決這個問題，以 Mihomo 為例，有兩種 DNS 劫持模式，&lt;code&gt;redir-host&lt;/code&gt;和&lt;code&gt;fake-ip&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;redir-host 模式：&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;    A1[目標軟件發起 DNS 查詢] --&gt; A2[代理軟件向上遊 DNS&lt;br/&gt;查詢真實 IP]&#xA;    A2 --&gt; A3[上游 DNS 返回真實 IP]&#xA;    A3 --&gt; A4[將真實 IP 返回給目標軟件]&#xA;    A4 --&gt; A5[目標軟件用真實 IP 建立連接]&#xA;    A5 --&gt; A6[代理軟件攔截並分流]&#xA;&#xA;    A2 -.- LEAK[&#34;⚠️ 真實 DNS 請求洩露到外部網絡&#34;]&#xA;&#xA;    classDef danger fill:#fdecea,stroke:#c62828,stroke-width:2px,color:#7f0000;&#xA;    class LEAK danger;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;fake-ip 模式：&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;    B1[目標軟件發起 DNS 查詢] --&gt; B2[代理軟件立即返回假 IP&lt;br/&gt;如 198.18.0.x]&#xA;    B2 --&gt; B3[目標軟件用假 IP 建立連接]&#xA;    B3 --&gt; B4[代理軟件攔截, 查映射表&lt;br/&gt;還原真實域名]&#xA;    B4 --&gt; B5[通過加密隧道轉發請求]&#xA;&#xA;    classDef safe fill:#e8f7ee,stroke:#2e7d32,stroke-width:2px,color:#1b5e20;&#xA;    class B2,B4,B5 safe;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其中&lt;code&gt;redir-host&lt;/code&gt;比較老實，既然目標系統/軟件發起了 DNS 查詢，那就老老實實通過上游 DNS 去查，把查詢到的真實的 IP 返回給目標系統/軟件。由於這種模式下系統必定會發生一次真實的 DNS 解析請求尋址過程，存在著先天的結構性缺陷，不可避免地會造成 DNS 洩露。原來的 Clash 核心在後續的版本更新中乾脆直接砍掉了 &lt;code&gt;redir-host&lt;/code&gt; 模式。&lt;/p&gt;&#xA;&lt;p&gt;不過，繼承了 Clash Meta 核心的 Mihomo 內核選擇了將其保留下來，並且加入了&lt;code&gt;sniffer&lt;/code&gt;&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;對其進行增強。在&lt;code&gt;redir-host&lt;/code&gt;模式下，&lt;code&gt;sniffer&lt;/code&gt;依靠嗅探數據包中的特徵（比如從 TLS 的 SNI 字段&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;）嘗試將被解析成裸 IP 的流量恢復成連接域名，再用這個域名走一遍分流規則的匹配，補齊了&lt;code&gt;redir-host&lt;/code&gt;模式在分流上的諸多短板。但需要特別注意：&lt;code&gt;sniffer&lt;/code&gt; 解決的是分流準確性的問題，&lt;strong&gt;並不能消除 DNS 洩露本身&lt;/strong&gt;——DNS 查詢請求在 sniffer 介入之前就已經發出去了，運營商該記錄的早就記錄了。&lt;/p&gt;&#xA;&lt;p&gt;但這裡需要提醒：如果你在透明代理環境下仍然堅持使用&lt;code&gt;redir-host&lt;/code&gt;模式，就必須在配置一套無汙染的 DNS，具體的例子可以參考我的&lt;a class=&#34;link&#34; href=&#34;https://blog.l3zc.com/2025/02/what-i-have-done-on-my-dns/&#34; &gt;另一篇文章&lt;/a&gt;，否則你的 DNS 訪問記錄依然會洩露。&lt;/p&gt;&#xA;&lt;p&gt;相對而言，&lt;code&gt;fake-ip&lt;/code&gt;模式則採用了截然不同的解決思路，不僅完美規避了透明代理環境下的 DNS 洩露問題，甚至還能順帶提升瀏覽器的網頁響應速度。&lt;/p&gt;&#xA;&lt;h2 id=&#34;如何規避-dns-洩露&#34;&gt;如何規避 DNS 洩露&#xA;&lt;/h2&gt;&lt;h3 id=&#34;優化餵給代理軟件的分流規則&#34;&gt;優化餵給代理軟件的分流規則&#xA;&lt;/h3&gt;&lt;p&gt;前面已經提到過，不當的分流規則會讓代理軟件過早地觸發解析。一個典型的反面例子如下：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;rules:&#xA;  - GEOIP,cn,DIRECT&#xA;  - GEOSITE,telegram,Telegram&#xA;  - GEOIP,telegram,Telegram&#xA;  - MATCH,Proxy&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;問題出在第一條 &lt;code&gt;GEOIP,cn,DIRECT&lt;/code&gt;。當設備請求 &lt;code&gt;telegram.org&lt;/code&gt; 這個域名時，內核從上往下匹配，第一條就是個 IP 規則。可域名不是 IP，要判斷它到底落不落在中國大陸的網段裡，內核只能先把 &lt;code&gt;telegram.org&lt;/code&gt; 拿去解析一遍。這一解析，DNS 查詢就發出去了——哪怕下面第二條 &lt;code&gt;GEOSITE,telegram&lt;/code&gt; 本來能直接命中，根本用不著知道 IP。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;    Start[請求 telegram.org] --&gt; Rule1{第 1 條&lt;br/&gt;GEOIP,cn?}&#xA;    Rule1 --&gt; Leak[⚠️ IP 規則遇上域名&lt;br/&gt;必須先解析 telegram.org&lt;br/&gt;DNS 查詢就此洩露]&#xA;    Leak --&gt; Judge{解析出的 IP&lt;br/&gt;在中國大陸?}&#xA;    Judge -- 否 --&gt; Rule2{第 2 條&lt;br/&gt;GEOSITE,telegram?}&#xA;    Rule2 -- 是 --&gt; ProxyTG[走 Telegram 代理&lt;br/&gt;但 DNS 已經洩露了]&#xA;&#xA;    classDef danger fill:#fdecea,stroke:#c62828,stroke-width:2px,color:#7f0000;&#xA;    class Leak danger;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;解析請求一旦發出，運營商那頭就記下了。後面命中哪條規則或者走不走代理都不重要，反正運營商已經知道你在訪問 Telegram。&lt;/p&gt;&#xA;&lt;p&gt;規則匹配自上而下順序進行，內核每收到一個請求就一條一條往下比，直到命中為止。換句話說，一個域名只要被前面的規則匹配到，後面的規則，第二條、第三條、哪怕第一千萬條，都不會再跑。&lt;/p&gt;&#xA;&lt;p&gt;正因如此，寫規則時要遵循&lt;strong&gt;域名規則在前，IP 規則在後&lt;/strong&gt;的原則。就算是針對某個服務的專用規則集，也要給裡面靠前的 IP 規則加上 &lt;code&gt;no-resolve&lt;/code&gt; 標記，告訴內核這條 IP 規則匹配不上就跳過，別去解析。一個典型的例子：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;DOMAIN-SUFFIX,e-hentai.org&#xA;DOMAIN-SUFFIX,ehgt.org&#xA;DOMAIN-SUFFIX,ehwiki.org&#xA;DOMAIN-SUFFIX,exhentai.org&#xA;DOMAIN-SUFFIX,hath.network&#xA;DOMAIN-SUFFIX,hentaiverse.org&#xA;IP-CIDR,178.175.128.0/21,no-resolve&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;用 GeoSite 和 GeoIP 數據庫時同理，靠前的 IP 規則都要帶上 &lt;code&gt;no-resolve&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;rules:&#xA;  - GEOSITE,telegram,Telegram&#xA;  - GEOIP,telegram,Telegram,no-resolve&#xA;  - GEOSITE,gfw,Proxy&#xA;  - GEOIP,cn,DIRECT&#xA;  - Match,Proxy&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;把這套規則和前面的反面例子對照著看，差別就在 DNS 解析觸發的時機上：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;    Start[請求 telegram.org] --&gt; Rule1{第 1 條&lt;br/&gt;GEOSITE,telegram?}&#xA;    Rule1 -- 是 --&gt; ProxyTG[走 Telegram 代理&lt;br/&gt;✅ 全程未解析]&#xA;    Rule1 -- 否 --&gt; Rule2{第 2 條&lt;br/&gt;GEOIP,telegram,no-resolve?}&#xA;&#xA;    Rule2 -- 是&lt;br/&gt;僅匹配裸 IP 請求 --&gt; ProxyTG&#xA;    Rule2 -- 否 --&gt; Rule3{第 3 條&lt;br/&gt;GEOSITE,gfw?}&#xA;&#xA;    Rule3 -- 是 --&gt; ProxyGFW[走 Proxy 代理&lt;br/&gt;✅ 全程未解析]&#xA;    Rule3 -- 否 --&gt; NeedIP[到這一步才需要 IP&lt;br/&gt;觸發本地 DNS 解析]&#xA;&#xA;    NeedIP --&gt; Rule4{第 4 條&lt;br/&gt;GEOIP,cn?}&#xA;    Rule4 -- 是 --&gt; Direct[DIRECT 直連]&#xA;    Rule4 -- 否 --&gt; ProxyOther[Match,Proxy&lt;br/&gt;走 Proxy 代理]&#xA;&#xA;    classDef safe fill:#e8f7ee,stroke:#2e7d32,stroke-width:2px,color:#1b5e20;&#xA;    classDef warn fill:#fff3e0,stroke:#ef6c00,stroke-width:2px,color:#8a4b00;&#xA;    class ProxyTG,ProxyGFW safe;&#xA;    class NeedIP warn;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;同樣是請求 &lt;code&gt;telegram.org&lt;/code&gt;，這回第一條就是 &lt;code&gt;GEOSITE,telegram&lt;/code&gt;，域名直接命中，請求甩給代理，全程沒碰 DNS。訪問其他掛在 GFW 列表上的網站也一樣，會先撞上 &lt;code&gt;GEOSITE,gfw,Proxy&lt;/code&gt;，趕在那條非解析不可的 &lt;code&gt;GEOIP,cn,DIRECT&lt;/code&gt; 之前就把請求送進了節點。該走代理的早早走了代理，靠後那些基於 IP 的規則壓根沒輪到執行，DNS 也就沒機會洩露出去。&lt;/p&gt;&#xA;&lt;h3 id=&#34;用-fake-ip-規避透明代理問題&#34;&gt;用 Fake-IP 規避透明代理問題&#xA;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;fake-ip&lt;/code&gt; 模式則解決了透明代理下的 DNS 洩露問題，還能順帶提升瀏覽器的網頁響應速度。&lt;/p&gt;&#xA;&lt;p&gt;在&lt;code&gt;fake-ip&lt;/code&gt;模式下進行透明代理，當目標軟件發起 DNS 查詢時，代理軟件並不會傻乎乎地去本地或遠端上游查詢真實 IP，而是直接在本地瞬間返回一個假的內網保留 IP（例如&lt;code&gt;198.18.0.2&lt;/code&gt;&lt;sup id=&#34;fnref:8&#34;&gt;&lt;a href=&#34;#fn:8&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;8&lt;/a&gt;&lt;/sup&gt;）。目標軟件拿到這個假 IP 後信以為真，緊接著就會拿著這個假 IP 嘗試建立連接。&lt;/p&gt;&#xA;&lt;p&gt;而在代理軟件這裡，它只需要攔截所有發往自己簽發過的這些假 IP 的請求即可。由於代理軟件內部維護了一張詳細的「假 IP 對應真實域名」的映射表，所以即便目標軟件發來的是往假 IP 發送的包，代理軟件依然心知肚明你其實想訪問的是什麼域名。於是，代理軟件直接提取出原始域名，將網絡請求順著加密代理隧道發往遠端節點去處理。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;sequenceDiagram&#xA;    participant App as 目標軟件&lt;br/&gt;(瀏覽器等)&#xA;    participant TUN as TUN 虛擬網卡&#xA;    participant Proxy as 代理軟件&lt;br/&gt;(Mihomo)&#xA;    participant Node as 遠端代理節點&#xA;    participant Web as 目標網站&#xA;&#xA;    App-&gt;&gt;TUN: DNS 查詢: google.com 的 IP 是?&#xA;    TUN-&gt;&gt;Proxy: 轉發 DNS 查詢&#xA;    Note over Proxy: 不向外部發起任何 DNS 請求!&lt;br/&gt;從 198.18.0.0/15 池中分配假 IP&lt;br/&gt;記錄映射: 198.18.0.5 → google.com&#xA;    Proxy--&gt;&gt;TUN: 返回假 IP: 198.18.0.5&#xA;    TUN--&gt;&gt;App: DNS 響應: 198.18.0.5&#xA;&#xA;    App-&gt;&gt;TUN: 連接 198.18.0.5:443 (HTTPS)&#xA;    TUN-&gt;&gt;Proxy: 轉發連接請求&#xA;    Note over Proxy: 查映射表:&lt;br/&gt;198.18.0.5 → google.com&lt;br/&gt;匹配分流規則 → PROXY&#xA;    Proxy-&gt;&gt;Node: 加密轉發: 請連接 google.com:443&#xA;    Node-&gt;&gt;Web: 建立真實連接&#xA;    Web--&gt;&gt;Node: 返回網頁內容&#xA;    Node--&gt;&gt;Proxy: 加密回傳&#xA;    Proxy--&gt;&gt;TUN: 返回內容&#xA;    TUN--&gt;&gt;App: 收到響應&#xA;&#xA;    Note over App,Web: 全程無真實 DNS 請求洩露到外部網絡 ✓&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;這一機制解決了&lt;code&gt;redir-host&lt;/code&gt;模式下透明代理必然會向外部網絡暴露域名查詢記錄的結構性缺陷。但必須指出的是，&lt;code&gt;fake-ip&lt;/code&gt;僅僅是繞過了目標軟件發起 DNS 查詢時的洩露風險。正如前文所述，如果分流規則配置不當，代理內核自身依然會主動向上游發起真實的 DNS 解析，從而導致洩露。在分流規則配置妥當的前提下，&lt;code&gt;fake-ip&lt;/code&gt;模式不僅能讓審查者無從窺探你的真實訪問目標，還能因為免去了本地設備等待 DNS 真實響應的時間，順帶大幅度降低網頁訪問的首字節加載延遲（TTFB&lt;sup id=&#34;fnref:9&#34;&gt;&lt;a href=&#34;#fn:9&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;9&lt;/a&gt;&lt;/sup&gt;），可謂一石二鳥。&lt;/p&gt;&#xA;&lt;h2 id=&#34;太長不想讀小結&#34;&gt;太長不想讀（小結）&#xA;&lt;/h2&gt;&lt;p&gt;DNS 洩露本身並不可怕，可怕的是不明就裡地被焦慮營銷牽著鼻子走。理解了機制之後，應對思路其實很清晰：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;系統代理模式&lt;/strong&gt;：遵循「域名規則在前，IP 規則在後」的原則，為所有 IP 類規則加上 &lt;code&gt;no-resolve&lt;/code&gt;，敏感域名在觸發 DNS 解析之前就已經被分流走了。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;TUN 透明代理模式&lt;/strong&gt;：優先選用 &lt;code&gt;fake-ip&lt;/code&gt;，從根源上杜絕 DNS 查詢外洩，同時還能獲得更低的 TTFB。如果堅持使用 &lt;code&gt;redir-host&lt;/code&gt;，必須搭配無汙染的上游 DNS，且要清楚 sniffer 只能改善分流準確性，無法阻止 DNS 請求本身的洩露。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;不要迷信洩露測試網站&lt;/strong&gt;：測試用的隨機域名根本不在 GFW 封鎖列表內，觸發本地解析是正常且無害的行為，綠燈與否不是衡量配置質量的標準。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;被中國大陸防火長城（Great Firewall, GFW）封鎖的域名列表。&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;SOCKS5 是一種網絡代理協議（RFC 1928），工作在會話層，支持 TCP 和 UDP 轉發，且允許客戶端將域名直接傳遞給代理服務器解析，而非在本地解析後再連接——這正是它在防止 DNS 洩露方面優於普通 HTTP 代理（非 CONNECT 隧道模式）的關鍵特性。&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;可以理解為域名。&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:4&#34;&gt;&#xA;&lt;p&gt;&lt;code&gt;no-resolve&lt;/code&gt; 是附加在 IP 類規則末尾的標記，作用是告訴內核：當流量的目標是域名而非 IP 時，跳過此規則，不要為了匹配它而主動發起 DNS 解析。但如果流量本身就是直接連接 IP（如 Telegram 客戶端直連服務器 IP），則該規則仍然會正常參與匹配。&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:5&#34;&gt;&#xA;&lt;p&gt;也就是我們所說的虛擬網卡模式&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:6&#34;&gt;&#xA;&lt;p&gt;流量嗅探（Sniffer）是指代理軟件通過檢查數據包的協議特徵來還原目標域名的技術。例如，HTTPS 連接在握手階段會以明文發送目標域名（即 SNI），HTTP 請求頭中也包含 Host 字段，代理軟件可以從這些特徵中提取出真實的目標域名。&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:7&#34;&gt;&#xA;&lt;p&gt;Server Name Indication，TLS 協議的擴展字段。由於同一 IP 上可能託管多個 HTTPS 站點，客戶端在 TLS 握手的 ClientHello 階段需要以明文告知服務器自己要訪問哪個域名，以便服務器返回正確的證書。這也是為什麼即使使用了 HTTPS，中間人仍然能看到你訪問的域名（但看不到具體路徑和內容）。&amp;#160;&lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:8&#34;&gt;&#xA;&lt;p&gt;Mihomo 默認使用 &lt;code&gt;198.18.0.0/15&lt;/code&gt; 地址段分配假 IP。該地址段由 IANA 在 RFC 2544 中保留，專門用於網絡設備基準測試，不會出現在正常的互聯網路由表中，因此不會與任何真實的公網地址衝突。整個 /15 段可提供約 131,072 個地址供映射使用。&amp;#160;&lt;a href=&#34;#fnref:8&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:9&#34;&gt;&#xA;&lt;p&gt;Time To First Byte，即從客戶端發出請求到收到服務器響應第一個字節所經過的時間。在傳統模式下，瀏覽器需要先等待 DNS 解析完成（通常耗時 20-120ms）才能發起 TCP 連接；而 fake-ip 模式下 DNS 響應幾乎是瞬時的（&amp;lt;1ms），因此能顯著縮短整體的頁面加載時間。&amp;#160;&lt;a href=&#34;#fnref:9&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
        </item><item>
            <title>Terraform IaC 初體驗</title>
            <link>https://blog.l3zc.com/zh-hant-tw/2026/04/iac-with-terraform/</link>
            <pubDate>Thu, 09 Apr 2026 07:13:35 +0000</pubDate>
            <guid>https://blog.l3zc.com/zh-hant-tw/2026/04/iac-with-terraform/</guid>
            <description>&lt;img src=&#34;https://blog.l3zc.com/2026/04/iac-with-terraform/cover_hu_a8e83f97ed61569c.webp&#34; alt=&#34;Featured image of post Terraform IaC 初體驗&#34; /&gt;&lt;p&gt;Terraform 是一款 IaC 工具，所謂 IaC 就是「基礎設施即代碼」，將我們的基礎設施以聲明式的代碼寫出來，隨後使用&lt;code&gt;terraform apply&lt;/code&gt;即可完成基礎設施的部署，同一份配置，部署出來的一定是一模一樣的基礎設施（Nix OS 用戶狂喜）。&lt;/p&gt;&#xA;&lt;h2 id=&#34;為什麼要用-terraform&#34;&gt;為什麼要用 Terraform&#xA;&lt;/h2&gt;&lt;p&gt;傳統的基礎設施管理絕大部分基於人力和各種雲服務商的 Dashboard，這就帶來了下面這些痛點：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th style=&#34;text-align: left&#34;&gt;痛點&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: left&#34;&gt;說明&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;難以重現&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;通過 Dashboard 點點點進行配置，容易改錯或者改漏，而且難以復現&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;環境漂移&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;手動操作導致生產和測試環境配置逐漸偏離，導致極端情況下測試沒問題生產直接崩&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;難以擴展&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;新增一套環境需要重複大量手工操作，耗時且容易出錯&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;難以審計&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;沒有變更記錄，&lt;del&gt;出了事不好甩鍋&lt;/del&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;難以協作&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;基礎設施由少數「懂的人」掌控，誰想要改都得去找這些人，效率低&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;為了解決這些問題，「基礎設施即代碼」&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;的概念被提了出來，Terraform 就是其中一個著名的解決方案。&lt;/p&gt;&#xA;&lt;p&gt;假設一個現實的使用場景，例如你每年都會購買新的 GCP 的 $300 試用金賬號，雖然便宜，但是每年都要重新跑到 GCP 的面板裡開機器。而有了 Terraform，想要部署同樣配置的機器，只要在每次更換賬號之後，將 API Token 替換成新的，隨後&lt;code&gt;terraform apply&lt;/code&gt;，只需要幾分鐘就能開出和你上個賬號一模一樣的機器、VPC、S3、防火牆配置等等。&lt;/p&gt;&#xA;&lt;p&gt;再比如 Cloudflare DNS 和 Tunnel 的管理和遷移，也只需要將原 Tunnel 的 Ingress Rule 複製到新 Tunnel 的 Ingress Rule。即使將來要遷移到 AliDNS、Route 53 等其他服務商，我們也可以原封不動的將數據複製過去&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;。&lt;/p&gt;&#xA;&lt;p&gt;尤其是到了多人協作的時候，配合 Git，每次更改都有跡可循，更不需要擔心合併衝突，PR 可以自動生成更改預覽，出現問題可以立刻回滾到上一個版本，這些都是傳統依賴人力去直接操作服務提供商 Dashboard 的做法完全無法做到的。&lt;/p&gt;&#xA;&lt;h2 id=&#34;terraform-的安裝&#34;&gt;Terraform 的安裝&#xA;&lt;/h2&gt;&lt;p&gt;Terraform 是用 Go 寫的，編譯出來的產物自然也是單個可執行文件，所以安裝起來也非常容易，Windows 下可以直接使用 Winget 安裝：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-pwsh&#34;&gt;winget install Hashicorp.Terraform&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果不想使用 Winget，也可以使用 Scoop 等其他包管理器，或者將預編譯的二進制文件放入&lt;code&gt;$PATH&lt;/code&gt;，即可完成安裝。&lt;/p&gt;&#xA;&lt;p&gt;如果你在 Linux 環境下，則使用對應的包管理器安裝即可，如果使用將二進制文件放入&lt;code&gt;$PATH&lt;/code&gt;的安裝方法，則要記得&lt;code&gt;sudo chmod +x terraform&lt;/code&gt;賦予文件的執行權限。&lt;/p&gt;&#xA;&lt;h2 id=&#34;terraform-的兩個基本概念&#34;&gt;Terraform 的兩個基本概念&#xA;&lt;/h2&gt;&lt;h3 id=&#34;providers&#34;&gt;Provider(s)&#xA;&lt;/h3&gt;&lt;p&gt;我們前面已經提到，Terraform 是一個款礎設施即代碼工具，作為一個工具本身，他並不綁定某個平臺，而是通過 Provider(s) 與各個平臺對接，要查看有哪些 Provider(s)，可以瀏覽 &lt;a class=&#34;link&#34; href=&#34;https://registry.terraform.io/browse/providers&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Terraform 的 Registry&lt;/a&gt;。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2026/04/iac-with-terraform/image_hu_463ebc576c22c691.webp&#34; alt=&#34;Terraform 擁有豐富的 Provider(s) 生態&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h3 id=&#34;狀態管理&#34;&gt;狀態管理&#xA;&lt;/h3&gt;&lt;p&gt;Terraform 將每次執行基礎設施變更操作時的狀態信息保存在一個狀態文件中，默認情況下會保存在當前工作目錄下的&lt;code&gt;terraform.tfstate&lt;/code&gt;文件裡&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;，我們也可以修改配置文件自行指定其他的存儲後端，例如 S3、Postgres。每次我們執行&lt;code&gt;terraform apply&lt;/code&gt;時，Terraform 都會將目前配置文件聲明的狀態同現有的狀態文件進行比對，從而計算變動的部分，自動確定調整順序之後操作 Provider 落實狀態變動。&lt;/p&gt;&#xA;&lt;h2 id=&#34;terraform-的資源導入難題&#34;&gt;Terraform 的資源導入難題&#xA;&lt;/h2&gt;&lt;p&gt;現有資源的導入一直是 Terraform 為人詬病的難題，Hashi Corp. 似乎一直在堅持著一個固執且愚蠢的觀點：你所有的基礎設施從一開始就應當是用我們的 Terraform 創建的，所以也不存在什麼資源導入的問題。&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;時間&lt;/th&gt;&#xA;          &lt;th&gt;版本&lt;/th&gt;&#xA;          &lt;th&gt;進展&lt;/th&gt;&#xA;          &lt;th&gt;問題&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;2014–2022&lt;/td&gt;&#xA;          &lt;td&gt;v0.x–v1.4&lt;/td&gt;&#xA;          &lt;td&gt;只有 &lt;code&gt;terraform import&lt;/code&gt;，一次一條，而且完全不生成配置&lt;/td&gt;&#xA;          &lt;td&gt;導入完還得自己手寫 HCL&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;2023.06&lt;/td&gt;&#xA;          &lt;td&gt;v1.5&lt;/td&gt;&#xA;          &lt;td&gt;引入 &lt;code&gt;import&lt;/code&gt; 塊和 &lt;code&gt;-generate-config-out&lt;/code&gt;參數，可以生成配置了&lt;/td&gt;&#xA;          &lt;td&gt;然而還是得一條一條寫，而且還得自己提供現有資源的 ID&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;2024.01&lt;/td&gt;&#xA;          &lt;td&gt;v1.7&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;import&lt;/code&gt; 塊支持 &lt;code&gt;for_each&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;批量了，但 ID 還得自己搞&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;2024 下半年&lt;/td&gt;&#xA;          &lt;td&gt;v1.12&lt;/td&gt;&#xA;          &lt;td&gt;引入了 &lt;code&gt;terraform query&lt;/code&gt; 和 &lt;code&gt;list&lt;/code&gt; 塊，終於實現了自動發現資源的功能&lt;/td&gt;&#xA;          &lt;td&gt;然而這個功能要 Provider 要自己實現，大量 Provider 根本沒跟上&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;這麼多年的時間，社區一直在罵，然而，「我有一堆現有資源，怎麼導入 Terraform」這個問題一直沒有被認真對待。Terraform 作為「基礎設施即代碼」工具，設計哲學就是聲明式和冪等性&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;，Hashi Corp. 他們篤信「一切以代碼聲明的狀態為準，就應該用 Terraform 從零開始創建資源」。但現實是絕大多數公司都有大量歷史存量資源，先有資源、後有代碼才是常態。這個矛盾 Hashi Corp. 承認得很晚，&lt;code&gt;v1.12&lt;/code&gt; 的 &lt;code&gt;terraform query&lt;/code&gt; 才算是官方第一次認真面對這個問題——但 Provider 生態的跟進嘛……真的是一言難盡。&lt;/p&gt;&#xA;&lt;h2 id=&#34;terraform-的文件結構&#34;&gt;Terraform 的文件結構&#xA;&lt;/h2&gt;&lt;p&gt;Terraform 的文件結構很簡單，其主程序運行時會無腦讀取工作目錄下所有的&lt;code&gt;.tf&lt;/code&gt;文件，只要信息齊全，想給文件取什麼名字都可以，比如我的文件結構就長這樣：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;❯ tree -a -I .git&#xA;.&#xA;├── .editorconfig&#xA;├── .github&#xA;│   ├── dependabot.yml&#xA;│   └── workflows&#xA;│       ├── terraform-apply.yml&#xA;│       ├── terraform-plan.yml&#xA;│       └── your-fork.yml&#xA;├── .gitignore&#xA;├── .terraform.lock.hcl&#xA;├── cf_dns_zones.tf&#xA;├── cf_tunnel.tf&#xA;├── dns_example_com.tf&#xA;├── dns_example_net.tf&#xA;├── dns_example_top.tf&#xA;├── dns_example_cn.tf&#xA;├── main.tf                 # 基礎配置（terraform 塊）&#xA;├── moved.tf                # 移動過的資源&#xA;├── provider.tf             # 每個 Provider 的配置&#xA;├── README.md&#xA;├── rename_resources.ps1&#xA;└── variables.tf            # 自定義的變量&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;main.tf&lt;/code&gt;用於存放基礎配置:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;terraform {&#xA;  required_providers {&#xA;    cloudflare = {&#xA;      source  = &#34;cloudflare/cloudflare&#34;&#xA;      version = &#34;~&gt; 5&#34;&#xA;    }&#xA;&#xA;    tencentcloud = {&#xA;      source  = &#34;tencentcloudstack/tencentcloud&#34;&#xA;      version = &#34;&gt;= 1.81.43&#34;&#xA;    }&#xA;  }&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;provider.tf&lt;/code&gt;用於存放每個 Provider 的配置：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;provider &#34;cloudflare&#34; {}&#xA;provider &#34;tencentcloud&#34; {}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;這裡留空是因為我們可以通過環境變量來傳遞 Credentials，而不是直接將 Credentials 寫在配置文件中，例如 Cloudflare 的 Provider 就接受&lt;code&gt;CLOUDFLARE_API_TOKEN&lt;/code&gt;作為&lt;code&gt;api_token&lt;/code&gt;這個變量的替代。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;variables.tf&lt;/code&gt;用於聲明自定義的變量：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;variable &#34;cloudflare_zone_example_com&#34; {&#xA;  description = &#34;Cloudflare zone ID for example.com&#34;&#xA;  type        = string&#xA;}&#xA;&#xA;variable &#34;cloudflare_zone_example_top&#34; {&#xA;  description = &#34;Cloudflare zone ID for example.top&#34;&#xA;  type        = string&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;這些變量可以通過前綴為&lt;code&gt;TF_VAR_&lt;/code&gt;的環境變量傳入，Terraform 也會自動讀取&lt;code&gt;terraform.tfvars&lt;/code&gt;文件中的變量，變量的主要用途是在別的配置文件中調用，例如：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;resource &#34;cloudflare_dns_record&#34; &#34;example_cname&#34; {&#xA;  content = &#34;${cloudflare_zero_trust_tunnel_cloudflared.Production_Tunnel.id}.cfargotunnel.com&#34;&#xA;  name    = &#34;example.example.com&#34;&#xA;  proxied = true&#xA;  tags    = []&#xA;  ttl     = 1&#xA;  type    = &#34;CNAME&#34;&#xA;  zone_id = var.cloudflare_zone_id_example_com  #這裡調用了cloudflare_zone_example_com這個變量&#xA;  settings = {&#xA;    flatten_cname = false&#xA;  }&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;有了這些基礎配置，我們就可以運行&lt;code&gt;terraform init&lt;/code&gt;初始化 Terraform 環境並鎖定依賴版本，剩下的就都是我們資源的聲明瞭。&lt;/p&gt;&#xA;&lt;h2 id=&#34;將現有資源導入-terraform&#34;&gt;將現有資源導入 Terraform&#xA;&lt;/h2&gt;&lt;p&gt;前文提到過 Terraform 的狀態管理這個概念，狀態管理雖好，但在我們初始化時也帶來了一個問題——在默認狀態下，Terraform 的狀態文件顯然是空的。&lt;/p&gt;&#xA;&lt;p&gt;此時我們需要做的是將雲服務提供商上現有資源的狀態導入 Terraform，這樣 Terraform 才能無縫接手並管理我們的基礎設施。相信大家也都看到了前文對 Terraform 資源導入問題的吐槽。以導入 Cloudflare 上託管的 DNS 記錄為例，前文提到過，如果 Provider 支持，可以使用 Terraform 在 1.12 版本之後引入的&lt;code&gt;terraform query&lt;/code&gt;，然而大多數情況下，Providers 都是沒有跟進這個新功能的，Cloudflare 就屬於不支持的那一類。&lt;/p&gt;&#xA;&lt;p&gt;好在雖然 Hashi Corp. 不認真解決問題，各路高強度使用 Terraform 管理基礎設施的公司就各顯神通。Cloudflare 就維護了名為 &lt;a class=&#34;link&#34; href=&#34;https://github.com/cloudflare/cf-terraforming&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;cf-terraforming&lt;/a&gt; 的導入工具，免去了很多手動導入的麻煩。首先安裝&lt;code&gt;cf-terraforming&lt;/code&gt;，這個工具是用 Go 語言編寫的，需要在有 Go 語言環境的情況下安裝，或者將官方編譯好的二進制文件其放入&lt;code&gt;$PATH&lt;/code&gt;並賦予其執行權限。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;go install github.com/cloudflare/cf-terraforming/cmd/cf-terraforming@latest&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;這個工具的用法還是比較簡單的，首先你需要以下環境變量：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# 如果你使用 API Token&#xA;export CLOUDFLARE_API_TOKEN=&#39;Hzsq3Vub-7Y-hSTlAaLH3Jq_YfTUOCcgf22_Fs-j&#39;&#xA;&#xA;# 如果你使用 API Key&#xA;export CLOUDFLARE_EMAIL=&#39;user@example.com&#39;&#xA;export CLOUDFLARE_API_KEY=&#39;1150bed3f45247b99f7db9696fffa17cbx9&#39;&#xA;&#xA;# 指定需要導入的域名的區域 ID，如果導入的是賬戶資源（例如 Cloudflare Tunnel）則不需要&#xA;export CLOUDFLARE_ZONE_ID=&#39;81b06ss3228f488fh84e5e993c2dc17&#39;&lt;/code&gt;&lt;/pre&gt;&lt;blockquote class=&#34;alert alert-tip&#34;&gt;&#xA;        &lt;div class=&#34;alert-header&#34;&gt;&#xA;            &lt;span class=&#34;alert-icon&#34;&gt;💡&lt;/span&gt;&#xA;            &lt;span class=&#34;alert-title&#34;&gt;提示&lt;/span&gt;&#xA;        &lt;/div&gt;&#xA;        &lt;div class=&#34;alert-body&#34;&gt;&#xA;            &lt;p&gt;此處的命令假設你正在使用 Bash，如果使用的是與 Bash 語法不兼容的 Shell，則需要做出調整，例如對於 Windows 上的 PowerShell，導入環境變量的語法如下：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-pwsh&#34;&gt;$env:CLOUDFLARE_API_TOKEN=&#39;Hzsq3Vub-7Y-hSTlAaLH3Jq_YfTUOCcgf22_Fs-j&#39;&lt;/code&gt;&lt;/pre&gt;&#xA;        &lt;/div&gt;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;通常我們只需要設置&lt;code&gt;CLOUDFLARE_API_TOKEN&lt;/code&gt;和&lt;code&gt;CLOUDFLARE_ZONE_ID&lt;/code&gt;就可以了，在控制檯創建 API Token 時，記得賦予這個 Token 必要的權限，本次我們只是導入 DNS 記錄，所以只賦予編輯區域 DNS 的權限即可。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2026/04/iac-with-terraform/image-1_hu_15298d4bd366a377.webp&#34; alt=&#34;賦予操作資源所需要的權限&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;好了，準備工作全部完成，現在可以開始生成配置文件：&lt;/p&gt;&#xA;&lt;p&gt;首先導入賬戶中域名的配置，也就是&lt;code&gt;cloudflare_zone&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;cf-terraforming generate \&#xA;  --key $CLOUDFLARE_API_KEY \&#xA;  --resource-type &#34;cloudflare_zone&#34; &gt; zone.tf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;這一步會在當前目錄下生成一個名為&lt;code&gt;zone.tf&lt;/code&gt;的文件，裡面會有如下格式的內容：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;resource &#34;cloudflare_zone&#34; &#34;REDACTED&#34; {&#xA;  name                = &#34;REDACTED&#34;&#xA;  paused              = false&#xA;  type                = &#34;full&#34;&#xA;  vanity_name_servers = []&#xA;  account = {&#xA;    id   = &#34;REDACTED&#34;&#xA;    name = &#34;REDACTED&#34;&#xA;  }&#xA;}&#xA;&#xA;resource &#34;cloudflare_zone&#34; &#34;REDACTED&#34; {&#xA;  name                = &#34;REDACTED&#34;&#xA;  paused              = false&#xA;  type                = &#34;full&#34;&#xA;  vanity_name_servers = []&#xA;  account = {&#xA;    id   = &#34;REDACTED&#34;&#xA;    name = &#34;REDACTED&#34;&#xA;  }&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;現在域名資源已經導入了，但是裡面的配置並沒有。接下來，導入域名下的 DNS 記錄：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;cf-terraforming generate \&#xA;  --zone $CLOUDFLARE_ZONE_ID \&#xA;  --key $CLOUDFLARE_API_KEY \&#xA;  --resource-type &#34;cloudflare_dns_record&#34; &gt;&gt; dns.tf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;這一步會在當前目錄下生成一個名為&lt;code&gt;dns.tf&lt;/code&gt;的配置文件，裡面會有如下格式的內容：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;resource &#34;cloudflare_dns_record&#34; &#34;terraform_managed_resource_5deb14xxxxxb629bf123xxxxxxxc8f_0&#34; {&#xA;  content  = &#34;67.24.33.108&#34;&#xA;  name     = &#34;example.example.com&#34;&#xA;  proxied  = true&#xA;  tags     = []&#xA;  ttl      = 1&#xA;  type     = &#34;A&#34;&#xA;  zone_id  = &#34;81c7f2de8dfxxxxxx52629xxxxxxfc&#34;&#xA;  settings = {}&#xA;}&#xA;&#xA;resource &#34;cloudflare_dns_record&#34; &#34;terraform_managed_resource_89xxxxx0bf9cxxxxxx9a_1&#34; {&#xA;  content  = &#34;35.27.108.33&#34;&#xA;  name     = &#34;terraform.example.com&#34;&#xA;  proxied  = true&#xA;  tags     = []&#xA;  ttl      = 1&#xA;  type     = &#34;A&#34;&#xA;  zone_id  = &#34;8xxxxxx7644e428526xxxxxx&#34;&#xA;  settings = {}&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果有多個域名需要導入，則多次分別設置環境變量&lt;code&gt;CLOUDFLARE_ZONE_ID&lt;/code&gt;再重複運行命令即可。&lt;/p&gt;&#xA;&lt;p&gt;這裡生成的配置文件可以直接使用，也就是我們之後需要的 Terraform 配置文件。然而，此時我們僅僅只是生成了配置文件，但是目前 Terraform 的狀態依然是空的，這時要是直接&lt;code&gt;terraform apply&lt;/code&gt;，Terraform 會不管三七二十一將我們剛剛導入的聲明一律視為新增資源，然後甩出一大堆「Alredy Exists」報錯。所以接下來，我們需要將生成的配置文件導入 Terraform 的&lt;code&gt;terraform.tfstate&lt;/code&gt;狀態。&lt;/p&gt;&#xA;&lt;p&gt;Terraform 在 1.5 版本引入了&lt;code&gt;import&lt;/code&gt;塊，相比以往一行一行輸入命令的方式更加現代。其導入流程是先生成一個包含&lt;code&gt;import&lt;/code&gt;塊的&lt;code&gt;.tf&lt;/code&gt;文件，下次進行&lt;code&gt;terraform apply&lt;/code&gt;時，Terraform 就會自動為我們執行導入操作。&lt;/p&gt;&#xA;&lt;p&gt;生成&lt;code&gt;cloudflare_zone&lt;/code&gt;的&lt;code&gt;import&lt;/code&gt;塊：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;cf-terraforming import \&#xA;  --resource-type &#34;cloudflare_zone&#34; \&#xA;  --modern-import-block \&#xA;  --key $CLOUDFLARE_API_KEY \&#xA;  --zone $CLOUDFLARE_ZONE_ID &gt;&gt; import.tf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;生成&lt;code&gt;cloudflare_dns_record&lt;/code&gt;的&lt;code&gt;import&lt;/code&gt;塊：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;cf-terraforming import \&#xA;  --resource-type &#34;cloudflare_dns_record&#34; \&#xA;  --modern-import-block \&#xA;  --key $CLOUDFLARE_API_KEY \&#xA;  --zone $CLOUDFLARE_ZONE_ID &gt;&gt; import.tf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;這一步會在當前目錄下生成&lt;code&gt;import.tf&lt;/code&gt;，它包含了所需要的導入信息，作用就是告訴 Terraform 上一步生成的每個&lt;code&gt;resource&lt;/code&gt;塊到底對應的是哪一個雲服務提供商的資源 ID。這個 ID 是雲服務提供商內部標記資源的代碼，平常是不會在控制面板上顯示的，只有在用 API 特別請求時才會知道。Terraform 在導入過程中需要用到這個 ID 以確認本地的定義對應的雲端資源，以實現嚴格的冪等性。&lt;/p&gt;&#xA;&lt;p&gt;好了，現在我們運行&lt;code&gt;terraform plan&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ terraform plan&#xA;cloudflare_dns_record.minio_a: Refreshing state... [id=xxxxxxxxxxx53]&#xA;cloudflare_zero_trust_tunnel_cloudflared_config.raspberrypi: Refreshing state...&#xA;......&#xA;&#xA;Terraform will perform the following actions:&#xA;&#xA;  # cloudflare_dns_record.terraform_managed_resource_0 will be imported&#xA;    resource &#34;cloudflare_dns_record&#34; &#34;terraform_managed_resource_REDACTED_0&#34; {&#xA;        content     = &#34;67.24.33.108&#34;&#xA;        created_on  = &#34;2026-04-08T10:18:12Z&#34;&#xA;        id          = &#34;5deb14c21xxxxxxx20f1c8f&#34;&#xA;        meta        = jsonencode({})&#xA;        modified_on = &#34;2026-04-08T10:18:12Z&#34;&#xA;        name        = &#34;example.example.com&#34;&#xA;        proxiable   = true&#xA;        proxied     = true&#xA;        settings    = {}&#xA;        tags        = []&#xA;        ttl         = 1&#xA;        type        = &#34;A&#34;&#xA;        zone_id     = &#34;REDACTED&#34;&#xA;    }&#xA;&#xA;  # cloudflare_dns_record.terraform_managed_resource_1 will be imported&#xA;    resource &#34;cloudflare_dns_record&#34; &#34;terraform_managed_resource_89c149exxxxxxxxxxxba13xxxxxa_1&#34; {&#xA;        content     = &#34;35.27.108.33&#34;&#xA;        created_on  = &#34;2026-04-08T10:17:54Z&#34;&#xA;        id          = &#34;89cxxxxxxxxxxxxxxxxxx09a&#34;&#xA;        meta        = jsonencode({})&#xA;        modified_on = &#34;2026-04-08T10:17:54Z&#34;&#xA;        name        = &#34;terraform.example.com&#34;&#xA;        proxiable   = true&#xA;        proxied     = true&#xA;        settings    = {}&#xA;        tags        = []&#xA;        ttl         = 1&#xA;        type        = &#34;A&#34;&#xA;        zone_id     = &#34;81xxxxxxxxxxxxxxxxxxxxxfc&#34;&#xA;    }&#xA;&#xA;Plan: 2 to import, 0 to add, 0 to change, 0 to destroy.&#xA;&#xA;────────────────────────────────────────────────────────────────────────────────────────────────────────&#xA;&#xA;Note: You didn&#39;t use the -out option to save this plan, so Terraform can&#39;t guarantee to take exactly&#xA;these actions if you run &#34;terraform apply&#34; now.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果需要 Add、Change 和 Destroy 的資源數量都是 0，說明我們的導入操作沒有問題，直接&lt;code&gt;terraform apply --auto-approve&lt;/code&gt;，資源就導入完成了。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ terraform apply --auto-approve&#xA;cloudflare_dns_record.push_a: Refreshing state... [id=REDACTED]&#xA;&#xA;Terraform will perform the following actions:&#xA;&#xA;  # cloudflare_dns_record.terraform_managed_resource_REDACTED_0 will be imported&#xA;    resource &#34;cloudflare_dns_record&#34; &#34;terraform_managed_resource_REDACTED_0&#34; {&#xA;        content     = &#34;67.24.33.108&#34;&#xA;        created_on  = &#34;2026-04-08T10:18:12Z&#34;&#xA;        id          = &#34;REDACTED&#34;&#xA;        meta        = jsonencode({})&#xA;        modified_on = &#34;2026-04-08T10:18:12Z&#34;&#xA;        name        = &#34;example.example.com&#34;&#xA;        proxiable   = true&#xA;        proxied     = true&#xA;        settings    = {}&#xA;        tags        = []&#xA;        ttl         = 1&#xA;        type        = &#34;A&#34;&#xA;        zone_id     = &#34;REDACTED&#34;&#xA;    }&#xA;&#xA;  # cloudflare_dns_record.terraform_managed_resource_REDACTED_1 will be imported&#xA;    resource &#34;cloudflare_dns_record&#34; &#34;terraform_managed_resource_REDACTED_1&#34; {&#xA;        content     = &#34;35.27.108.33&#34;&#xA;        created_on  = &#34;2026-04-08T10:17:54Z&#34;&#xA;        id          = &#34;REDACTED&#34;&#xA;        meta        = jsonencode({})&#xA;        modified_on = &#34;2026-04-08T10:17:54Z&#34;&#xA;        name        = &#34;terraform.example.com&#34;&#xA;        proxiable   = true&#xA;        proxied     = true&#xA;        settings    = {}&#xA;        tags        = []&#xA;        ttl         = 1&#xA;        type        = &#34;A&#34;&#xA;        zone_id     = &#34;REDACTED&#34;&#xA;    }&#xA;&#xA;Plan: 2 to import, 0 to add, 0 to change, 0 to destroy.&#xA;cloudflare_dns_record.terraform_managed_resource_REDACTED_1: Importing... [id=REDACTED/REDACTED]&#xA;cloudflare_dns_record.terraform_managed_resource_REDACTED_1: Import complete [id=REDACTED/REDACTED]&#xA;cloudflare_dns_record.terraform_managed_resource_REDACTED_0: Importing... [id=REDACTED/REDACTED]&#xA;cloudflare_dns_record.terraform_managed_resource_REDACTED_0: Import complete [id=REDACTED/REDACTED]&#xA;&#xA;Apply complete! Resources: 2 imported, 0 added, 0 changed, 0 destroyed.&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;狀態存儲和持續集成&#34;&gt;狀態存儲和持續集成&#xA;&lt;/h2&gt;&lt;p&gt;IaC 的核心價值之一就在於可以輕易實現基於 Git 的多人協作，以及 CI 的持續集成，但是在此之前，又有一個新的問題需要解決——&lt;code&gt;terraform.tfstate&lt;/code&gt;到底放哪裡：沒人想要換一次環境辛辛苦苦導入的狀態就丟一次。&lt;/p&gt;&#xA;&lt;p&gt;Terraform 目前支持以下幾種保存狀態的後端：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;local&lt;/li&gt;&#xA;&lt;li&gt;remote&lt;/li&gt;&#xA;&lt;li&gt;azurerm&lt;/li&gt;&#xA;&lt;li&gt;consul&lt;/li&gt;&#xA;&lt;li&gt;cos&lt;/li&gt;&#xA;&lt;li&gt;gcs&lt;/li&gt;&#xA;&lt;li&gt;http&lt;/li&gt;&#xA;&lt;li&gt;Kubernetes&lt;/li&gt;&#xA;&lt;li&gt;oci&lt;/li&gt;&#xA;&lt;li&gt;oss&lt;/li&gt;&#xA;&lt;li&gt;pg&lt;/li&gt;&#xA;&lt;li&gt;s3&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;沒有特殊需求可以像我一樣選擇&lt;code&gt;s3&lt;/code&gt;，畢竟 Cloudflare R2 有免費額度，不用白不用。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;terraform {&#xA;  backend &#34;s3&#34; {&#xA;    bucket = &#34;terraform&#34;&#xA;    key    = &#34;terraform.tfstate&#34;&#xA;    region = &#34;auto&#34;&#xA;    endpoints = {&#xA;      s3 = &#34;https://REDACTED.r2.cloudflarestorage.com&#34;&#xA;    }&#xA;&#xA;    # R2 不需要這些 AWS 的驗證&#xA;    skip_credentials_validation = true&#xA;    skip_metadata_api_check     = true&#xA;    skip_region_validation      = true&#xA;    skip_requesting_account_id  = true&#xA;    use_path_style              = true&#xA;  }&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;對於 S3 的後端，建議用&lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt;和&lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;兩個環境變量來存儲 Credentials：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;export AWS_ACCESS_KEY_ID=&#39;REDACTED&#39;&#xA;export AWS_SECRET_ACCESS_KEY=&#39;REDACTED&#39;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;配置完成，運行&lt;code&gt;terraform init -migrate-state&lt;/code&gt;，配置就成功存儲到雲上了，以後不論在何處修改配置、運行&lt;code&gt;terraform apply&lt;/code&gt;都無須擔心 Terraform 的 State 不同步的問題。&lt;/p&gt;&#xA;&lt;p&gt;接下來就是 Github CI 的配置，其實很簡單，無非就是每次&lt;code&gt;git push&lt;/code&gt;時觸發一次&lt;code&gt;terraform init&lt;/code&gt;、&lt;code&gt;terraform fmt&lt;/code&gt;和&lt;code&gt;terraform apply&lt;/code&gt;，以下是我的&lt;code&gt;.github/workflows/apply.yml&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;name: &#34;Terraform Apply&#34;&#xA;&#xA;on:&#xA;  push:&#xA;    branches:&#xA;      - main&#xA;&#xA;env:&#xA;  TF_IN_AUTOMATION: &#34;true&#34;&#xA;  CLOUDFLARE_API_TOKEN: &#34;${{ secrets.CLOUDFLARE_API_TOKEN }}&#34;&#xA;  AWS_ACCESS_KEY_ID: &#34;${{ secrets.AWS_ACCESS_KEY_ID }}&#34;&#xA;  AWS_SECRET_ACCESS_KEY: &#34;${{ secrets.AWS_SECRET_ACCESS_KEY }}&#34;&#xA;  TF_VAR_cloudflare_zone_id_example_com: &#34;${{ vars.TF_VAR_CLOUDFLARE_ZONE_ID_EXAMPLE_COM }}&#34;&#xA;  TF_VAR_cloudflare_zone_id_example_top: ${{ vars.TF_VAR_CLOUDFLARE_ZONE_ID_EXAMPLE_TOP }}&#xA;&#xA;jobs:&#xA;  terraform:&#xA;    name: &#34;Terraform Apply&#34;&#xA;    runs-on: ubuntu-latest&#xA;    permissions:&#xA;      contents: read&#xA;    concurrency:&#xA;      group: terraform-apply&#xA;      cancel-in-progress: false&#xA;    steps:&#xA;      - name: Checkout&#xA;        uses: actions/checkout@v6&#xA;&#xA;      - name: Setup Terraform&#xA;        uses: hashicorp/setup-terraform@v4&#xA;&#xA;      - name: Terraform Init&#xA;        run: terraform init -input=false&#xA;&#xA;      - name: Terraform Apply&#xA;        run: terraform apply -input=false -auto-approve&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;對於 PR 則應當讓 CI 自動為每次 PR 附上&lt;code&gt;terraform plan&lt;/code&gt;的輸出：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;name: Terraform Plan&#xA;&#xA;on:&#xA;  pull_request:&#xA;    paths:&#xA;      - &#34;**/*.tf&#34;&#xA;      - &#34;.github/workflows/terraform-plan.yml&#34;&#xA;&#xA;permissions:&#xA;  contents: read&#xA;  pull-requests: write&#xA;&#xA;env:&#xA;  TF_IN_AUTOMATION: &#34;true&#34;&#xA;  CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}&#xA;  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}&#xA;  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}&#xA;  TF_VAR_cloudflare_zone_id_example_com: &#34;${{ vars.TF_VAR_CLOUDFLARE_ZONE_ID_EXAMPLE_COM }}&#34;&#xA;  TF_VAR_cloudflare_zone_id_example_top: ${{ vars.TF_VAR_CLOUDFLARE_ZONE_ID_EXAMPLE_TOP }}&#xA;&#xA;jobs:&#xA;  plan:&#xA;    name: Terraform Plan&#xA;    runs-on: ubuntu-latest&#xA;    steps:&#xA;      - uses: actions/checkout@v6&#xA;&#xA;      - uses: hashicorp/setup-terraform@v4&#xA;&#xA;      - name: Terraform fmt&#xA;        id: fmt&#xA;        run: terraform fmt -check -recursive&#xA;        continue-on-error: true&#xA;&#xA;      - name: Terraform Init&#xA;        id: init&#xA;        run: terraform init -input=false&#xA;&#xA;      - name: Terraform Validate&#xA;        id: validate&#xA;        run: terraform validate -no-color&#xA;&#xA;      - name: Terraform Plan&#xA;        id: plan&#xA;        run: terraform plan -input=false -no-color&#xA;        continue-on-error: true&#xA;&#xA;      - name: Post Plan to PR&#xA;        uses: actions/github-script@v8&#xA;        with:&#xA;          github-token: ${{ secrets.GITHUB_TOKEN }}&#xA;          script: |&#xA;            const { data: comments } = await github.rest.issues.listComments({&#xA;              owner: context.repo.owner,&#xA;              repo: context.repo.repo,&#xA;              issue_number: context.issue.number,&#xA;            });&#xA;            const botComment = comments.find(c =&gt;&#xA;              c.user.type === &#39;Bot&#39; &amp;&amp; c.body.includes(&#39;&lt;!-- terraform-plan --&gt;&#39;)&#xA;            );&#xA;&#xA;            const planOutput = `${{ steps.plan.outputs.stdout }}`.substring(0, 65000);&#xA;&#xA;            const body = `&lt;!-- terraform-plan --&gt;&#xA;            #### Terraform Plan&#xA;&#xA;            | Step     | Result                            |&#xA;            | -------- | --------------------------------- |&#xA;            | fmt      | \`${{ steps.fmt.outcome }}\`      |&#xA;            | init     | \`${{ steps.init.outcome }}\`     |&#xA;            | validate | \`${{ steps.validate.outcome }}\` |&#xA;            | plan     | \`${{ steps.plan.outcome }}\`     |&#xA;&#xA;            &lt;details&gt;&lt;summary&gt;展開 Plan 詳情&lt;/summary&gt;&#xA;&#xA;            \`\`\`terraform&#xA;            ${planOutput}&#xA;            \`\`\`&#xA;            &lt;/details&gt;`;&#xA;&#xA;            if (botComment) {&#xA;              await github.rest.issues.updateComment({&#xA;                owner: context.repo.owner,&#xA;                repo: context.repo.repo,&#xA;                comment_id: botComment.id,&#xA;                body&#xA;              });&#xA;            } else {&#xA;              await github.rest.issues.createComment({&#xA;                issue_number: context.issue.number,&#xA;                owner: context.repo.owner,&#xA;                repo: context.repo.repo,&#xA;                body&#xA;              });&#xA;            }&#xA;&#xA;      - name: Fail if plan failed&#xA;        if: steps.plan.outcome == &#39;failure&#39;&#xA;        run: exit 1&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2026/04/iac-with-terraform/image-2_hu_c922b7d4de19a009.webp&#34; alt=&#34;每次 PR 都會有 Plan 的輸出&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h2 id=&#34;後續的工作流程&#34;&gt;後續的工作流程&#xA;&lt;/h2&gt;&lt;p&gt;到這一步，Terraform 的「接管初始化」已經結束了，後面就進入了日常維護階段。這個階段其實就三件事：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;創建新資源&lt;/li&gt;&#xA;&lt;li&gt;修改現有的資源&lt;/li&gt;&#xA;&lt;li&gt;刪除不再需要的資源&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;常用的操作就兩個：&lt;code&gt;terraform plan&lt;/code&gt;和&lt;code&gt;terraform apply&lt;/code&gt;，如果是個人使用，小的改動直接提交就算了；如果是團隊協作，每次修改則應當遵循能 PR 就不直接 Commit 的原則。&lt;/p&gt;&#xA;&lt;h3 id=&#34;創建基礎設施&#34;&gt;創建基礎設施&#xA;&lt;/h3&gt;&lt;p&gt;假設你現在要新增一條 DNS 記錄，或者新建一個 Tunnel、一個對象存儲桶，流程如下：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;  C1[新建分支&#xA;如 feat/add-minio-record] --&gt; C2[新增 resource 塊]&#xA;  C2 --&gt; C3[terraform fmt + validate]&#xA;  C3 --&gt; C4[terraform plan]&#xA;  C4 --&gt; C5{僅新增預期資源?}&#xA;  C5 -- 否 --&gt; C6[修正配置後重跑 plan]&#xA;  C6 --&gt; C4&#xA;  C5 -- 是 --&gt; C7[提交 PR]&#xA;  C7 --&gt; C8[合併後 CI apply]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;最理想的&lt;code&gt;plan&lt;/code&gt;輸出是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;X to add&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;0 to change&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;0 to destroy&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;如果你只是想加東西，結果出現了&lt;code&gt;to destroy&lt;/code&gt;，那就先別衝動，通常是引用寫錯、變量搞錯，或者不小心改了資源地址，仔細檢查是什麼地方出了問題。&lt;/p&gt;&#xA;&lt;h3 id=&#34;修改與刪除基礎設施&#34;&gt;修改與刪除基礎設施&#xA;&lt;/h3&gt;&lt;p&gt;修改流程和創建類似，但要多一步——評估變更是否會觸發重建。&lt;/p&gt;&#xA;&lt;p&gt;因為很多 Provider 字段是&lt;code&gt;ForceNew&lt;/code&gt;，你以為只是改個字段，Terraform 看完說：「好的，刪了重建。」這在我們修改 DNS 的這個場景並不是什麼很大的問題，但是到了雲實例這種資源，如果刪除重建勢必會造成損失。&lt;/p&gt;&#xA;&lt;p&gt;建議按下面這個順序來：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;  M1[修改 .tf] --&gt; M2[terraform plan]&#xA;  M2 --&gt; M3{出現 replace/destroy?}&#xA;  M3 -- 否 --&gt; M7[確認影響範圍]&#xA;  M7 --&gt; M8[terraform apply]&#xA;  M3 -- 是 --&gt; M4[暫停並複核變更]&#xA;  M4 --&gt; M5[必要時加 lifecycle 保護]&#xA;  M5 --&gt; M6[安排變更窗口]&#xA;  M6 --&gt; M8&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;對生產環境來說，創建、修改得慢一點並不是什麼很大的問題，最應當看重的是操作的正確性，慢一點，不要出錯。IaC 不是比手速，IaC 比的是可預期性。&lt;/p&gt;&#xA;&lt;p&gt;如果是刪除資源（比如下線某個 DNS 記錄、清理廢棄 Tunnel），走下面這個流程&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;  D1[確認資源已廢棄&#xA;  檢查業務/監控/腳本依賴] --&gt; D2[刪除 resource 塊或調整 count/for_each]&#xA;  D2 --&gt; D3[terraform plan]&#xA;  D3 --&gt; D4{to destroy 是否符合預期?}&#xA;  D4 -- 否 --&gt; D5[回滾修改並繼續排查依賴]&#xA;  D5 --&gt; D1&#xA;  D4 -- 是 --&gt; D6[準備回滾方案並選低峰窗口]&#xA;  D6 --&gt; D7[PR 審核通過]&#xA;  D7 --&gt; D8[terraform apply]&#xA;  D8 --&gt; D9[刪除資源後的可用性檢查]&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;日常協作建議&#34;&gt;日常協作建議&#xA;&lt;/h3&gt;&lt;ul&gt;&#xA;&lt;li&gt;把 Credentials 放環境變量或 CI Secret，別寫進&lt;code&gt;.tf&lt;/code&gt;和倉庫&lt;/li&gt;&#xA;&lt;li&gt;對關鍵資源開啟保護策略，防止誤刪&lt;/li&gt;&#xA;&lt;li&gt;將目錄按資源類型拆分&lt;/li&gt;&#xA;&lt;li&gt;定期執行&lt;code&gt;terraform plan&lt;/code&gt;做基礎設施漂移檢查&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;和校正，避免在面板誤操作手動修改&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;雖然這套流程看起來很麻煩，但每一次變更都有記錄、可審計、可回滾，最重要的是可復現，這才是 IaC 最有價值的地方。&lt;/p&gt;&#xA;&lt;h2 id=&#34;參考&#34;&gt;參考&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://candinya.com/posts/manage-cloudflare-dns-with-terraform/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;使用 Terraform 管理 CloudFlare 上的 DNS 解析記錄 - Candinya&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://developer.hashicorp.com/terraform/tutorials/automation/github-actions&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Automate Terraform with GitHub Actions&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://developer.hashicorp.com/terraform/language/files/tfquery&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Query configuration files&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://developer.hashicorp.com/terraform/language/block/tfquery/list&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;list block reference&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://github.com/cloudflare/cf-terraforming&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;cloudflare/cf-terraforming&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://developers.cloudflare.com/terraform/advanced-topics/import-cloudflare-resources/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Import Cloudflare resources&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Cloudflare Provider - Terraform Registry&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://en.wikipedia.org/wiki/Infrastructure_as_code&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Infrastructure as Code&lt;/a&gt;，是指採用機器可讀的配置文件定義所需要的基礎設施的部署方法。&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;每個供應商的配置文件字段命名和格式都有區別，但這可以很容易的編寫腳本進行轉換。&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;需要特別注意的是，狀態文件中可能包含數據庫密碼、API 密鑰等明文存儲的敏感信息，因此絕對不要將 &lt;code&gt;.tfstate&lt;/code&gt; 文件提交到公開的代碼倉庫中。&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:4&#34;&gt;&#xA;&lt;p&gt;HashiCorp Configuration Language，HashiCorp 自家開發的一種聲明式配置語言，旨在兼顧機器可讀性與人類可讀性。&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:5&#34;&gt;&#xA;&lt;p&gt;冪等性（Idempotence）指計算機系統或接口在接收到同一請求的多次操作時，產生的影響與一次執行的結果相同，不論執行多少次，系統的最終狀態始終保持一致。&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:6&#34;&gt;&#xA;&lt;p&gt;如果你僅僅是想讓 Terraform 不再管理某個資源，而不是真正在雲端銷燬它，應該使用 &lt;code&gt;terraform state rm&lt;/code&gt; 命令，而不是在代碼中刪掉資源塊然後 &lt;code&gt;apply&lt;/code&gt;，否則雲環境上的真實資源也會被一併銷燬。&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:7&#34;&gt;&#xA;&lt;p&gt;基礎設施漂移（Infrastructure Drift）是指現實中通過控制檯點按等非 IaC 途徑修改了基礎設施，導致其實際狀態與代碼中聲明的狀態不一致的情況。&amp;#160;&lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
        </item><item>
            <title>拋棄所謂「客戶端」，直接使用 Mihomo 內核</title>
            <link>https://blog.l3zc.com/zh-hant-tw/2025/07/switch-to-pure-mihomo-kernel/</link>
            <pubDate>Thu, 03 Jul 2025 10:30:00 +0000</pubDate>
            <guid>https://blog.l3zc.com/zh-hant-tw/2025/07/switch-to-pure-mihomo-kernel/</guid>
            <description>&lt;img src=&#34;https://blog.l3zc.com/2025/07/switch-to-pure-mihomo-kernel/cover_hu_1a93b6f26c22bbfe.webp&#34; alt=&#34;Featured image of post 拋棄所謂「客戶端」，直接使用 Mihomo 內核&#34; /&gt;&lt;p&gt;自從開始使用 Clash/Mihomo，我和大多數人一樣選擇了基於其內核的圖形化客戶端——圖方便。實際上，這些 Mihomo 客戶端本質上都差不多：內核都是同一個，他們主要負責提供一個易用的界面、管理配置文件、訂閱更新，以及 GUI 下的系統代理設置。基於這一點，我認為判斷一個 Mihomo 客戶端優劣的關鍵，便是看它的「覆寫」功能做得如何。每一個通過客戶端下載或者訂閱的配置文件，都會經過一系列「覆寫」過程，比如更換 &lt;code&gt;mixed-port&lt;/code&gt;、添加 &lt;code&gt;sniffer&lt;/code&gt; 配置等，最後生成用於啟動 Mihomo 內核的最終配置。&lt;/p&gt;&#xA;&lt;p&gt;然而，並非所有客戶端都能勝任這一核心工作。比如 ShellCrash，其覆寫功能經常莫名其妙出岔子，說到底還是實現得太粗糙。如果連覆蓋和修改配置都做不好，這種客戶端實在難稱合格。&lt;/p&gt;&#xA;&lt;p&gt;與其依賴這些&lt;del&gt;屎一樣的&lt;/del&gt;不盡如人意的 Mihomo 客戶端，不如自己動手，直接自己編寫、管理配置文件交給內核啟動，而不是依賴「黑箱」一般的各種「客戶端」，不僅更純淨、更穩定，而且更可控。&lt;/p&gt;&#xA;&lt;h2 id=&#34;需要的預備知識&#34;&gt;需要的預備知識&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;基本的 Linux 操作&lt;/li&gt;&#xA;&lt;li&gt;知道如何使用 CLI 編輯器，如 nano&lt;/li&gt;&#xA;&lt;li&gt;已經搭建 Substore（可選）&lt;/li&gt;&#xA;&lt;li&gt;默認使用 root 用戶操作，非 root 用戶請自行注意提權&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;安裝-mihomo-內核&#34;&gt;安裝 Mihomo 內核&#xA;&lt;/h2&gt;&lt;p&gt;對於基於 Debian 的分支，可以使用預編譯的&lt;code&gt;.deb&lt;/code&gt;包安裝，對於其他使用&lt;code&gt;systemd&lt;/code&gt;的系統，下載&lt;a class=&#34;link&#34; href=&#34;https://github.com/MetaCubeX/mihomo/releases&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;編譯好的二進制文件&lt;/a&gt;，重命名為&lt;code&gt;mihomo&lt;/code&gt;，放到&lt;code&gt;/usr/local/bin&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;curl -o /usr/local/bin/mihomo &lt;下載鏈接&gt;&#xA;chmod +x /usr/local/bin/mihomo&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然後新建&lt;code&gt;/etc/systemd/system/mihomo.service&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-systemd&#34;&gt;[Unit]&#xA;Description=mihomo Daemon, Another Clash Kernel.&#xA;After=network.target NetworkManager.service systemd-networkd.service iwd.service&#xA;&#xA;[Service]&#xA;Type=simple&#xA;LimitNPROC=500&#xA;LimitNOFILE=1000000&#xA;CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE&#xA;AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE&#xA;Restart=always&#xA;ExecStartPre=/usr/bin/sleep 1s&#xA;ExecStart=/usr/local/bin/mihomo -d /etc/mihomo&#xA;ExecReload=/bin/kill -HUP $MAINPID&#xA;&#xA;[Install]&#xA;WantedBy=multi-user.target&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;使用&lt;code&gt;systemctl daemon-reload&lt;/code&gt;重新加載&lt;code&gt;systemd&lt;/code&gt;，此時還沒有配置文件，不能直接啟動內核，但可以使用&lt;code&gt;systemctl enable mihomo&lt;/code&gt;讓內核開機自啟，方便後續使用。&lt;/p&gt;&#xA;&lt;h2 id=&#34;配置文件&#34;&gt;配置文件&#xA;&lt;/h2&gt;&lt;p&gt;內核啟動時會加載&lt;code&gt;/etc/mihomo/config.yaml&lt;/code&gt;，沒有了黑箱一樣的礙事「客戶端」，配置文件可以隨心所欲的定製。部分機場會下發完整的配置文件，直接用&lt;code&gt;curl&lt;/code&gt;下載即可。&lt;/p&gt;&#xA;&lt;p&gt;對於訂閱的管理，我目前使用 Substore，我之前分享過&lt;a class=&#34;link&#34; href=&#34;https://blog.l3zc.com/2025/03/clash-subscription-convert/&#34; &gt;「最速 Substore 訂閱管理指南」&lt;/a&gt;，可以直接參考這篇文章來定製自己的訂閱。為了實現純內核啟動，現在我的 Substore &lt;a class=&#34;link&#34; href=&#34;https://github.com/powerfullz/override-rules/blob/main/convert.js&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;JS 格式覆寫&lt;/a&gt;已經加入了&lt;code&gt;full&lt;/code&gt;參數，可以生成完整的配置文件，包括各種端口設置、統一延遲和外部控制器等，開箱即用。&lt;/p&gt;&#xA;&lt;p&gt;在 Substore 配置完成以後便可以下載配置文件並啟動內核：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;sudo curl -o /etc/mihomo/config.yaml 配置文件鏈接&#xA;sudo systemctl start mihomo&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;自定義覆寫&#34;&gt;自定義覆寫&#xA;&lt;/h3&gt;&lt;p&gt;我的配置文件不能滿足你的所有需求？沒問題！你可以自己添加覆寫，想要什麼加什麼。&lt;/p&gt;&#xA;&lt;p&gt;我用過各種覆寫規則，後來也開始自己從零開始編寫覆寫規則，即使使用自己的覆寫規則能滿足 99% 的場景，但有極個別的域名還是會在規則中有遺漏，更不用說用別人寫的覆寫規則了。這些遺漏的規則大多是形如我服務器的非標準端口 SSH 的前置代理等相對隱私的規則，直接上傳到 Github 公開顯然不太合適，那麼在自定義規則的基礎上再添加覆寫就成了唯一的選擇。&lt;/p&gt;&#xA;&lt;p&gt;好在 Substore 可以添加多個腳本操作，只需要在生成配置文件時額外添加一個腳本操作就能解決我們的問題。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-js&#34;&gt;function main(config) {&#xA;  config[&#34;rules&#34;].unshift(&#34;DOMAIN-SUFFIX,xxx,DIRECT&#34;)&#xA;  return config&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;需要注意的是覆寫&lt;code&gt;rules&lt;/code&gt;時必須要使用&lt;code&gt;.unshift()&lt;/code&gt;放在最前面，而不是用&lt;code&gt;.push()&lt;/code&gt;放到最後面，因為放在&lt;code&gt;MATCH&lt;/code&gt;後面的規則是永遠都匹配不到的。&lt;/p&gt;&#xA;&lt;h3 id=&#34;自定義配置文件&#34;&gt;自定義配置文件&#xA;&lt;/h3&gt;&lt;p&gt;完全不喜歡我的覆寫規則？不會用/不喜歡用 Substore？沒問題！你也可以參考 &lt;a class=&#34;link&#34; href=&#34;https://wiki.metacubex.one/config/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Mihomo 文檔&lt;/a&gt;，自己從頭開始手搓配置文件：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;mode: rule&#xA;mixed-port: 7890&#xA;redir-port: 7892&#xA;tproxy-port: 7893&#xA;allow-lan: true&#xA;log-level: info&#xA;ipv6: true&#xA;external-controller: 127.0.0.1:8000&#xA;# secret: yoursecret&#xA;unified-delay: true&#xA;routing-mark: 7894&#xA;tcp-concurrent: true&#xA;disable-keep-alive: true # 推薦在給移動設備代理時啟用，可以解決待機異常耗電的問題&#xA;&#xA;dns:&#xA;  # 你的 DNS 配置&#xA;&#xA;sniffer:&#xA;  # 你的域名嗅探配置&#xA;&#xA;geodata-mode: true&#xA;geox-url:&#xA;  # 自定義 Geodata 文件 URL&#xA;&#xA;proxy-providers:&#xA;  # 你的機場訂閱&#xA;&#xA;rule-providers:&#xA;  # 外部規則&#xA;&#xA;rules:&#xA;  # 分流規則&#xA;&#xA;proxy-groups:&#xA;  # 自定義代理分組&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;管理面板&#34;&gt;管理面板&#xA;&lt;/h2&gt;&lt;p&gt;管理面板可以根據個人喜好選擇，以 Zashboard 為例，我在使用 mihomo 自帶的&lt;code&gt;external-ui&lt;/code&gt;時遇到了一些莫名其妙的問題，所以乾脆直接運行一個 Docker 容器，畢竟這東西就真的只是一個 Web 面板，只要確保內核 API 使用 HTTP 時，Web 面板也使用 HTTP 即可，如果此時 Web 面板使用 HTTPS，則會因為 CORS 策略問題無法連接。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ mkdir zashboard &amp;&amp; cd zashboard&#xA;$ nano compose.yml&#xA;$ docker compose up -d&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;compose.yml&lt;/code&gt;內容：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;services:&#xA;  zashboard:&#xA;    image: ghcr.io/zephyruso/zashboard:latest&#xA;    ports:&#xA;      - &#34;8899:80&#34;&#xA;    restart: &#34;unless-stopped&#34;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;自動維護&#34;&gt;自動維護&#xA;&lt;/h2&gt;&lt;p&gt;內核已經運行起來，自動更新訂閱這種功能怎麼實現？&lt;/p&gt;&#xA;&lt;p&gt;答曰：自行編寫一個 Shell 腳本，配合 Crontab 即可實現自動更新訂閱的功能。例如我希望每天凌晨 3 點自動更新訂閱並重啟服務，遂編寫&lt;code&gt;/etc/mihomo/auto_update.sh&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;#!/bin/bash&#xA;&#xA;# === 配置信息 ===&#xA;CONFIG_URL=&#34;&#34;&#xA;CONFIG_PATH=&#34;/etc/mihomo/config.yaml&#34;&#xA;BACKUP_DIR=&#34;/etc/mihomo&#34;&#xA;BACKUP_PREFIX=&#34;config.yaml&#34;&#xA;MAX_BACKUPS=7&#xA;TMP_PATH=&#34;/tmp/config.yaml.tmp&#34;&#xA;LOG_FILE=&#34;/var/log/mihomo_update.log&#34;&#xA;&#xA;# === 日誌 ===&#xA;log() {&#xA;    echo &#34;$(date &#39;+%F %T&#39;) $1&#34; | tee -a &#34;$LOG_FILE&#34;&#xA;}&#xA;&#xA;# === 備份現有配置，並自動清理舊備份 ===&#xA;backup_config() {&#xA;    if [ -f &#34;$CONFIG_PATH&#34; ]; then&#xA;        backup_file=&#34;$BACKUP_DIR/${BACKUP_PREFIX}.$(date &#39;+%Y%m%d_%H%M%S&#39;).bak&#34;&#xA;        cp &#34;$CONFIG_PATH&#34; &#34;$backup_file&#34;&#xA;        log &#34;配置文件已備份到 $backup_file&#34;&#xA;        # 清理多餘的備份，只保留最新的 $MAX_BACKUPS 個&#xA;        old_backups=$(ls -1t $BACKUP_DIR/${BACKUP_PREFIX}.*.bak 2&gt;/dev/null | tail -n +$(($MAX_BACKUPS+1)))&#xA;        for f in $old_backups; do&#xA;            rm -f &#34;$f&#34; &amp;&amp; log &#34;已刪除舊備份 $f&#34;&#xA;        done&#xA;    else&#xA;        log &#34;未找到現有配置文件，無需備份&#34;&#xA;    fi&#xA;}&#xA;&#xA;# === 下載新配置 ===&#xA;download_config() {&#xA;    log &#34;開始下載新配置...&#34;&#xA;    curl -fsSL -o &#34;$TMP_PATH&#34; &#34;$CONFIG_URL&#34;&#xA;    if [ $? -ne 0 ]; then&#xA;        log &#34;下載配置失敗，請檢查網絡或地址&#34;&#xA;        return 1&#xA;    fi&#xA;    # 基本校驗：檢測文件體積&#xA;    if [ ! -s &#34;$TMP_PATH&#34; ]; then&#xA;        log &#34;下載文件為空，停止更新&#34;&#xA;        return 2&#xA;    fi&#xA;    log &#34;配置下載完成&#34;&#xA;    return 0&#xA;}&#xA;&#xA;# === 更新配置文件 ===&#xA;replace_config() {&#xA;    mv &#34;$TMP_PATH&#34; &#34;$CONFIG_PATH&#34;&#xA;    log &#34;配置文件已更新&#34;&#xA;}&#xA;&#xA;# === 重啟 mihomo 服務 ===&#xA;restart_service() {&#xA;    systemctl restart mihomo&#xA;    if [ $? -eq 0 ]; then&#xA;        log &#34;mihomo 服務已重啟&#34;&#xA;    else&#xA;        log &#34;mihomo 服務重啟失敗，請手動檢查&#34;&#xA;    fi&#xA;}&#xA;&#xA;main() {&#xA;    backup_config&#xA;&#xA;    download_config&#xA;    DL_STATUS=$?&#xA;    if [ &#34;$DL_STATUS&#34; -ne 0 ]; then&#xA;        log &#34;操作終止：配置文件未更新，保留原有配置&#34;&#xA;        exit 1&#xA;    fi&#xA;&#xA;    replace_config&#xA;&#xA;    restart_service&#xA;&#xA;    log &#34;=== 更新流程完成 ===&#34;&#xA;}&#xA;&#xA;main &#34;$@&#34;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;使用&lt;code&gt;crontab -e&lt;/code&gt;編輯 Crontab，設置如下 Crontab 即可在每天凌晨三點自動更新配置：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-crontab&#34;&gt;0 3 * * * /etc/mihomo/update_config.sh&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;防火牆&#34;&gt;防火牆&#xA;&lt;/h2&gt;&lt;p&gt;手動配置防火牆把流量劫持到 Mihomo 內核其實並不是什麼難事，我之前在「&lt;a class=&#34;link&#34; href=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/#%E5%9C%A8-exit-node-%E5%8A%AB%E6%8C%81%E6%B5%81%E9%87%8F&#34; &gt;從入門到進階：Tailscale + ShellCrash 異地組網和科學上網&lt;/a&gt;」（以下簡稱「Tailscale 那篇文章」中的兩個小節中提到過具體的操作方法，這裡只提操作，不做解說。&lt;/p&gt;&#xA;&lt;p&gt;根據我的情況，我需要把&lt;code&gt;tailscale0&lt;/code&gt;上的網卡的所有流量劫持到 Mihomo 內核，其它的情況（例如本機代理）操作也大同小異。如果只是想劫持 TCP 流量，那麼用&lt;code&gt;iptables&lt;/code&gt;的 REDIRECT 功能已經足夠，但若還想劫持 UDP、QUIC 等流量，則必須用到 Tproxy。&lt;strong&gt;最後，不要忘了 IPV6。&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;以下是我的防火牆配置：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# 創建自定義鏈&#xA;iptables -t mangle -N MIHOMO&#xA;&#xA;# 根據自己的需要忽略本地流量&#xA;iptables -t mangle -A MIHOMO -d 127.0.0.1/8 -j RETURN&#xA;iptables -t mangle -A MIHOMO -d 100.64.0.0/10 -j RETURN&#xA;iptables -t mangle -A MIHOMO -d 192.168.1.0/24 -j RETURN&#xA;iptables -t mangle -A MIHOMO -d 172.17.0.0/16 -j RETURN&#xA;&#xA;# mark UDP 和 TCP 到代理&#xA;iptables -t mangle -A MIHOMO -p tcp -j TPROXY --on-port 7893 --tproxy-mark 233&#xA;iptables -t mangle -A MIHOMO -p udp -j TPROXY --on-port 7893 --tproxy-mark 233&#xA;&#xA;# 接口跳轉&#xA;iptables -t mangle -A PREROUTING -i tailscale0 -j MIHOMO&#xA;&#xA;# 路由表配置&#xA;echo &#34;233 mihomo&#34; | tee -a /etc/iproute2/rt_tables&#xA;ip rule add fwmark 233 lookup mihomo&#xA;ip route add local 0.0.0.0/0 dev lo table mihomo&#xA;&#xA;# IPv6&#xA;# 創建鏈&#xA;ip6tables -t mangle -N MIHOMO6&#xA;&#xA;# 跳過本地地址&#xA;ip6tables -t mangle -A MIHOMO6 -d ::1/128 -j RETURN&#xA;ip6tables -t mangle -A MIHOMO6 -d fd7a:115c:a1e0::/48 -j RETURN&#xA;&#xA;# 標記 TCP/UDP&#xA;ip6tables -t mangle -A MIHOMO6 -i tailscale0 -p tcp -j TPROXY --on-port 7893 --tproxy-mark 233&#xA;ip6tables -t mangle -A MIHOMO6 -i tailscale0 -p udp -j TPROXY --on-port 7893 --tproxy-mark 233&#xA;&#xA;# 接口跳轉&#xA;ip6tables -t mangle -A PREROUTING -i tailscale0 -j MIHOMO6&#xA;&#xA;# 路由表配置&#xA;echo &#34;233 mihomo&#34; | tee -a /etc/iproute2/rt_tables&#xA;ip -6 rule add fwmark 233 lookup mihomo&#xA;ip -6 route add local ::/0 dev lo table mihomo&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;大多數系統默認不會保存防火牆規則，關於規則持久化的內容我已經分別在 Tailscale 那篇文章的「&lt;a class=&#34;link&#34; href=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/#%E8%B7%AF%E7%94%B1%E8%A7%84%E5%88%99%E6%8C%81%E4%B9%85%E5%8C%96&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;路由規則持久化&lt;/a&gt;」和「&lt;a class=&#34;link&#34; href=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/#iptables-%E8%A7%84%E5%88%99%E6%8C%81%E4%B9%85%E5%8C%96&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;iptables 規則持久化&lt;/a&gt;」兩小節做了詳細說明，直接參考即可。&lt;/p&gt;&#xA;&lt;h3 id=&#34;本機代理怎麼配置&#34;&gt;本機代理怎麼配置？&#xA;&lt;/h3&gt;&lt;p&gt;大多數代理軟件默認的配置（其實就是 ShellCrash 的默認配置）是 REDIRECT，用它也基本能滿足大多數需求，REDIRECT 的示例如下：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# IPv4 劫持 eth0&#xA;iptables -t nat -A PREROUTING -i eth0 -p tcp -j REDIRECT --to-ports 7892&#xA;&#xA;# IPv6 劫持 eth0&#xA;ip6tables -t nat -A PREROUTING -i eth0 -p tcp -j REDIRECT --to-ports 7892&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其中 7892 要和 Mihomo 配置中的&lt;code&gt;redir-port&lt;/code&gt;一致，&lt;code&gt;eth0&lt;/code&gt;就是想要劫持的 Interface，相比 Tproxy 那可真是簡單太多了。&lt;/p&gt;&#xA;&lt;p&gt;實際上我個人並不喜歡用把本機所有流量都劫持到防火牆，我更喜歡在需要時直接通過環境變量、&lt;code&gt;proxy-chains&lt;/code&gt;和各種軟件自帶的代理配置把流量指向 Mihomo 內核，例如想讓 Docker 走代理，則可以直接編輯 Docker 的&lt;code&gt;/etc/docker/daemon.json&lt;/code&gt;來指定代理，而不是直接一股腦把網卡上的所有流量都劫持到代理：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{&#xA;  &#34;proxies&#34;: {&#xA;    &#34;http-proxy&#34;: &#34;http://127.0.0.1:7890&#34;,&#xA;    &#34;https-proxy&#34;: &#34;http://127.0.0.1:7890&#34;,&#xA;    &#34;no-proxy&#34;: &#34;127.0.0.0/8&#34;&#xA;  }&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;這麼折騰何必呢用客戶端不香嗎&#34;&gt;這麼折騰，何必呢？用客戶端不香嗎？&#xA;&lt;/h2&gt;&lt;p&gt;別問，問就是玩虛空終端玩的。&lt;/p&gt;&#xA;</description>
        </item><item>
            <title>從入門到進階：Tailscale &#43; ShellCrash 異地組網和科學上網</title>
            <link>https://blog.l3zc.com/zh-hant-tw/2025/04/tailscale-setup-recap/</link>
            <pubDate>Mon, 14 Apr 2025 10:10:25 +0000</pubDate>
            <guid>https://blog.l3zc.com/zh-hant-tw/2025/04/tailscale-setup-recap/</guid>
            <description>&lt;img src=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/tailscale_hu_62168b00e213c4a1.webp&#34; alt=&#34;Featured image of post 從入門到進階：Tailscale + ShellCrash 異地組網和科學上網&#34; /&gt;&lt;p&gt;在幾次旅途中，我試圖將新拍的照片上傳到長沙家中的 Immich 服務器。過去我用 Cloudflare Tunnel 做反代，但由於國內特殊的網絡環境，連接慢速、頻繁中斷、上傳失敗幾乎成了常態。後來我開始嘗試 Tailscale —— 一個基於 WireGuard 的內網穿透工具，它終於讓我在全國各地都能穩定、高速地訪問家中的 NAS 和照片庫。&lt;/p&gt;&#xA;&lt;p&gt;但新的問題也隨之而來：Tailscale 在 Android 手機上運行時，需要作為 VPN 服務接管系統流量，而我平時使用的 Clash 同樣依賴 VPN 接口進行分流。由於 Android 系統限制（只能啟用一個 VPN 服務），兩者無法共存，導致我必須在「訪問家中服務」和「科學上網」之間二選一。&lt;/p&gt;&#xA;&lt;p&gt;這篇文章從 Tailscale 的原理講起，到自建 DERP 服務器優化連接質量，再到如何用 iptables 劫持 Tailscale Exit Node 的流量並轉發給 ShellCrash，實現流量的靈活轉發與安全穿透。無論你是想訪問家庭局域網上的 NAS、照片庫，還是希望在陌生網絡中保護自己的數據安全，這篇文章都能為你提供一套實用、穩定的解決方案。&lt;/p&gt;&#xA;&lt;h2 id=&#34;什麼是-tailscale&#34;&gt;什麼是 Tailscale&#xA;&lt;/h2&gt;&lt;p&gt;Tailscale 是一款基於 WireGuard 協議的零配置虛擬局域網工具，它能夠讓分佈在不同網絡環境中的設備像處於同一個安全內網中一樣互聯互通。通過自動穿透 NAT、防火牆等網絡障礙，Tailscale 讓你無需公網 IP、無需端口轉發，也能輕鬆訪問家中的 NAS、個人服務器、開發環境等內網資源。它的核心優勢在於簡單、安全、穩定，啟動即用，數據傳輸全程加密，適合個人開發者、遠程辦公者、家庭用戶等多種場景使用。&lt;/p&gt;&#xA;&lt;p&gt;Tailscale 的技術實現非常巧妙：其構建在 WireGuard 加密協議之上，卻顛覆了傳統 VPN 的 IP 分配邏輯。每個設備通過 SSO/OAuth2 完成身份認證後，會獲得一個終身綁定的節點密鑰。這種基於身份的組網模式，讓「長沙的 NAS」和「香港的手機」在虛擬網絡中如同辦公室同事般直接對話。&lt;/p&gt;&#xA;&lt;h2 id=&#34;tailscale-的連接過程原理&#34;&gt;Tailscale 的連接過程原理&#xA;&lt;/h2&gt;&lt;h3 id=&#34;中心控制服務器control-server&#34;&gt;中心控制服務器（Control Server）&#xA;&lt;/h3&gt;&lt;p&gt;每個 Tailscale 客戶端在啟動後，首先會連接控制服務器（controlplane），進行身份驗證，並拉取整個網絡中其他節點的信息，包括每臺設備的公網 IP、端口、NAT 類型等。這一步相當於是「認識朋友」。&lt;/p&gt;&#xA;&lt;p&gt;Tailscale 的控制服務器不會轉發任何數據，只負責協調連接 —— 類似一個調度中心。&lt;/p&gt;&#xA;&lt;h3 id=&#34;derp-服務器&#34;&gt;DERP 服務器&#xA;&lt;/h3&gt;&lt;p&gt;說到 Tailscale 能保持高連接成功率的關鍵，就不得不提到 Tailscale 自研的中轉協議 DERP，在 Tailscale 的網絡架構裡，DERP（Designated Encrypted Relay for Packets）是一個很重要但通常只在必要時介入的組件。簡單來說，它就是一個基於 HTTP 的加密中繼服務器，用來在兩臺設備無法直接通信時，作為它們之間的「中轉站」。&lt;/p&gt;&#xA;&lt;p&gt;所有客戶端之間的連接都是先選擇 DERP 模式（中繼模式），這意味著連接立即就能建立，用戶無需等待。然後連接雙方開始並行地進行路徑發現，通常幾秒鐘之後，Tailscale 就能發現一條更優路徑，然後將現有連接透明升級（upgrade）過去，變成點對點連接（直連）。&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#xA;&lt;p&gt;需要注意的是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;所有通過 DERP 的數據都是端到端加密的，DERP 服務器無法查看內容；&lt;/li&gt;&#xA;&lt;li&gt;Tailscale 會盡可能少地使用 DERP，一旦直連建立成功，就會自動切換過去；&lt;/li&gt;&#xA;&lt;li&gt;官方部署了多個分佈式 DERP 節點，客戶端會自動選擇延遲最低的那個；&lt;/li&gt;&#xA;&lt;li&gt;你也可以自建 DERP 節點（比如在國內），來解決延遲高或連接不穩定的問題。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;可以把 DERP 理解為一個兜底機制，雖然性能不如直連，但確保了即便不能打洞成功，設備之間也始終能保持連接。&lt;/p&gt;&#xA;&lt;h3 id=&#34;nat-穿透nat-traversal&#34;&gt;NAT 穿透（NAT Traversal）&#xA;&lt;/h3&gt;&lt;p&gt;拿到對端的地址信息後，Tailscale 會嘗試通過 NAT 穿透來建立點對點（P2P）連接。這個過程使用了 STUN 協議，雙方互相發送探測包，嘗試在 NAT 路由器上打出一條直連的通道。如果雙方的網絡條件允許，就可以成功建立起一個 UDP 的直連隧道，數據走直連，速度快、延遲低。&lt;/p&gt;&#xA;&lt;p&gt;受制於篇幅，我無法完整細緻的講述 NAT 穿透的原理，若對這部分感興趣，可以閱讀 Tailscale 官方的「&lt;a class=&#34;link&#34; href=&#34;https://tailscale.com/blog/how-nat-traversal-works&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;How NAT traversal works&lt;/a&gt;」一文。&lt;/p&gt;&#xA;&lt;h3 id=&#34;完整連接流程圖示&#34;&gt;完整連接流程圖示&#xA;&lt;/h3&gt;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;    A[設備 A 啟動 Tailscale] --&gt; B[通過 DERP 服務器建立初始連接]&#xA;    B --&gt; C[交換網絡信息和 WireGuard 密鑰]&#xA;    C --&gt; D[雙方並行進行 NAT 類型探測]&#xA;    D --&gt; E{能否直連？}&#xA;    E -- 是 --&gt; F[建立 P2P 直連隧道]&#xA;    F --&gt; G[定期檢測連接質量]&#xA;    G --&gt; H{直連優於 DERP？}&#xA;    H -- 是 --&gt; I[切換大部分流量至直連通道]&#xA;    H -- 否 --&gt; J[繼續通過 DERP 轉發部分或全部流量]&#xA;    E -- 否 --&gt; J&#xA;&#xA;    style B fill:#e3f2fd,stroke:#2196f3,color:#000&#xA;    style F fill:#e8f5e9,stroke:#4caf50,color:#000&#xA;    style J fill:#fff3e0,stroke:#ff9800,color:#000&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;自建-derp&#34;&gt;自建 DERP&#xA;&lt;/h2&gt;&lt;p&gt;Tailscale 的安裝在各個平臺上都相對簡單，官方文檔已經提供了詳細的操作指南。本文將不再贅述安裝過程，以下內容默認你已經在相關設備上成功安裝並登錄了 Tailscale。&lt;/p&gt;&#xA;&lt;h3 id=&#34;為什麼要自建-derp&#34;&gt;為什麼要自建 DERP？&#xA;&lt;/h3&gt;&lt;p&gt;Tailscale 在全球部署了眾多 DERP &lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;中繼服務器，用於在打洞失敗時接管流量中轉。但由於眾所周知的原因，中國大陸並沒有官方部署的 DERP 節點。這意味著：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;一旦 NAT 打洞失敗，所有流量都必須繞行海外的 DERP 節點，延遲高、速度慢，體驗極差；&lt;/li&gt;&#xA;&lt;li&gt;某些官方 DERP 節點容易被 GFW 干擾，可能出現連接中斷、握手失敗等問題；&lt;/li&gt;&#xA;&lt;li&gt;即使打洞成功，Tailscale 仍需通過 DERP 交換路由信息和 WireGuard 密鑰，如果 DERP 不可達，連接質量也會受到影響。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;因此，在國內網絡環境下，自建一個本地 DERP 節點，不僅可以顯著提高連接穩定性和傳輸性能，還能規避部分網絡封鎖所帶來的不可預期問題，是一個非常值得做的優化。&lt;/p&gt;&#xA;&lt;h3 id=&#34;準備工作&#34;&gt;準備工作&#xA;&lt;/h3&gt;&lt;p&gt;前面提到，DERP 是基於 HTTP 的，所以你需要準備好一個 HTTP 反代服務，並自行解決 SSL 證書的簽發等基礎問題。本文使用 Docker 部署，在部署開始之前，你需要在你的服務器上裝好 Docker 以及 Docker Compose 等附加組件。為了編輯配置文件，你當然也得知道如何使用 nano 之類的編輯器。為了最好的效果，你的服務器最好擁有靜態公網 IPv4+IPv6 雙棧地址。&lt;/p&gt;&#xA;&lt;p&gt;如果以上條件都具備，就可以開始部署了。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;mkdir tailscale-derp &amp;&amp; cd tailscale-derp&#xA;nano docker-compose.yml&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;docker-composeyml&#34;&gt;docker-compose.yml&#xA;&lt;/h3&gt;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;services:&#xA;  derper:&#xA;    name: tailscale-derp&#xA;    image: fredliang/derper&#xA;    environment:&#xA;      - DERP_DOMAIN=derp.nightcity.pub&#xA;      - DERP_VERIFY_CLIENTS=true&#xA;      - DERP_ADDR=:4433&#xA;    network_mode: host&#xA;    restart: unless-stopped&#xA;    volumes:&#xA;      - &#34;/var/run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock&#34;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;network_mode: host&lt;/code&gt;是一個關鍵配置，表示容器將共享宿主機的網絡棧。如果使用 Docker 默認的 bridge 網絡模式，容器的網絡會經過 Docker 內網轉發，會造成 DERP 的 STUN 服務識別到 Docker &lt;code&gt;172.17.0.0/16&lt;/code&gt;網段下的地址，而&#xA;無法識別到客戶端正確的外網地址，導致 Tailscale 客戶端無法正確連接。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;volumes:&#xA;  - &#34;/var/run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock&#34;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;前面提到所有通過 DERP 的數據都是端到端加密的，DERP 並不知道是誰在使用，這意味著如果不採取措施，任何知道你 DERP 服務器地址和端口號的人都可以使用它。這條配置的作用是掛載宿主機的 Tailscale 套接字文件到容器內，目的是允許 derper 服務通過 Tailscale 的 tailscaled 服務進行身份驗證。配合&lt;code&gt;DERP_VERIFY_CLIENTS=true&lt;/code&gt;，可以防止你的 DERP 節點被他人白嫖。&lt;/p&gt;&#xA;&lt;p&gt;需要注意的是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;宿主機必須已經安裝並登陸 Tailscale 客戶端（tailscaled），否則這個文件不存在，容器會報錯；&lt;/li&gt;&#xA;&lt;li&gt;tailscaled 必須以 root 權限運行，才能創建這個 sock 文件。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;反向代理&#34;&gt;反向代理&#xA;&lt;/h3&gt;&lt;p&gt;以 Caddy 為例，需要反向代理 4433 端口，併為其部署 SSL 證書。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-caddyfile&#34;&gt;{&#xA;        email webmaster@l3zc.com&#xA;}&#xA;&#xA;*.l3zc.com {&#xA;        encode gzip&#xA;&#xA;        tls {&#xA;                dns dnspod APP_ID,APP_KEY&#xA;                resolvers 119.29.29.29 223.5.5.5&#xA;        }&#xA;&#xA;        @derp host derp.l3zc.com&#xA;        reverse_proxy @derp :4433&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;需要注意的是，如果你和我一樣使用 Caddy 配合 dnsproviders 申請泛域名證書，Tailscale 的 MagicDNS 可能會導致 Caddy 本地證書驗證失敗而報錯，需要手動指定&lt;code&gt;resolvers&lt;/code&gt;參數解決。&lt;/p&gt;&#xA;&lt;p&gt;訪問剛剛反代的節點，如果出現以下頁面，說明配置正確。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/image_hu_8846b1a6f5d79837.webp&#34; alt=&#34;DERP 搭建成功&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h3 id=&#34;配置-acl-策略&#34;&gt;配置 ACL 策略&#xA;&lt;/h3&gt;&lt;p&gt;打開 Tailscale 控制檯的「&lt;a class=&#34;link&#34; href=&#34;https://login.tailscale.com/admin/acls/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Access Controls&lt;/a&gt;」頁面配置 ACL 策略，將配置好的 DERP 加上。&lt;/p&gt;&#xA;&lt;p&gt;Tailscale 的 ACL 策略是用 HuJSON&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; 寫的，想要在 VSCode 中編輯，選擇語言為「JSON with Comments（jsonc）」即可。以下是一個配置示例：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-jsonc&#34;&gt;{&#xA;  &#34;acls&#34;: [{ &#34;action&#34;: &#34;accept&#34;, &#34;src&#34;: [&#34;*&#34;], &#34;dst&#34;: [&#34;*:*&#34;] }],&#xA;  &#34;ssh&#34;: [&#xA;    {&#xA;      &#34;action&#34;: &#34;check&#34;,&#xA;      &#34;src&#34;: [&#34;autogroup:member&#34;],&#xA;      &#34;dst&#34;: [&#34;autogroup:self&#34;],&#xA;      &#34;users&#34;: [&#34;autogroup:nonroot&#34;, &#34;root&#34;]&#xA;    }&#xA;  ],&#xA;&#xA;  // 自建 DERP 配置&#xA;  &#34;derpMap&#34;: {&#xA;    &#34;OmitDefaultRegions&#34;: false, // 改為 true 以排除官方 DERP&#xA;    &#34;Regions&#34;: {&#xA;      &#34;900&#34;: {&#xA;        &#34;RegionID&#34;: 900,&#xA;        &#34;RegionCode&#34;: &#34;sha&#34;,&#xA;        &#34;RegionName&#34;: &#34;Shanghai&#34;,&#xA;        &#34;Nodes&#34;: [&#xA;          {&#xA;            &#34;Name&#34;: &#34;myderp&#34;,&#xA;            &#34;RegionID&#34;: 900,&#xA;            &#34;HostName&#34;: &#34;derp.l3zc.com&#34;&#xA;          }&#xA;        ]&#xA;      }&#xA;    }&#xA;  }&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Tailscale 保留&lt;code&gt;RegionID&lt;/code&gt;中的 1-899 作為官方節點，自建節點的 RegionID 必須大於等於 900。&lt;/p&gt;&#xA;&lt;h3 id=&#34;測試連接&#34;&gt;測試連接&#xA;&lt;/h3&gt;&lt;p&gt;配置好 ACL 並保存，Tailscale 會自動為所有客戶端同步配置，稍等片刻在客戶端用&lt;code&gt;tailscale netcheck&lt;/code&gt;測試連接。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/image-1_hu_25ccc2304c1a66fd.webp&#34; alt=&#34;用 tailscale netcheck 測試連接&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;需要注意返回的 IP 是否是自己真實的公網 IP，若返回了&lt;code&gt;172.17.0.0/16&lt;/code&gt;網段的地址，說明你 Docker 部分配置錯了。&lt;/p&gt;&#xA;&lt;h2 id=&#34;與科學上網並存在-exit-node-劫持流量到-clash-內核&#34;&gt;與科學上網並存：在 Exit Node 劫持流量到 Clash 內核&#xA;&lt;/h2&gt;&lt;h3 id=&#34;聲明-exit-node&#34;&gt;聲明 Exit Node&#xA;&lt;/h3&gt;&lt;p&gt;在家中準備一個 24 小時開機的設備，可以是樹莓派，可以是 MacMini。在上面安裝 Tailscale 並將其聲明為 Exit Node，並根據需要在 Tailscale 內網聲明家庭內網網段，隨後在控制檯啟用這個設備作為 Exit Node，你就獲得了一個免費的 VPN，可以讓你在陌生的網絡環境中保持安全。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;sudo tailscale up --advertise-exit-node --advertise-routes 192.168.1.0/24&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/image-2_hu_bac8de7f1a5258d0.webp&#34; alt=&#34;找到 Route Settings&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/Snipaste_2025-04-13_21-20-09_hu_d8313f59e1940d09.webp&#34; alt=&#34;啟用相關設置&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;廣播完成後，無論身處何地，只要能連上 Tailscale 網絡，就能訪問家中所有的內網設備。&lt;/p&gt;&#xA;&lt;h3 id=&#34;啟用-ip-轉發和禁用-udp-gro&#34;&gt;啟用 IP 轉發和禁用 UDP GRO&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;&#xA;&lt;/h3&gt;&lt;p&gt;啟用 IP 轉發是樹莓派等設備作為 Exit Node 所必須的配置，這裡以樹莓派為例，如果你使用其他設備，請自行查閱 Tailscale 官網教程。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;echo &#39;net.ipv4.ip_forward = 1&#39; | sudo tee -a /etc/sysctl.d/99-tailscale.conf&#xA;echo &#39;net.ipv6.conf.all.forwarding = 1&#39; | sudo tee -a /etc/sysctl.d/99-tailscale.conf&#xA;sudo sysctl -p /etc/sysctl.d/99-tailscale.conf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;根據 Tailscale 官方的說法&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;，禁用 UDP GRO&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt; 可以提升轉發性能，但官方的持久化教程似乎在樹莓派上無效，好在我們可以手動配置。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;# 安裝 ethtool&#xA;sudo apt update &amp;&amp; sudo apt install ethtool -y&#xA;&#xA;# 關閉 UDP GRO&#xA;sudo ethtool -K eth0 gro off&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;針對持久化的問題手動編寫 systemd 配置文件：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# 創建服務文件&#xA;sudo nano /etc/systemd/system/ethtool.service&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;文件內容如下：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-systemd&#34;&gt;[Unit]&#xA;Description=Configure eth0 GRO&#xA;After=network.target&#xA;&#xA;[Service]&#xA;Type=oneshot&#xA;ExecStart=/sbin/ethtool -K eth0 gro off&#xA;&#xA;[Install]&#xA;WantedBy=multi-user.target&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;隨後啟動服務：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;sudo systemctl daemon-reload&#xA;sudo systemctl enable ethtool&#xA;sudo systemctl start ethtool&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;在-exit-node-劫持流量&#34;&gt;在 Exit Node 劫持流量&#xA;&lt;/h3&gt;&lt;p&gt;我的 Exit Node 是一臺樹莓派，在樹莓派上配置好 Exit Node 之後就來到了最後一步，也就是劫持手機發送到 Exit Node 上的流量，實現科學上網。出於穩定性原因，我不希望在家庭主路由上直接運行代理軟件，為了實現這一點，直接在樹莓派上劫持流量是唯一的選擇。&lt;/p&gt;&#xA;&lt;p&gt;首先安裝 ShellCrash，請自行根據你的需要導入配置文件、配置自動任務等：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;export url=&#39;https://fastly.jsdelivr.net/gh/juewuy/ShellCrash@master&#39; &amp;&amp; wget -q --no-check-certificate -O /tmp/install.sh $url/install.sh  &amp;&amp; bash /tmp/install.sh &amp;&amp; source /etc/profile &amp;&gt; /dev/null&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;隨後啟動服務，修改修改防火牆運行模式為純淨模式，我個人建議將 SNI 嗅探打開，並將 DNS 模式從&lt;code&gt;fake-ip&lt;/code&gt;切換為&lt;code&gt;redir-host&lt;/code&gt;&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;，同時啟用 IPv6 透明代理。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/image-5_hu_9e9d06f0578b4304.webp&#34; alt=&#34;修改防火牆劫持範圍&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/image-3_hu_efcbb45f4cd30701.webp&#34; alt=&#34;啟用域名嗅探&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/image-6_hu_d681ee087b8dd14a.webp&#34; alt=&#34;修改端口設置&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;設置純淨模式的目的是手動配置 iptables 以實現更精準的流量劫持。我們直接用 iptables 劫持所有 tailscale 網卡作為 Exit Node 轉發的流量，首先用&lt;code&gt;ifconfig&lt;/code&gt;查看 tailscale 網卡的名稱，默認情況下一般為 Tailscale 0：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;root@raspberrypi:~# ifconfig&#xA;eth0: ......&#xA;&#xA;tailscale0: flags=4305&lt;UP,POINTOPOINT,RUNNING,NOARP,MULTICAST&gt;  mtu 1280&#xA;        inet 100.111.19.50  netmask 255.255.255.255  destination 100.111.19.50&#xA;        inet6 fd7a:115c:a1e0::3d01:1332  prefixlen 128  scopeid 0x0&lt;global&gt;&#xA;        inet6 fe80::c0d5:1a1b:2005:48eb  prefixlen 64  scopeid 0x20&lt;link&gt;&#xA;        unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 500  (UNSPEC)&#xA;        RX packets 6780155  bytes 958732442 (914.3 MiB)&#xA;        RX errors 0  dropped 0  overruns 0  frame 0&#xA;        TX packets 4811113  bytes 10858316472 (10.1 GiB)&#xA;        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;劫持&lt;code&gt;tailscale0&lt;/code&gt;網卡的所有流量到本地 Clash 內核監聽端口&lt;code&gt;7892&lt;/code&gt;，這個設置在 ShellCrash 中叫做「靜態路由端口」。以及，如果你不想和我一樣遇到莫名其妙的網絡問題，就一定不要忘記劫持 IPv6。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;# IPv4 劫持 tailscale0&#xA;iptables -t nat -A PREROUTING -i tailscale0 -p tcp -j REDIRECT --to-ports 7892&#xA;&#xA;# IPv6 劫持 tailscale0&#xA;ip6tables -t nat -A PREROUTING -i tailscale0 -p tcp -j REDIRECT --to-ports 7892&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;進階用-tproxy-劫持-udp-流量&#34;&gt;進階：用 TProxy 劫持 UDP 流量&#xA;&lt;/h3&gt;&lt;p&gt;iptables 的 REDIRECT 只能重定向 TCP 流量，UDP 沒有連接狀態（無連接協議），所以 REDIRECT 無法保留目標地址，導致透明代理無法知道原始目標地址。&lt;/p&gt;&#xA;&lt;p&gt;所以，如果你用如下方式劫持 UDP 流量：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;iptables -t nat -A PREROUTING -i tailscale0 -p udp -j REDIRECT --to-ports 7892&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;則這個規則不會生效或代理行為不正常。&lt;/p&gt;&#xA;&lt;p&gt;那麼，有辦法代理 UDP 流量嗎？有的兄弟，有的。但前提是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;核心支持 UDP 透明代理（Clash Premium 和 Mihomo 都支持）；&lt;/li&gt;&#xA;&lt;li&gt;使用 TProxy 模式，而不是 REDIRECT；&lt;/li&gt;&#xA;&lt;li&gt;正確配置了 iptables mangle 表和 policy routing；&lt;/li&gt;&#xA;&lt;li&gt;代理配置文件中啟用了 UDP 代理（如 mode: rule + udp: true）。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;假設你已經滿足第一條、第二條和最後一條，則以下是一個示例&lt;sup id=&#34;fnref:8&#34;&gt;&lt;a href=&#34;#fn:8&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;8&lt;/a&gt;&lt;/sup&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# 創建自定義鏈&#xA;sudo iptables -t mangle -N SHELLCRASH&#xA;&#xA;# 根據自己的需要忽略本地流量&#xA;sudo iptables -t mangle -A SHELLCRASH -d 127.0.0.1/8 -j RETURN&#xA;sudo iptables -t mangle -A SHELLCRASH -d 100.64.0.0/10 -j RETURN&#xA;sudo iptables -t mangle -A SHELLCRASH -d 192.168.1.0/24 -j RETURN&#xA;sudo iptables -t mangle -A SHELLCRASH -d 172.17.0.0/16 -j RETURN&#xA;&#xA;# mark UDP 和 TCP 到代理&#xA;sudo iptables -t mangle -A SHELLCRASH -p tcp -j TPROXY --on-port 7893 --tproxy-mark 233&#xA;sudo iptables -t mangle -A SHELLCRASH -p udp -j TPROXY --on-port 7893 --tproxy-mark 233&#xA;&#xA;# 接口跳轉&#xA;sudo iptables -t mangle -A PREROUTING -i tailscale0 -j SHELLCRASH&#xA;&#xA;# 路由表配置&#xA;echo &#34;233 shellcrash&#34; | sudo tee -a /etc/iproute2/rt_tables&#xA;sudo ip rule add fwmark 233 lookup shellcrash&#xA;sudo ip route add local 0.0.0.0/0 dev lo table shellcrash&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;當然，不要忘記 IPv6：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# 創建鏈&#xA;sudo ip6tables -t mangle -N SHELLCRASH6&#xA;&#xA;# 跳過本地地址&#xA;sudo ip6tables -t mangle -A SHELLCRASH6 -d ::1/128 -j RETURN&#xA;sudo ip6tables -t mangle -A SHELLCRASH6 -d fd7a:115c:a1e0::/48 -j RETURN&#xA;&#xA;# 標記 TCP/UDP&#xA;ip6tables -t mangle -A SHELLCRASH6 -i tailscale0 -p tcp -j TPROXY --on-port 7893 --tproxy-mark 233&#xA;ip6tables -t mangle -A SHELLCRASH6 -i tailscale0 -p udp -j TPROXY --on-port 7893 --tproxy-mark 233&#xA;&#xA;# 接口跳轉&#xA;sudo ip6tables -t mangle -A PREROUTING -i tailscale0 -j SHELLCRASH6&#xA;&#xA;# 路由表配置&#xA;echo &#34;233 shellcrash&#34; | sudo tee -a /etc/iproute2/rt_tables&#xA;sudo ip -6 rule add fwmark 233 lookup shellcrash&#xA;sudo ip -6 route add local ::/0 dev lo table shellcrash&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;路由規則持久化&#34;&gt;路由規則持久化&#xA;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;ip rule&lt;/code&gt;和&lt;code&gt;ip route&lt;/code&gt;創建的規則在重啟後會丟失，所以需要我們手動持久化，最簡單直接的方法就是創建一個腳本，並將其添加至 crontab。&lt;/p&gt;&#xA;&lt;p&gt;創建一個腳本：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;sudo nano /usr/local/bin/policy-route.sh&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;編輯為如下內容：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/bash&#xA;&#xA;# IPv4 策略路由&#xA;ip rule add fwmark 233 lookup 233&#xA;ip route add local 0.0.0.0/0 dev lo table 233&#xA;&#xA;# IPv6 策略路由&#xA;ip -6 rule add fwmark 233 lookup 233&#xA;ip -6 route add local ::/0 dev lo table 233&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;授予執行權限後編輯 Crontab：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;sudo chmod +x /usr/local/bin/policy-route.sh&#xA;sudo crontab -e&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在 crontab 文件底部加上如下內容：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-crontab&#34;&gt;@reboot /usr/local/bin/policy-route.sh&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;原理解釋tproxy-到底是怎麼轉發-udp-流量的&#34;&gt;原理解釋：TProxy 到底是怎麼轉發 UDP 流量的？&#xA;&lt;/h3&gt;&lt;p&gt;如果你看到這裡，也許會產生疑惑：為什麼整個流程中，我們沒有在 iptables 裡寫&lt;code&gt;--to-ports&lt;/code&gt;，也沒看到目標地址被改寫，UDP 流量就莫名其妙地被代理了？這是怎麼做到的？&lt;/p&gt;&#xA;&lt;p&gt;要解釋這個問題，我們先來看 TProxy 和 REDIRECT 的根本區別：&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;REDIRECT 模式：&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;使用 iptables nat 表；&lt;/li&gt;&#xA;&lt;li&gt;將目標地址改寫為本地地址（比如 127.0.0.1:7892）；&lt;/li&gt;&#xA;&lt;li&gt;通常用於 TCP 流量；&lt;/li&gt;&#xA;&lt;li&gt;不能保留真實目標地址；&lt;/li&gt;&#xA;&lt;li&gt;需要指定&lt;code&gt;--to-ports&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TB&#xA;    A[客戶端設備&lt;br&gt;通過 Tailscale 發起 TCP 請求]&#xA;    B[tailscale0 接口接收流量]&#xA;    C[iptables NAT PREROUTING&lt;br&gt;REDIRECT --to-ports 7892]&#xA;    D[ShellCrash 本地監聽&lt;br&gt;127.0.0.1:7892]&#xA;    E[ShellCrash 發起新 TCP 請求&lt;br&gt;→ 目標服務器]&#xA;    F[響應從網絡返回&lt;br&gt;ShellCrash 轉發響應]&#xA;    G[響應回到客戶端設備]&#xA;&#xA;    A --&gt; B --&gt; C --&gt; D --&gt; E --&gt; F --&gt; G&#xA;&#xA;    style C fill:#f9f,stroke:#aaa,stroke-width:1px,color:#000&#xA;    style D fill:#bbf,stroke:#aaa,stroke-width:1px,color:#000&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;TPROXY 模式：&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;使用 iptables mangle 表；&lt;/li&gt;&#xA;&lt;li&gt;不修改目標 IP，而是保留原始目標地址；&lt;/li&gt;&#xA;&lt;li&gt;通過 fwmark 和 policy routing 將報文路由到 &lt;code&gt;lo&lt;/code&gt;；&lt;/li&gt;&#xA;&lt;li&gt;代理程序監聽一個特殊端口（例如 7893），並啟用 &lt;code&gt;IP_TRANSPARENT&lt;/code&gt;；&lt;/li&gt;&#xA;&lt;li&gt;支持 UDP 和 TCP；&lt;/li&gt;&#xA;&lt;li&gt;不需要 iptables 內指定 &lt;code&gt;--to-ports&lt;/code&gt;，因為不是 NAT，而是標記 + 路由。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TB&#xA;    A[客戶端設備&lt;br&gt;通過 Tailscale 發起 TCP/UDP 請求]&#xA;    B[tailscale0 接口接收流量]&#xA;    C[iptables MANGLE PREROUTING&lt;br&gt;打上 fwmark 233]&#xA;    D[ip rule: fwmark 233&lt;br&gt;使用 routing table shellcrash]&#xA;    E[ip route: local 0.0.0.0/0&lt;br&gt;dev lo table shellcrash]&#xA;    F[ShellCrash 在 lo:7893 監聽&lt;br&gt;IP_TRANSPARENT 模式]&#xA;    G[ShellCrash 獲取原始目標地址&lt;br&gt;發起代理連接]&#xA;    H[響應從網絡返回&lt;br&gt;ShellCrash 轉發響應]&#xA;    I[響應回到客戶端設備]&#xA;&#xA;    A --&gt; B --&gt; C --&gt; D --&gt; E --&gt; F --&gt; G --&gt; H --&gt; I&#xA;&#xA;    style C fill:#f9f,stroke:#aaa,stroke-width:1px,color:#000&#xA;    style F fill:#bbf,stroke:#aaa,stroke-width:1px,color:#000&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;TProxy 不使用 DNAT/REDIRECT，而是通過 mangle 表給數據包打上 mark，然後通過 policy routing（&lt;code&gt;ip rule&lt;/code&gt; + &lt;code&gt;ip route&lt;/code&gt;）將這些數據包送到 &lt;code&gt;lo&lt;/code&gt; 接口。代理程序（如 Clash / ShellCrash）監聽在 &lt;code&gt;lo&lt;/code&gt;&lt;sup id=&#34;fnref:9&#34;&gt;&lt;a href=&#34;#fn:9&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;9&lt;/a&gt;&lt;/sup&gt; 上的端口（例如 7893），通過啟用 &lt;code&gt;IP_TRANSPARENT&lt;/code&gt; 選項，可以讀取數據包的原始目標 IP 和端口並進行代理轉發。&lt;/p&gt;&#xA;&lt;p&gt;簡而言之，TProxy 模式只需要：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;iptables&lt;/code&gt; 給數據包打上 mark；&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;ip rule&lt;/code&gt; + &lt;code&gt;ip route&lt;/code&gt; 將這些包送到 &lt;code&gt;lo&lt;/code&gt;；&lt;/li&gt;&#xA;&lt;li&gt;程序監聽 &lt;code&gt;lo&lt;/code&gt; 上的端口並開啟 &lt;code&gt;IP_TRANSPARENT&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;所以不需要在 iptables 中指定 &lt;code&gt;--to-ports&lt;/code&gt;，因為目標 IP 和端口保持不變，代理程序自己可以感知並處理。&lt;/p&gt;&#xA;&lt;h3 id=&#34;iptables-規則持久化&#34;&gt;iptables 規則持久化&#xA;&lt;/h3&gt;&lt;p&gt;安裝&lt;code&gt;iptables-persistent&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;sudo apt update&#xA;sudo apt install iptables-persistent&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;安裝過程中會提示你是否保存當前的 IPv4 和 IPv6 配置，選擇「是」即可。之後如果你添加了新的規則，記得執行保存命令：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# 保存當前 IPv4/IPv6 規則&#xA;sudo netfilter-persistent save&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;保存後的規則文件路徑：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;IPv4：&lt;code&gt;/etc/iptables/rules.v4&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;IPv6：&lt;code&gt;/etc/iptables/rules.v6&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;你也可以直接編輯上面的&lt;code&gt;rules.v4&lt;/code&gt;/&lt;code&gt;rules.v6&lt;/code&gt;文件，按需修改。&lt;/p&gt;&#xA;&lt;h2 id=&#34;最終效果&#34;&gt;最終效果&#xA;&lt;/h2&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/Screenshot_2025-04-14-18-05-18-259_com.tailscale._hu_783d6158c36336e1.webp&#34; alt=&#34;基本都能打洞成功&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/Screenshot_2025-04-14-18-04-45-053_com.cnspeedtes_hu_a47b6440dc775101.webp&#34; alt=&#34;速度尚可接受&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;這套方案的使用體驗取決於你家的上行帶寬，我家的網絡是下行 500M 上行 60M，目前沒有遇到一次打洞失敗的情況，所以基本都能跑滿，延遲也尚可接受，並且可以在外地隨時隨地端到端加密訪問家中的 Immich 和 OpenWRT 路由器等設備以及實現科學上網，總體來看，我還算比較滿意。&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;參考: &lt;a class=&#34;link&#34; href=&#34;https://icloudnative.io/posts/custom-derp-servers/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://icloudnative.io/posts/custom-derp-servers/&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;參考: &lt;a class=&#34;link&#34; href=&#34;https://tailscale.com/kb/1232/derp-servers&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://tailscale.com/kb/1232/derp-servers&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;有關 HuJSON: &lt;a class=&#34;link&#34; href=&#34;https://github.com/tailscale/hujson&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://github.com/tailscale/hujson&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:4&#34;&gt;&#xA;&lt;p&gt;參考： &lt;a class=&#34;link&#34; href=&#34;https://tailscale.com/kb/1408/quick-guide-exit-nodes&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://tailscale.com/kb/1408/quick-guide-exit-nodes&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:5&#34;&gt;&#xA;&lt;p&gt;參考: &lt;a class=&#34;link&#34; href=&#34;https://tailscale.com/kb/1320/performance-best-practices#linux-optimizations-for-subnet-routers-and-exit-nodes&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://tailscale.com/kb/1320/performance-best-practices#linux-optimizations-for-subnet-routers-and-exit-nodes&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:6&#34;&gt;&#xA;&lt;p&gt;UDP GRO（Generic Receive Offload）是 Linux 內核中的一種網絡優化技術，主要用於合併多個小數據包以提高處理效率。但在設備作為網絡轉發節點的使用場景下，這可能會導致轉發延遲增加和高丟包率環境下的吞吐量下降。&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:7&#34;&gt;&#xA;&lt;p&gt;相較於&lt;code&gt;fake-ip&lt;/code&gt;，&lt;code&gt;redir-host&lt;/code&gt;兼容性更好，出現問題的概率更低，也不會出現開關代理之後短時間內因為&lt;code&gt;fake-ip&lt;/code&gt;殘留而斷網的情況，所以一般情況下我建議使用&lt;code&gt;redir-host&lt;/code&gt;搭配 GeoSite 分流規則使用。我這臺樹莓派的 DNS 上游是&lt;a class=&#34;link&#34; href=&#34;https://blog.l3zc.com/2025/02/what-i-have-done-on-my-dns/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;已經配置好的 SmartDNS&lt;/a&gt;，不存在 DNS 汙染的問題，體驗良好。&amp;#160;&lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:8&#34;&gt;&#xA;&lt;p&gt;參考: &lt;a class=&#34;link&#34; href=&#34;https://blog.zonowry.com/posts/clash_iptables_tproxy/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://blog.zonowry.com/posts/clash_iptables_tproxy/&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:8&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:9&#34;&gt;&#xA;&lt;p&gt;&lt;code&gt;lo&lt;/code&gt; 是 Linux 系統中默認的「迴環接口（Loopback Interface）」，在透明代理中，它不僅處理 localhost 流量，還被用來接收原本屬於外部世界的網絡連接，實現對外部流量的本地劫持和轉發。&amp;#160;&lt;a href=&#34;#fnref:9&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
        </item><item>
            <title>Google Play 商店的國內 CDN：從密碼學入門到分流策略優化</title>
            <link>https://blog.l3zc.com/zh-hant-tw/2025/03/chinese-cdn-used-by-playstore/</link>
            <pubDate>Sat, 15 Mar 2025 13:15:01 +0000</pubDate>
            <guid>https://blog.l3zc.com/zh-hant-tw/2025/03/chinese-cdn-used-by-playstore/</guid>
            <description>&lt;img src=&#34;https://blog.l3zc.com/2025/03/chinese-cdn-used-by-playstore/cover_hu_8e92ca92eba5064b.webp&#34; alt=&#34;Featured image of post Google Play 商店的國內 CDN：從密碼學入門到分流策略優化&#34; /&gt;&lt;p&gt;改用自己的 Mihomo 覆寫規則後，手機從 Google Play 上下載應用就會一直轉圈圈，而換用機場的規則就沒問題，非常奇怪。遂調取 Mihomo 內核日誌查看。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-log&#34;&gt;play-lh.googleusercontent.com:443 match RuleSet(cdn_domainset) using 靜態資源[🇭🇰 香港 07]&#xA;play.googleapis.com:443 match GeoSite(GFW) using 節點選擇[🇭🇰 香港 07]&#xA;play-lh.googleusercontent.com:443 match RuleSet(cdn_domainset) using 靜態資源[🇭🇰 香港 07]&#xA;play-fe.googleapis.com:443 match GeoSite(GFW) using 節點選擇[🇭🇰 香港 07]&#xA;services.googleapis.cn:443 match GeoSite(CN) using 全球直連[DIRECT]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;（以上日誌已精簡）&lt;/p&gt;&#xA;&lt;p&gt;國行手機內置的 Google Play 服務使用&lt;code&gt;services.googleapis.cn&lt;/code&gt;而非&lt;code&gt;services.googleapis.com&lt;/code&gt;域名提供服務，而這個域名在大多數分流規則中都是直連，對，問題就出在這裡，修改規則把這個域名分流到代理就萬事大吉了！&lt;/p&gt;&#xA;&lt;p&gt;……萬事大吉了，嗎？&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;rr4---sn-j5o7dn7s.xn--ngstr-lra8j.com:443 match Match using 漏網之魚[🇭🇰 香港 07]&#xA;rr2---sn-j5o7dn7s.xn--ngstr-lra8j.com:443 match Match using 漏網之魚[🇭🇰 香港 07]&#xA;rr1---sn-j5o76n7z.xn--ngstr-lra8j.com:443 match Match using 漏網之魚[🇭🇰 香港 07]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;等等，為什麼每次從 Play 商店下載應用都會出現這些奇怪的域名？在網上查找一番資料後，新世界的大門就此打開。&lt;/p&gt;&#xA;&lt;h2 id=&#34;與-google-截然相反卻又異曲同工的-ångströ&#34;&gt;與 Google 截然相反卻又異曲同工的 Ångströ&#xA;&lt;/h2&gt;&lt;p&gt;看到 xn&amp;ndash;ngstr-lra8j.com，熟悉域名的同學肯定知道，這是 PunyCode 編碼，這串字符解碼後就是 ångströ.com。Anders Jonas Ångström 是一位瑞典物理學家，他是光譜學的奠基人。&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;埃斯特朗（Ångström）是為紀念他而以他的名字命名的長度單位，$ 1 Å = 10^{-10} m = \frac{1}{10} nm $，通常用於描述非常短的距離，比如原子和分子尺寸或光的波長。其代表極端小的量級，尤其是在物理學和化學中用於精細測量。&lt;/p&gt;&#xA;&lt;p&gt;雖然 Google 的名字並不是直接取自「Googol」，但是它的靈感來源於該詞。「Googol」是一個數學術語，表達$10^{100}$，即1後面跟著100個零，是一個極其龐大的數字，常用來表示計算機科學中涉及的巨大數字或信息量。&lt;/p&gt;&#xA;&lt;p&gt;在命名哲學上，Google 與Ångströ 這對看似分處光譜兩端的科技概念，卻展現出耐人尋味的對稱美學。前者源於數學概念「Googol」的創造性改寫，後者則脫胎於物理單位「Ångström」的意象重構。這兩個經過藝術化變奏的稱謂，恰似科技文明的雙螺旋：Google 之稱承載著駕馭浩如星海的數字宇宙的雄心，Ångströ 之謂則暗喻著雕琢精微的原子級技術圖景。當這兩個經過創造性變形的專業術語在硅谷相遇，恰好構成了一組完美的認知座標——既指向人類處理信息的尺度極限，也昭示著科技文明同時向宏觀與微觀進軍的壯闊征程。&lt;/p&gt;&#xA;&lt;h2 id=&#34;入門密碼學google-基礎設施域名的規律&#34;&gt;入門密碼學：Google 基礎設施域名的規律&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&#xA;&lt;/h2&gt;&lt;p&gt;OK，enough。現在讓我們把視角轉回 Google 的基礎設施。先來看一些完整的連接域名：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;rr4---sn-j5o7dn7s.xn--ngstr-lra8j.com&#xA;rr1---sn-j5o76n7z.xn--ngstr-lra8j.com&#xA;rr5---sn-i3b7knzs.xn--ngstr-lra8j.com&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;這些看似隨機的字符串，實際上是經過一些簡單的密碼學加密後的結果。讓我們拆解其中的核心部件。&lt;/p&gt;&#xA;&lt;h3 id=&#34;城市名稱的轉換規則&#34;&gt;城市名稱的轉換規則&#xA;&lt;/h3&gt;&lt;p&gt;以&lt;code&gt;rr1---sn-j5o76n7z&lt;/code&gt;為例，關鍵的信息段在&lt;code&gt;sn-&lt;/code&gt;後面的 8 位：&lt;code&gt;r1---sn-[123][45][6][78]&lt;/code&gt;。前三位，也就是本例中的&lt;code&gt;j5o&lt;/code&gt;是城市名稱，由該城市主要機場的 IATA 碼通過一定的密碼學變換轉換而來。&lt;/p&gt;&#xA;&lt;p&gt;首先構建一張 5 * 7 的數字字母表：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;行0: 1 0 2 3 4&#xA;行1: 5 6 7 8 9&#xA;行2: a b c d e&#xA;行3: f g h i j&#xA;行4: k l m n o&#xA;行5: p q r s t&#xA;行6: u v w x y&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;逆時針旋轉這張表，即從新表格的左下角開始，依次按照原表格「從左到右，從上到下」的順序，把原表的數據在新表上按照「從下到上，從左到右」的順序謄抄。&lt;/p&gt;&#xA;&lt;p&gt;旋轉完成後，可以得到下表：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;| 6 d k r y |&#xA;| --------- |&#xA;| 5 c j q x |&#xA;| 4 b i p w |&#xA;| 3 a h o v |&#xA;| 2 9 g n u |&#xA;| 0 8 f m t |&#xA;| --------- |&#xA;| 1 7 e l s |&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;表格中間用線條框出來的 5*5 的部分就是最終的密碼錶，一共有 25 個字符，「從左到右，從上到下」依次代表字母&lt;code&gt;a&lt;/code&gt;到字母&lt;code&gt;y&lt;/code&gt;。例如，上海虹橋國際機場的 IATA 代碼為&lt;code&gt;sha&lt;/code&gt;,我們可以通過這張表得到加密後的密文：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;s&lt;/code&gt;在字母表中是第 19 個字母，「從左到右，從上到下」依次在密碼錶中數到第 19 個字符，也就是&lt;code&gt;n&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;h&lt;/code&gt;在字母表中是第 8 個字母，「從左到右，從上到下」依次在密碼錶中數到第 8 個字符，也就是&lt;code&gt;i&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;a&lt;/code&gt;在字母表中是第 1 個字母，「從左到右，從上到下」依次在密碼錶中數到第 1 個字符，也就是&lt;code&gt;5&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;加密後的密文就是&lt;code&gt;ni5&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;p&gt;反之亦然，回到一開始的城市名稱&lt;code&gt;j5o&lt;/code&gt;，從我們剛剛得到的密碼錶中反推出原文：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;j&lt;/code&gt;按照「從左到右，從上到下」的順序是第 3 個字符，也就是&lt;code&gt;c&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;5&lt;/code&gt;按照「從左到右，從上到下」的順序是第 1 個字符，也就是&lt;code&gt;a&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;o&lt;/code&gt;按照「從左到右，從上到下」的順序是第 14 個字母，也就是&lt;code&gt;n&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;翻譯完成的明文是&lt;code&gt;can&lt;/code&gt;，也就是廣州白雲國際機場的 IATA 碼，顯然，我們連接到的服務器位於廣州。&lt;/p&gt;&#xA;&lt;p&gt;要是谷歌有某個服務器位於蘇黎世，而蘇黎世機場的 IATA 碼是&lt;code&gt;zrh&lt;/code&gt;，有一個字母&lt;code&gt;z&lt;/code&gt;，可是我們的密碼錶只有&lt;code&gt;a&lt;/code&gt;到&lt;code&gt;y&lt;/code&gt;啊？別擔心，Google 當然也考慮到了這個問題，&lt;code&gt;z&lt;/code&gt;對應密碼錶中的&lt;code&gt;1&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;p&gt;將這套規則用代碼表示如下：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;table = &#34;5cjqx4bipw3ahov29gnu08fmt1&#34;&#xA;def iata2cipher(iata):&#xA;    global table&#xA;    iata = iata.upper()&#xA;    cipher = &#34;&#34;&#xA;    for element in iata:&#xA;        index = ord(element) - 65&#xA;        cipher += table[index]&#xA;    return cipher&#xA;&#xA;def cipher2iata(cipher):&#xA;    global table&#xA;    cipher = cipher.lower()&#xA;    iata = &#34;&#34;&#xA;    for element in cipher:&#xA;        index = table.find(element)&#xA;        iata += chr(65 + index)&#xA;    return iata&#xA;&#xA;# print(iata2cipher(input(&#34;IATA:&#34;)))&#xA;# print(cipher2iata(input(&#34;Cipher:&#34;)))&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;服務器組編號&#34;&gt;服務器組編號&#xA;&lt;/h3&gt;&lt;p&gt;[45]和[78]位，如&lt;code&gt;76&lt;/code&gt;和&lt;code&gt;7z&lt;/code&gt;，表示服務器組（接入點）編號，由下表第一列（已經用分隔線隔開）包含的字符組成，&lt;code&gt;7elsz6dkry&lt;/code&gt;分別代表&lt;code&gt;0123456789&lt;/code&gt;。所以，&lt;code&gt;76&lt;/code&gt;的明文是&lt;code&gt;05&lt;/code&gt;，&lt;code&gt;7z&lt;/code&gt;的明文是&lt;code&gt;04&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;  | 1 2 3 4 5 6&#xA;7 | 8 9 a b c d&#xA;e | f g h i j k&#xA;l | m n o p q r&#xA;s | t u v w x y&#xA;z | 0 1 2 3 4 5&#xA;6 | 7 8 9 a b c&#xA;d | e f g h i j&#xA;k | l m n o p q&#xA;r | s t u v w x&#xA;y | z&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;以 Python 表示如下：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;codeTable = &#34;7elsz6dkry&#34;&#xA;def cipher2code(cipher):&#xA;    global codeTable&#xA;    cipher = cipher.lower()&#xA;    codeString = &#34;&#34;&#xA;    for element in cipher:&#xA;        index = codeTable.find(element)&#xA;        codeString += chr(index + 48)&#xA;    return codeString&#xA;&#xA;# print(cipher2code(input(&#34;Cipher:&#34;)))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;同樣要將數字加密為密文同理，這裡不再贅述。&lt;/p&gt;&#xA;&lt;h3 id=&#34;支持協議&#34;&gt;支持協議&#xA;&lt;/h3&gt;&lt;p&gt;[6]位表示網絡協議信息：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;n&lt;/code&gt;：IPv6（地址段 0x000-0x3FF）&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;u&lt;/code&gt;：IPv6（地址段 0x400-0x7FF）&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;m&lt;/code&gt;：僅支持 IPv4&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;例如：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;a5mekn7r&lt;/code&gt;，IPv6 前綴：&lt;code&gt;2607:f8b0:4007:a::/64&lt;/code&gt;，IPv4 前綴：&lt;code&gt;74.125.103.0/24&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;a5m7zu7r&lt;/code&gt;，IPv6 前綴：&lt;code&gt;2607:f8b0:4007:407::/64&lt;/code&gt;，IPv4 前綴：&lt;code&gt;74.125.215.0/24&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;a5mekm76&lt;/code&gt;，僅支持 IPv4，前綴：&lt;code&gt;208.117.242.0/24&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;正確的分流處理&#34;&gt;正確的分流處理&#xA;&lt;/h2&gt;&lt;blockquote class=&#34;alert alert-warning&#34;&gt;&#xA;        &lt;div class=&#34;alert-header&#34;&gt;&#xA;            &lt;span class=&#34;alert-icon&#34;&gt;⚠️&lt;/span&gt;&#xA;            &lt;span class=&#34;alert-title&#34;&gt;警告&lt;/span&gt;&#xA;        &lt;/div&gt;&#xA;        &lt;div class=&#34;alert-body&#34;&gt;&#xA;            &lt;p&gt;Google 顯然不會為這些位於中國大陸的，未經 ICP 備案的 CDN 的穩定性和速度背書。如果流量足夠，那還是直接將&lt;code&gt;services.googleapis.cn&lt;/code&gt;加入代理列表吧。&lt;/p&gt;&#xA;        &lt;/div&gt;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;瞭解了 Google 基礎設施域名的加密規律後，我們可以根據這些信息實現更精準的分流策略。目前已知 Google 在中國大陸的 CDN 節點主要分佈在北京（&lt;code&gt;2x3&lt;/code&gt;）、上海（&lt;code&gt;ni5&lt;/code&gt;）和廣州（&lt;code&gt;j5o&lt;/code&gt;），對應的域名特徵可以通過正則表達式識別：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;rules:&#xA;  - DOMAIN-REGEX,^r+[0-9]+(---|\.)sn-(2x3|ni5|j5o)\w{5}\.xn--ngstr-lra8j\.com$,DIRECT&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;這條規則會匹配所有形如 &lt;code&gt;rr1---sn-j5o76n7z.xn--ngstr-lra8j.com&lt;/code&gt; 的國內 CDN 域名，並將其標記為直連。而對於其他未被覆蓋的 Google 域名（如 &lt;code&gt;play.googleapis.com&lt;/code&gt; 等），仍遵循原有的代理規則。&lt;/p&gt;&#xA;&lt;p&gt;事實上，GeoSite 數據庫的上游 &lt;a class=&#34;link&#34; href=&#34;https://github.com/v2fly/domain-list-community&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;v2fly/domain-list-community&lt;/a&gt; 已針對 Google Play 的國內 CDN 節點進行了優化&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;。只需在 Mihomo 配置中啟用 &lt;code&gt;GEOSITE,GOOGLE-PLAY@CN&lt;/code&gt; 規則，即可自動實現國內 CDN 直連與海外域名代理的智能分流：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;rules:&#xA;  - GEOSITE,GOOGLE-PLAY@CN,直連&#xA;  - GEOSITE,GOOGLE,代理&lt;/code&gt;&lt;/pre&gt;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;參考: &lt;a class=&#34;link&#34; href=&#34;https://en.wikipedia.org/wiki/Anders_Jonas_%C3%85ngstr%C3%B6m&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://en.wikipedia.org/wiki/Anders_Jonas_%C3%85ngstr%C3%B6m&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;參考: &lt;a class=&#34;link&#34; href=&#34;https://github.com/lennylxx/ipv6-hosts/wiki/sn-domains&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://github.com/lennylxx/ipv6-hosts/wiki/sn-domains&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;具體的commit: &lt;a class=&#34;link&#34; href=&#34;https://github.com/v2fly/domain-list-community/pull/2436/commits/a86c1bf3d9bf577869180874d87c76ddf6282fc1&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://github.com/v2fly/domain-list-community/pull/2436/commits/a86c1bf3d9bf577869180874d87c76ddf6282fc1&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
        </item><item>
            <title>最速 Substore 訂閱管理指南</title>
            <link>https://blog.l3zc.com/zh-hant-tw/2025/03/clash-subscription-convert/</link>
            <pubDate>Fri, 07 Mar 2025 10:54:29 +0000</pubDate>
            <guid>https://blog.l3zc.com/zh-hant-tw/2025/03/clash-subscription-convert/</guid>
            <description>&lt;img src=&#34;https://blog.l3zc.com/2025/03/clash-subscription-convert/image-8_hu_92098803a4bf150d.webp&#34; alt=&#34;Featured image of post 最速 Substore 訂閱管理指南&#34; /&gt;&lt;p&gt;當我趁著春節各家機場的促銷訂閱多個機場之後，如何充分利用每個節點就變成了說難不難說簡單也不簡單的問題，我當然可以訂閱各家機場提供的配置文件，然後在他們之間切換，但這樣未免也太麻煩了。更何況我還有自建節點，我可不想為了這一個節點專門去開一個新的配置。&lt;/p&gt;&#xA;&lt;p&gt;Sub-Store 很好的解決了這個問題，它可以從多個訂閱中抽取節點信息，通過正則表達式或者 JS 整理它們，最後輸出一個整合了所有節點信息的訂閱。&lt;/p&gt;&#xA;&lt;p&gt;部署它可以直接使用 xream 打包好的鏡像，這個鏡像整合了前後端，如果在公網部署，記得更改一下後端路徑，否則你的配置文件很可能會被盜用。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;services:&#xA;  sub-store:&#xA;    image: xream/sub-store&#xA;    container_name: substore&#xA;    restart: always&#xA;    environment:&#xA;      - SUB_STORE_CRON=55 23 * * *&#xA;      - SUB_STORE_FRONTEND_BACKEND_PATH=/super-random-path&#xA;    ports:&#xA;      - &#34;3001:3001&#34;&#xA;    volumes:&#xA;      - ./data:/opt/app/data&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;反代 3001 端口即可訪問 Substore 的前端，這裡以 Caddy 為例：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-caddyfile&#34;&gt;sub-domain.example.com {&#xA;        reverse_proxy :3001&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;當然，初次進入前端別忘了新增後端地址，這時的後端地址取決於之前 compose file 裡的設置，在本文的例子中，後端地址為&lt;code&gt;https://sub-domain.example.com/super-random-path&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;h2 id=&#34;組合訂閱的管理&#34;&gt;組合訂閱的管理&#xA;&lt;/h2&gt;&lt;p&gt;添加所有機場上游和自建節點之後，就可以開始把它們全都加進單個組合訂閱，但各個機場對節點的命名五花八門，默認情況下看起來非常雜亂，甚至不同機場之間的節點還有重名的可能。好在 Sub-Store 有通過腳本對節點進行批量重命名操作的功能，這裡推薦&lt;a class=&#34;link&#34; href=&#34;https://github.com/Keywos/rule/blob/main/rename.js&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;一個腳本&lt;/a&gt;，能夠幫我們為所有的機場節點重命名。&lt;/p&gt;&#xA;&lt;p&gt;欲使用這個腳本，只需在編輯訂閱時將以下地址粘貼到腳本操作處即可。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;https://raw.githubusercontent.com/Keywos/rule/main/rename.js&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/03/clash-subscription-convert/image_hu_8310153db045f240.webp&#34; alt=&#34;節點操作&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/03/clash-subscription-convert/image-1_hu_333de5a5c288737.webp&#34; alt=&#34;整理完成的節點列表&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;最後再進行一些你喜歡的節點操作，可以整理出一個統一規範的節點列表。&lt;/p&gt;&#xA;&lt;h2 id=&#34;生成-clash-配置&#34;&gt;生成 Clash 配置&#xA;&lt;/h2&gt;&lt;p&gt;現在雖然已經有了節點列表，但現在生成的配置文件並不包含任何規則，需要自行編寫或者拉取第三方規則。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/03/clash-subscription-convert/image-2_hu_755a17a55a5da685.webp&#34; alt=&#34;選擇 Mihomo 配置&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/03/clash-subscription-convert/image-3_hu_244966c33439c095.webp&#34; alt=&#34;編輯信息&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/03/clash-subscription-convert/image-4_hu_c92302b1defa984b.webp&#34; alt=&#34;生成分享鏈接&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/03/clash-subscription-convert/image-6_hu_c37bf30e1b9005a7.webp&#34; alt=&#34;腳本操作&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;轉到 Substore 的文件管理，創建一份新的 Mihomo 配置：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;「來源」選擇組合訂閱，並在訂閱名稱上選擇你的訂閱組&lt;/li&gt;&#xA;&lt;li&gt;在腳本操作中填入自己的覆寫配置&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;這是我自己的覆寫規則 &lt;a class=&#34;link&#34; href=&#34;https://github.com/powerfullz/override-rules&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;powerfullz/override-rules&lt;/a&gt;，為 Mihomo/Substore 設計，核心特色如下：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;集成 &lt;a class=&#34;link&#34; href=&#34;https://github.com/SukkaW/Surge&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;SukkaW/Surge&lt;/a&gt; 與 &lt;a class=&#34;link&#34; href=&#34;https://github.com/217heidai/adblockfilters&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;217heidai/adblockfilters&lt;/a&gt; 等優質規則，兼容性強，覆蓋面廣。&lt;/li&gt;&#xA;&lt;li&gt;針對 Truth Social、E-Hentai、TikTok、加密貨幣等場景，新增專用分流規則，滿足多樣化需求。&lt;/li&gt;&#xA;&lt;li&gt;精簡冗餘，結構清晰，維護便捷。&lt;/li&gt;&#xA;&lt;li&gt;深度融合 &lt;a class=&#34;link&#34; href=&#34;https://github.com/Loyalsoldier/v2ray-rules-dat&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Loyalsoldier/v2ray-rules-dat&lt;/a&gt; GeoSite/GeoIP，分流更精準。&lt;/li&gt;&#xA;&lt;li&gt;IP 規則默認添加 &lt;code&gt;no-resolve&lt;/code&gt;，有效減少本地 DNS 解析，提升速度與隱私。&lt;/li&gt;&#xA;&lt;li&gt;動態覆寫：自動識別節點國家/地區，僅生成實際存在的分組，節點名稱實時枚舉，配置更智能。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;javascript-格式覆寫&#34;&gt;JavaScript 格式覆寫&#xA;&lt;/h3&gt;&lt;p&gt;複製 JavaScript 格式覆寫文件的鏈接&lt;code&gt;https://cdn.jsdelivr.net/gh/powerfullz/override-rules@1/convert.min.js&lt;/code&gt;，並根據需要在後面附加參數，格式如下：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;https://cdn.jsdelivr.net/gh/powerfullz/override-rules@1/convert.min.js#參數1=true&amp;參數2=true&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;目前支持如下參數：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;參數&lt;/th&gt;&#xA;          &lt;th&gt;功能&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;loadbalance&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;啟用國家/地區節點組的負載均衡&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;landing&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;啟用落地節點功能&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ipv6&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;啟用 IPv6 支持&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;full&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;生成針對純內核使用場景的完整配置&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;keepalive&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;啟用 TCP KeepAlive&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;quic&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;允許 QUIC 流量&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;fakeip&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;DNS 增強模式使用 fake-ip&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;regex&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;各國家代理組改用 include-all + 正則過濾模式&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;threshold&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;國家/地區節點數量小於該值時不顯示分組（默認 0）&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;例如，若有負載均衡和 IPv6 需求，最終的覆寫腳本鏈接為：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;https://cdn.jsdelivr.net/gh/powerfullz/override-rules@1/convert.min.js#loadbalance=true&amp;ipv6=true&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;將最終的覆寫腳本鏈接粘貼到腳本操作處，使用 Substore 的生成預覽功能確認沒有問題後即可保存。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/03/clash-subscription-convert/image-7_hu_a9aea54492dfed6c.webp&#34; alt=&#34;新建一個腳本操作並粘貼最終的訂閱鏈接&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h3 id=&#34;yaml-格式覆寫&#34;&gt;YAML 格式覆寫&#xA;&lt;/h3&gt;&lt;p&gt;&lt;del&gt;YAML 格式覆寫我自己已經不用了，隨緣維護，但歡迎 PR&lt;/del&gt;&lt;/p&gt;&#xA;&lt;p&gt;寫了個 Github Actions 自動根據 JS 格式覆寫生成 YAML 格式覆寫，所以現在 YAML 格式又開始維護了。&lt;/p&gt;&#xA;&lt;p&gt;除了直接引用 convert.js 動態覆寫，你也可以使用倉庫中預先生成好的 32 份 YAML 格式覆寫——它們都放在 yamls/ 目錄裡，由 GitHub Actions 在每次推送後自動重新生成、覆蓋。適用於諸如 Clash Verge 等不支持 JS 覆寫的客戶端和轉換服務。&lt;/p&gt;&#xA;&lt;p&gt;文件命名規則：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;config_lb-{0|1}_landing-{0|1}_ipv6-{0|1}_full-{0|1}_keepalive-{0|1}_fakeip-{0|1}_quic-{0|1}.yaml&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;示例（開啟 full，其餘關閉）：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;https://raw.githubusercontent.com/powerfullz/override-rules/refs/heads/main/yamls/config_lb-0_landing-0_ipv6-0_full-1_keepalive-0_fakeip-0_quic-0.yaml&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;CI 只是套用一份假的&lt;code&gt;fake_proxies.json&lt;/code&gt;來生成覆寫，所以不可能實現 JS 覆寫自動根據節點匹配生成對應代理組的功能，只能把所有地區節點組都放進去。如果你已經搭建 Substore，並且想要「動態識別國家 + 傳參」的靈活性，還是推薦使用 JS 覆寫。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/03/clash-subscription-convert/image-6_hu_c37bf30e1b9005a7.webp&#34; alt=&#34;填入對應的 RAW 鏈接&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h2 id=&#34;生成下載鏈接&#34;&gt;生成下載鏈接&#xA;&lt;/h2&gt;&lt;p&gt;保存成功後點擊分享按鈕生成分享鏈接，設置分享有效期後點擊「創建分享」，生成的鏈接即最終成型的 Mihomo 配置文件，將其作為訂閱鏈接在你的代理軟件內訂閱就大功告成了。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/03/clash-subscription-convert/image-8_hu_92098803a4bf150d.webp&#34; alt=&#34;最終效果&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;部分機場 UDP 性能差，開啟後可能出現體驗降級。&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;開啟後可能有助於解決 TUN 模式無法上網的問題。&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;各國家代理組改用 &lt;code&gt;include-all&lt;/code&gt; + 正則過濾模式，由 Mihomo 內核在運行時按正則動態篩選節點，而非在腳本執行時枚舉節點名稱（默認 false）&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
        </item><item>
            <title>我的家庭網絡域名解析方案</title>
            <link>https://blog.l3zc.com/zh-hant-tw/2025/02/what-i-have-done-on-my-dns/</link>
            <pubDate>Thu, 06 Feb 2025 09:01:51 +0000</pubDate>
            <guid>https://blog.l3zc.com/zh-hant-tw/2025/02/what-i-have-done-on-my-dns/</guid>
            <description>&lt;img src=&#34;https://blog.l3zc.com/2025/02/what-i-have-done-on-my-dns/cover_hu_1bda1c3daad4bc1b.webp&#34; alt=&#34;Featured image of post 我的家庭網絡域名解析方案&#34; /&gt;&lt;p&gt;人在中國，用起 DNS 會碰到兩個問題，一個是 DNS 汙染，另一個是 DNS 洩露。想要上網上得痛快，就必須要先解決 DNS 的問題。&lt;/p&gt;&#xA;&lt;h2 id=&#34;什麼是-dns-洩露&#34;&gt;什麼是 DNS 洩露&#xA;&lt;/h2&gt;&lt;p&gt;如果你現在正在開著代理看這篇文章，可以先打開&lt;a class=&#34;link&#34; href=&#34;https://browserleaks.com/dns&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;這個網站&lt;/a&gt;檢測一下自己的 DNS 請求是否被髮送到了國內的 DNS 服務器上。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/02/what-i-have-done-on-my-dns/image-5_hu_76c2844c94b4a181.webp&#34; alt=&#34;典型的 DNS 洩露&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;簡單來說，當你在使用 VPN 等代理工具時，你認為你的請求不會被除了你和 VPN 提供商以外的第三方看到，然而主機的 DNS 請求卻被髮送送給了運營商的 DNS 服務器或者是公共 DNS 服務器(阿里、騰訊等)進行解析，而你的真實 IP 地址和請求的域名都會被記錄下來。如果 DNS 請求解析一些敏感網站的域名(比如電報、維基解密等)，就會被監管者注意到，從而被警告或者請去喝茶。&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#xA;&lt;p&gt;因此，正確地配置代理工具的分流策略來避免 DNS 洩露就顯得尤為重要。&lt;/p&gt;&#xA;&lt;h2 id=&#34;smartdns-的配置&#34;&gt;SmartDNS 的配置&#xA;&lt;/h2&gt;&lt;h3 id=&#34;防止-dns-洩露&#34;&gt;防止 DNS 洩露&#xA;&lt;/h3&gt;&lt;p&gt;DNS 作為上網的指路人，排在第一位的要求就是必須要快，在這樣的要求下本地部署 SmartDNS 充當家庭網絡的 DNS 服務器真的再合適不過了。關於 SmartDNS 的作用和配置技巧，以及其如何優選 CDN 加快網絡訪問速度，Sukka 的「&lt;a class=&#34;link&#34; href=&#34;https://blog.skk.moe/post/i-have-my-unique-dns-setup/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;我有特別的 DNS 配置和使用技巧&lt;/a&gt;」已經講的很好，推薦在繼續閱讀本文之前先去看看這篇文章，其中的內容我在這裡便不再贅述，我的敘述重心將會放在如何在 Luci 界面上實現這些配置。&lt;/p&gt;&#xA;&lt;p&gt;如果你和我一樣使用 OpenWRT，SmartDNS 的 Luci App 會在每次啟動時根據面板上保存的配置自動生成新的配置文件，所以，不要直接修改&lt;code&gt;/etc/smartdns&lt;/code&gt;目錄下原有的配置文件，所有的更改都應該在 Luci App 裡操作。&lt;/p&gt;&#xA;&lt;p&gt;基本上，SmartDNS 上游數量越多越好，因為較多的上游並不會影響 SmartDNS 的性能&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;，並且多個上游可以增加冗餘。為了防止 DNS 洩露，在配置上游時，應當在默認分組添加國外 DNS 上游（當然，最好是加密 DNS），並將國內的服務器歸入一個單獨的分組（例如&lt;code&gt;domestic&lt;/code&gt;），並加上&lt;code&gt;-exclude-default-group&lt;/code&gt;的額外參數以將它們從默認分組中排除。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/02/what-i-have-done-on-my-dns/image_hu_bd77370d9a6d2fcd.webp&#34; alt=&#34;國內上游的分組設置&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/02/what-i-have-done-on-my-dns/image-1_hu_1ba4737b13c17f98.webp&#34; alt=&#34;一些可供參考的上游&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h3 id=&#34;加速國內域名解析&#34;&gt;加速國內域名解析&#xA;&lt;/h3&gt;&lt;p&gt;設置好國外上游，DNS 洩露的問題就解決了，但因為我們在默認分組中排除了國內上游，訪問新的域名時需要等待國外上游返回結果，這顯然會拖慢國內網站的訪問速度，並且還會劣化 CDN。&lt;/p&gt;&#xA;&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://github.com/felixonmars/dnsmasq-china-list&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;dnsmasq-china-list&lt;/a&gt; 是 Felix Yan 的項目，目前支持通過 Makefile 生成 SmartDNS 格式的配置文件，目前已經有人做好了現成的 Github Action 每天自動更新，直接下載就可以了，注意這些配置文件只會在國內 DNS 上游分組名稱為&lt;code&gt;domestic&lt;/code&gt;時生效。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;cd /etc/smartdns&#xA;wget https://gcore.jsdelivr.net/gh/Olixn/china_list_for_smartdns@main/accelerated-domains.china.domain.smartdns.conf&#xA;wget https://gcore.jsdelivr.net/gh/Olixn/china_list_for_smartdns@main/apple.china.domain.smartdns.conf&#xA;wget https://gcore.jsdelivr.net/gh/Olixn/china_list_for_smartdns@main/chinalist.domain.smartdns.conf&#xA;wget https://gcore.jsdelivr.net/gh/Olixn/china_list_for_smartdns@main/google.china.domain.smartdns.conf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;隨後在 SmartDNS 中引入這些配置文件，將下面的內容粘貼到「自定義設置」欄的末尾。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;conf-file /etc/smartdns/accelerated-domains.china.domain.smartdns.conf&#xA;conf-file /etc/smartdns/apple.china.domain.smartdns.conf&#xA;conf-file /etc/smartdns/chinalist.domain.smartdns.conf&#xA;conf-file /etc/smartdns/google.china.domain.smartdns.conf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/02/what-i-have-done-on-my-dns/image-2_hu_92b3727e74c74e1a.webp&#34; alt=&#34;引入配置文件&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;這樣配置之後，國內域名的訪問速度便不再會受到影響，訪問國外域名時，也不會再有 DNS 洩露的問題了。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/02/what-i-have-done-on-my-dns/image-6_hu_d5c1130c9fef193d.webp&#34; alt=&#34;在無代理的情況下防止 DNS 洩露&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h3 id=&#34;去廣告&#34;&gt;去廣告&#xA;&lt;/h3&gt;&lt;p&gt;DNS 層面的去廣告效果有限，重點應該放在防止誤殺上，所以不要用那臭名昭著&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;的號稱自己是「致力於成為中文區命中率最高的廣告過濾列表」的 Anti AD，而是使用一些別的規則。個人使用的規則是 &lt;a class=&#34;link&#34; href=&#34;https://github.com/217heidai/adblockfilters&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;217heidai/adblockfilters&lt;/a&gt;，這是一個已經整理好的聚合規則，直接拉取即可。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;wget -O /etc/smartdns/adblock.conf https://gcore.jsdelivr.net/gh/217heidai/adblockfilters@main/rules/adblocksmartdns.conf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;最後引入配置文件：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;conf-file /etc/smartdns/adblock.conf&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;自動更新規則&#34;&gt;自動更新規則&#xA;&lt;/h3&gt;&lt;p&gt;設置如下 Crontab 即可在每天凌晨三點自動更新所有規則。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-crontab&#34;&gt;0 0 3 * * ?  wget -O /etc/smartdns/adblock.conf https://gcore.jsdelivr.net/gh/217heidai/adblockfilters@main/rules/adblocksmartdns.conf&#xA;0 0 3 * * ? wget -O /etc/smartdns/accelerated-domains.china.domain.smartdns.conf https://gcore.jsdelivr.net/gh/Olixn/china_list_for_smartdns@main/accelerated-domains.china.domain.smartdns.conf&#xA;0 0 3 * * ? wget -O /etc/smartdns/apple.china.domain.smartdns.conf https://gcore.jsdelivr.net/gh/Olixn/china_list_for_smartdns@main/apple.china.domain.smartdns.conf&#xA;0 0 3 * * ? wget -O /etc/smartdns/chinalist.domain.smartdns.conf https://gcore.jsdelivr.net/gh/Olixn/china_list_for_smartdns@main/chinalist.domain.smartdns.conf&#xA;0 0 3 * * ? wget -O /etc/smartdns/google.china.domain.smartdns.conf https://gcore.jsdelivr.net/gh/Olixn/china_list_for_smartdns@main/google.china.domain.smartdns.conf&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;旁路由可以用嗎&#34;&gt;旁路由可以用嗎&#xA;&lt;/h3&gt;&lt;p&gt;除非不使用 IPv6，否則最好還是將 OpenWRT 路由器作為主路由。在作為旁路由的情況下，某些系統（說的就是你，Windows）會不遵守旁路由廣播的 IPv4 DNS，而執意使用運營商光貓廣播的 IPv6 DNS 進行解析，我嘗試修改過一些相關的註冊表鍵值，但毫無作用。無奈只好讓運營商上門將光貓改為橋接模式，並將 OpenWRT 用作主路由。&lt;/p&gt;&#xA;&lt;h2 id=&#34;覆寫-clash-規則&#34;&gt;覆寫 Clash 規則&#xA;&lt;/h2&gt;&lt;p&gt;這時如果你開著 Clash 去跑 DNS 洩露測試，大概率還是會看到你的 DNS 請求被髮到了中國的 DNS 服務器上——幾乎所有機場的默認配置都會使用騰訊、阿里等國內大廠的加密 DNS，雖然不至於明文洩露到運營商 DNS 而導致你接到反詐中心的電話，但總讓人膈應 &lt;del&gt;（強迫症犯了）&lt;/del&gt;，更有甚者甚至完全沒有相關配置。&lt;/p&gt;&#xA;&lt;p&gt;好在如果使用的是 Clash Verge Rev，那麼可以很方便的覆寫機場的配置，這裡分享我的覆寫配置，將其粘貼到全局擴展配置中即可覆寫機場的配置。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;dns:&#xA;  enable: true&#xA;  ipv6: false&#xA;  prefer-h3: true&#xA;  use-system-hosts: true&#xA;  nameserver:&#xA;    - &#34;https://dns.cloudflare.com/dns-query&#34;&#xA;    - &#34;https://dns.sb/dns-query&#34;&#xA;    - &#34;https://dns.google/dns-query&#34;&#xA;    - &#34;https://public.dns.iij.jp/dns-query&#34;&#xA;    - &#34;https://jp.tiar.app/dns-query&#34;&#xA;    - &#34;https://jp.tiarap.org/dns-query&#34;&#xA;  nameserver-policy:&#xA;    &#34;geosite:cn&#34;:&#xA;      - system&#xA;  fallback:&#xA;    - &#34;tls://8.8.4.4&#34;&#xA;    - &#34;tls://1.1.1.1&#34;&#xA;  proxy-server-nameserver:&#xA;    - &#34;https://doh.pub/dns-query&#34;&#xA;    - &#34;https://223.5.5.5/dns-query&#34;&#xA;  direct-nameserver:&#xA;    - system&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/02/what-i-have-done-on-my-dns/image-3_hu_92a7537b0aa409b3.webp&#34; alt=&#34;如何修改全局擴展配置&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h3 id=&#34;配置解析&#34;&gt;配置解析&#xA;&lt;/h3&gt;&lt;p&gt;根據 Mihomo 文檔&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;，其 DNS 請求的流程如下：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;  Start[客戶端發起請求] --&gt; rule[匹配規則]&#xA;  rule --&gt;  Domain[匹配到基於域名的規則]&#xA;  rule --&gt; IP[匹配到基於 IP 的規則]&#xA;&#xA;  Domain --&gt; |域名匹配到直連規則|DNS&#xA;  IP --&gt; DNS[通過 Clash DNS 解析域名]&#xA;&#xA;&#xA;  Domain --&gt; |域名匹配到代理規則|Remote[通過代理服務器解析域名並建立連接]&#xA;&#xA;  Cache --&gt; |Redir-host/FakeIP-Direct 未命中|NS[匹配 nameserver-policy 並查詢 ]&#xA;  Cache --&gt; |Cache 命中|Get&#xA;  Cache --&gt; |FakeIP 未命中,代理域名|Remote&#xA;&#xA;  NS --&gt; |匹配成功| Get[將查詢到的 IP 用於匹配 IP 規則]&#xA;  NS --&gt; |沒匹配到| NF[nameserver/fallback 併發查詢]&#xA;&#xA;  NF --&gt; Get[查詢得到 IP]&#xA;  Get --&gt; |緩存 DNS 結果|Cache[(Cache)]&#xA;  Get --&gt; S[通過 IP 直接/通過代理建立連接]&#xA;&#xA;  DNS --&gt; Redir-host/FakeIP&#xA;  Redir-host/FakeIP --&gt; |查詢 DNS 緩存|Cache&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其中&lt;code&gt;nameserver-policy&lt;/code&gt;的優先級高於&lt;code&gt;nameserver&lt;/code&gt;和&lt;code&gt;fallback&lt;/code&gt;，指定&lt;code&gt;geosite:cn&lt;/code&gt;中的網站以系統 DNS 查詢，也就是使用之前配置好的 SmartDNS 查詢，可以享受到局域網級別的響應速度和自帶測速的 CDN 選擇。而不在&lt;code&gt;geosite:cn&lt;/code&gt;中的網站則使用&lt;code&gt;nameserver/fallback&lt;/code&gt;查詢，使用國外加密上游就可以確保解析結果的可信並防止 DNS 洩露。&lt;/p&gt;&#xA;&lt;h3 id=&#34;替換-geoip-和-geosite-數據庫&#34;&gt;替換 geoip 和 geosite 數據庫&#xA;&lt;/h3&gt;&lt;p&gt;Mihomo 默認使用 V2Ray 官方的 geoip 和 geosite 數據庫，這個數據庫其實不太全，而且更新的頻率也很慢，所以我還是用 &lt;a class=&#34;link&#34; href=&#34;https://github.com/Loyalsoldier/v2ray-rules-dat&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Loyalsoldier/v2ray-rules-dat&lt;/a&gt; 這個「加強版」數據庫替換了原來的數據庫，只需下載&lt;code&gt;geoip.dat&lt;/code&gt;和&lt;code&gt;geosite.dat&lt;/code&gt;以後丟進內核目錄即可。當然，也可以新增幾行覆寫配置以實現自動/一鍵更新：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;geodata-mode: true&#xA;geox-url:&#xA;  geoip: &#34;https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat&#34;&#xA;  geosite: &#34;https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat&#34;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;clash-meta-for-android&#34;&gt;Clash Meta For Android&#xA;&lt;/h3&gt;&lt;p&gt;對於手機上的 Clash Meta For Android（以下簡稱 Clash），覆寫設置時需要更加考慮手機的實際情況。&lt;/p&gt;&#xA;&lt;p&gt;首先手機上的 Clash 覆寫設置選項就不如桌面端的 Clash Verge Rev 這麼靈活，很多鍵值都不能覆寫。另外，手機不像電腦，需要經常切換網絡環境，這就導致系統 DNS 也會換得很頻繁，經測試，在這種情況下，每次更換網絡環境都需要開關一次 Clash 才能讓其更新&lt;code&gt;system&lt;/code&gt;所代表的 DNS。於是我們只能退而求其次，固定一個國內加密 DNS 作為國內查詢。&lt;/p&gt;&#xA;&lt;p&gt;進入 Clash -&amp;gt;「設置」-&amp;gt;「覆寫」以下是所有覆寫設置：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;「DNS」-&amp;gt;「策略」：強制啟用&lt;/li&gt;&#xA;&lt;li&gt;「DNS」-&amp;gt;「Prefer h3」：已啟用&lt;/li&gt;&#xA;&lt;li&gt;「DNS」-&amp;gt;「Name Server」：添加兩個國外加密 DNS&lt;/li&gt;&#xA;&lt;li&gt;「DNS」-&amp;gt;「Name Server 策略」：在「鍵」一欄中填寫&lt;code&gt;geosite:cn&lt;/code&gt;，值一欄填寫&lt;code&gt;https://doh.pub/dns-query&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;提前從 Github 上下載&lt;code&gt;geoip.dat&lt;/code&gt;和&lt;code&gt;geosite.dat&lt;/code&gt;，最後進入 Clash -&amp;gt;「設置」-&amp;gt;「Meta Features」，分別用「導入 GeoIP 數據庫」和「導入 GeoSite 數據庫」功能導入它們。&lt;/p&gt;&#xA;&lt;h2 id=&#34;完成&#34;&gt;完成&#xA;&lt;/h2&gt;&lt;p&gt;最後再打開一個 &lt;a class=&#34;link&#34; href=&#34;https://browserleaks.com/dns&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;DNS 洩露測試&lt;/a&gt;，看一看問題是否解決，如果不再出現國內的 DNS，說明配置正確，好好享受 SmartDNS 和 Clash 帶來的網絡優化和防洩漏吧~&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/02/what-i-have-done-on-my-dns/image-4_hu_fb37d196f4006eaa.webp&#34; alt=&#34;DNS 不再洩露&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;參考自: &lt;a class=&#34;link&#34; href=&#34;https://blog.lololowe.com/posts/8f1e/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://blog.lololowe.com/posts/8f1e/&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;SmartDNS 在設計時就考慮到了延遲問題，具體機制可以參考「&lt;a class=&#34;link&#34; href=&#34;https://blog.skk.moe/post/i-have-my-unique-dns-setup/#%E4%B8%AD%E5%9C%BA%E4%BC%91%E6%81%AF%EF%BC%9ASmartDNS-%E6%98%AF%E5%A6%82%E4%BD%95%E9%81%BF%E5%85%8D%E5%9B%A0%E6%B5%8B%E9%80%9F%E5%AF%BC%E8%87%B4-DNS-%E8%A7%A3%E6%9E%90%E8%BF%87%E6%85%A2%E7%9A%84&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;中場休息：SmartDNS 是如何避免因測速導致 DNS 解析過慢的&lt;/a&gt;」。&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;有人整理了 &lt;a class=&#34;link&#34; href=&#34;https://github.com/Mosney/anti-anti-AD&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Anti AD 的光榮事蹟&lt;/a&gt;。&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:4&#34;&gt;&#xA;&lt;p&gt;引用自: &lt;a class=&#34;link&#34; href=&#34;https://wiki.metacubex.one/config/dns/diagram/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://wiki.metacubex.one/config/dns/diagram/&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
        </item><item>
            <title>Windows Hiccup 小日常：處理時間同步服務的問題</title>
            <link>https://blog.l3zc.com/zh-hant-tw/2024/11/windows-ntp-hiccup/</link>
            <pubDate>Mon, 18 Nov 2024 10:03:47 +0000</pubDate>
            <guid>https://blog.l3zc.com/zh-hant-tw/2024/11/windows-ntp-hiccup/</guid>
            <description>&lt;img src=&#34;https://blog.l3zc.com/2024/11/windows-ntp-hiccup/cover_hu_e196169a0c972a34.webp&#34; alt=&#34;Featured image of post Windows Hiccup 小日常：處理時間同步服務的問題&#34; /&gt;&lt;p&gt;不知出了什麼問題，我的電腦的時間從來不會自動和 NTP 服務器同步，雖然短期內並不會對使用造成什麼影響，但倘若放任不管，時鐘的偏移量太多，則可能會造成 SSL 和 TOTP 等依賴時間戳的應用出現問題，再者，手動按同步鍵也很不方便，今天我決定必須要解決這個問題。&lt;/p&gt;&#xA;&lt;h2 id=&#34;specialpollinterval&#34;&gt;SpecialPollInterval&#xA;&lt;/h2&gt;&lt;p&gt;在網上搜索一番之後，我發現有人提到&lt;code&gt;SpecialPollInterval&lt;/code&gt;這個鍵值，其具體位置是&lt;code&gt;HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpClient&lt;/code&gt;，微軟官方文檔對其的描述如下：&lt;/p&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;This entry specifies the special poll interval in seconds for manual peers. When the SpecialInterval 0x1 flag is enabled, W32Time uses this poll interval instead of a poll interval determine by the operating system. The default value on domain members is 3,600. The default value on stand-alone clients and servers is 604,800.&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;對於一般客戶端，這個鍵值的默認值是&lt;code&gt;604800&lt;/code&gt;，也就是一個星期同步一次，這個間隔顯然太久，於是我將其設置成了&lt;code&gt;3600&lt;/code&gt;令其一小時同步一次。&lt;/p&gt;&#xA;&lt;h2 id=&#34;w32tm&#34;&gt;w32tm&#xA;&lt;/h2&gt;&lt;p&gt;幾天後，當我再次檢查時間時，卻發現時間仍然沒有自動同步，這一次的偏移量已經達到了 15.8 秒。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/11/windows-ntp-hiccup/Screenshot20241117-1915_hu_13e6b8a28a875d07.webp&#34; alt=&#34;系統時間快了15.8秒&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;於是查詢「SpecialPollInterval」想要知道其具體的作用和未及預期目的的原因，卻發現了微軟的一篇&lt;a class=&#34;link&#34; href=&#34;https://learn.microsoft.com/zh-cn/troubleshoot/windows-server/active-directory/specialpollinterval-polling-interval-time-service-not-correct&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;疑難解答&lt;/a&gt;。這篇疑難解答指出：「每次客戶端輪詢時間樣本到 NTP 服務器時，NTP 客戶端都會進入 SPIKE 狀態。 時間服務管理其內部狀態，如果客戶端進入 SPIKE 狀態，則客戶端不會同步其時間。」&lt;/p&gt;&#xA;&lt;p&gt;為了解決這個問題，需要將 Windows 時間配置為使用 MinPollInterval/MaxPollInterval 作為輪詢間隔。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-powershell&#34;&gt;w32tm /config /update /manualpeerlist:cn.pool.ntp.org /syncfromflags:MANUAL&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;服務尚未啟動&#34;&gt;服務尚未啟動&#xA;&lt;/h2&gt;&lt;p&gt;執行這一行配置時出現了「服務尚未啟動」的提示，這說明用於時間同步的服務根本沒有啟動。如此基礎的服務竟然不會自動開機啟動，我也不知道這是 Windows 的 Bug 還是我後期使用無意間製造的問題，不管怎樣，首先啟動這個服務：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-powershell&#34;&gt;net start w32time&#xA;w32tm /register   # 註冊一些基本的註冊表鍵值和相關服務&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;隨後 Win + R 運行&lt;code&gt;services.msc&lt;/code&gt;打開服務管理面板，找到「Windows Time」服務，將其啟動類型設置為「自動」，即可讓其開機自動啟動。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/11/windows-ntp-hiccup/image_hu_521a92b1a7f11576.webp&#34; alt=&#34;將 W32Time 的啟動類型設置為自動&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/11/windows-ntp-hiccup/image-1_hu_36c2133f76139721.webp&#34; alt=&#34;出現「下次同步」字樣即說明成功&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;再次重新啟動電腦，這次打開「設置」&amp;ndash;&amp;gt;「時間和語言」&amp;ndash;&amp;gt;「日期和時間」&amp;ndash;&amp;gt;「附加時鐘」，切換到「Internet 時間」選項卡，如果出現了「下次同步」的字樣，說明這套系統終於是正常運作了。&lt;/p&gt;&#xA;</description>
        </item><item>
            <title>OBS 多路推流折騰記：那些本不必要的麻煩</title>
            <link>https://blog.l3zc.com/zh-hant-tw/2024/09/my-multi-streaming-setup/</link>
            <pubDate>Wed, 18 Sep 2024 06:16:51 +0000</pubDate>
            <guid>https://blog.l3zc.com/zh-hant-tw/2024/09/my-multi-streaming-setup/</guid>
            <description>&lt;img src=&#34;https://blog.l3zc.com/2024/09/my-multi-streaming-setup/cover_hu_891493d684eb454d.webp&#34; alt=&#34;Featured image of post OBS 多路推流折騰記：那些本不必要的麻煩&#34; /&gt;&lt;p&gt;長話短說，最近我開始直播我的遊戲實況，我並不指望有多少人看，Just, Why not? 再者每個直播平臺都會有直播回放。相當於免費保存我每一次遊戲的錄像，何樂而不為？&lt;/p&gt;&#xA;&lt;p&gt;扯遠了，如你所見，近期我有了多平臺同時直播的需求，然而 OBS 本身顯然不支持同時推流，同時也因為不支持設置代理的特點和眾所周知的原因，無法推流到 twitch.tv，於是我開始尋找解決方案。&lt;/p&gt;&#xA;&lt;h2 id=&#34;明確需求&#34;&gt;明確需求&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;多路平臺同時直播&lt;/li&gt;&#xA;&lt;li&gt;最好對性能影響小&lt;/li&gt;&#xA;&lt;li&gt;其中一路需要推流需要代理&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;多路推流&#34;&gt;多路推流&#xA;&lt;/h2&gt;&lt;p&gt;多路推流有現成的插件可用：&lt;a class=&#34;link&#34; href=&#34;https://github.com/sorayuki/obs-multi-rtmp&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Github&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;在 Release 頁面下載&lt;code&gt;.exe&lt;/code&gt;安裝包，安裝完成後記得在「停靠窗口」菜單裡勾選「多路推流」。這時候默認會彈出一個獨立窗口，可以趁這個機會順便調整一下 OBS 的佈局。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/09/my-multi-streaming-setup/image_hu_b8b674a93b9f86bc.webp&#34; alt=&#34;啟用多路推流面板&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;插件的設置也沒什麼好說的，用在原版 OBS 的填法在這個插件裡如法炮製即可。&lt;/p&gt;&#xA;&lt;h3 id=&#34;bilibili-直播姬&#34;&gt;Bilibili 直播姬&#xA;&lt;/h3&gt;&lt;p&gt;莫名其妙，B 站小於 50 粉絲的 Up 主只能使用直播姬開播，其他國內平臺做出類似規定者大概也不在少數，啟用第三方推流模式之後，直播面板會顯示推流的配置。此時的不需要按照直播姬給出的推流地址進行配置，因為直播姬莫名其妙的給了一個每次都會變的內網 IP，直接以&lt;code&gt;127.0.0.1&lt;/code&gt;或者&lt;code&gt;localhost&lt;/code&gt;替換即可。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/09/my-multi-streaming-setup/image-2_hu_71c45766ee560a9.webp&#34; alt=&#34;Bilibili 直播姬的第三方推流模式&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/09/my-multi-streaming-setup/image-1_hu_17dc4c09eb0ae0af.webp&#34; alt=&#34;插件的設置&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h2 id=&#34;推流到-twitch&#34;&gt;推流到 Twitch&#xA;&lt;/h2&gt;&lt;p&gt;推流到 Twitch 則會因為 GFW 多出很多不必要的麻煩。&lt;/p&gt;&#xA;&lt;p&gt;OBS 本身並不支持設置代理，起初我打算用 Clash 的 TAP 模式強制代理 OBS 的流量，而 Clash 的 TAP 並不監聽 rtmp 流量，宣告失敗。我決定直接架設一個轉發服務器，這個服務器需要既可以與 Twitch 通信，又可以不受 GFW 的干擾。&lt;/p&gt;&#xA;&lt;p&gt;在網上查到一些信息，Caddy 似乎不支持 RTMP 協議，而 &lt;code&gt;nginx-rtmp&lt;/code&gt; 插件則專門為 RTMP 協議設計。這幾乎是唯一的選擇。&lt;/p&gt;&#xA;&lt;p&gt;部署的前提：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;熟悉基本的 Linux 命令行操作。&lt;/li&gt;&#xA;&lt;li&gt;服務器已經安裝好&lt;code&gt;docker&lt;/code&gt;和&lt;code&gt;docker compose&lt;/code&gt;插件。&lt;/li&gt;&#xA;&lt;li&gt;有合適的命令行文本編輯器，比如&lt;code&gt;nvim&lt;/code&gt;或者絕大部分 distro 自帶的&lt;code&gt;nano&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;新建目錄：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;mkdir nginx-rtmp &amp;&amp; cd nginx-rtmp&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;創建&lt;code&gt;docker-compose.yml&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;services:&#xA;  nginx-rtmp:&#xA;    image: tiangolo/nginx-rtmp:latest&#xA;    container_name: nginx_rtmp&#xA;    ports:&#xA;      - &#34;1935:1935&#34; # RTMP port&#xA;    volumes:&#xA;      - ./nginx.conf:/etc/nginx/nginx.conf&#xA;    restart: unless-stopped&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;創建&lt;code&gt;nginx.conf&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-nginx&#34;&gt;worker_processes  auto;&#xA;&#xA;events {&#xA;    worker_connections  1024;&#xA;}&#xA;&#xA;rtmp {&#xA;    server {&#xA;        listen 1935;&#xA;        chunk_size 4096;&#xA;&#xA;        application live {&#xA;            live on;&#xA;            push rtmp://live.twitch.tv/app/{Twitch 主直播密鑰};&#xA;        }&#xA;    }&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;啟動編排：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;docker compose up -d&#xA;# 老版本 compose 插件則是 docker-compose up&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;當然，別忘了開放防火牆端口。我的防火牆參考了 &lt;a class=&#34;link&#34; href=&#34;https://github.com/chaifeng/ufw-docker#solving-ufw-and-docker-issues&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;To Fix The Docker and UFW Security Flaw Without Disabling Iptables&lt;/a&gt; 一文中提到的配置，相應地，開放端口的命令如下：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;ufw route allow proto tcp from any to any port 1935&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;配置完成後，OBS 內的推流目標地址填入&lt;code&gt;rtmp://服務器ip/live&lt;/code&gt;，推流碼填寫任意值均可。設置完成後即可開始推流。&lt;/p&gt;&#xA;&lt;h3 id=&#34;netch&#34;&gt;Netch&#xA;&lt;/h3&gt;&lt;p&gt;如果沒有服務器，亦可以使用 Netch 代理 OBS 進程進行推流。&lt;/p&gt;&#xA;&lt;p&gt;首先下載 Netch 1.9.2 版本，注意一定要是 1.9.2 版本。添加一個進程模式，將 OBS 文件夾加入，隨後啟用該進程模式的代理即可。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/09/my-multi-streaming-setup/image-3_hu_fab77139607785da.webp&#34; alt=&#34;創建一個進程模式&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h2 id=&#34;尾聲&#34;&gt;尾聲&#xA;&lt;/h2&gt;&lt;p&gt;這次為了在同時在 Twitch 和 B 站上推流，遇到了很多原本不必要的麻煩。我可以怪 OBS 沒有內置代理功能，也可以怪 Clash Verge 等代理工具不支持 RTMP，轉來轉去，最終要怪的，還得是 GFW。雖然最後的結果還算令人滿意，僅僅為了推流而折騰這些東西的體驗實在是稱不上良好。也許我今後我應該去研究修改 Mihomo 內核，讓其支持 RTMP 協議，亦或是自己寫一套獨立的實現單獨轉發 OBS 的流量，不論如何，在這片土地上，想要暢快的直播，道阻且長。&lt;/p&gt;&#xA;&lt;p&gt;最後，歡迎關注我的 Twitch 頻道: &lt;a class=&#34;link&#34; href=&#34;https://www.twitch.tv/powerfullz233&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://www.twitch.tv/powerfullz233&lt;/a&gt;&lt;/p&gt;&#xA;</description>
        </item><item>
            <title>docker-mailserver 郵件服務器搭建記錄</title>
            <link>https://blog.l3zc.com/zh-hant-tw/2024/09/docker-mailserver-deployment-recap/</link>
            <pubDate>Fri, 06 Sep 2024 03:57:18 +0000</pubDate>
            <guid>https://blog.l3zc.com/zh-hant-tw/2024/09/docker-mailserver-deployment-recap/</guid>
            <description>&lt;img src=&#34;https://blog.l3zc.com/2024/09/docker-mailserver-deployment-recap/docker-mailserver_hu_a649605529f354c4.webp&#34; alt=&#34;Featured image of post docker-mailserver 郵件服務器搭建記錄&#34; /&gt;&lt;p&gt;某天早上醒來，發現收件箱裡多了封郵件，通知我的郵箱在韓國登錄，當時認為只是偶爾的 IP 誤判，也就沒有多想。可接下來幾天，時不時又有新的郵件進來，而且每次 IP 都不一樣，這下可就有些問題了，再不採取行動，怕不是郵箱真的要被拿去群發郵件。於是趕緊刪掉了域名的 MX、SPF、DMARC 記錄，尋找新的解決方案。&lt;/p&gt;&#xA;&lt;p&gt;我原本只是想改改密碼，結果呢，騰訊企業郵箱改密碼的入口真的巨能藏，到處都找不到。算了，遷移，何必受這氣。&lt;/p&gt;&#xA;&lt;p&gt;於是就有了這篇文章，從最開始的開放防火牆端口開始，到最後的優化送達率，我將分享我搭建 docker-mailserver 郵件服務器的經驗，希望能幫你少踩些坑。&lt;/p&gt;&#xA;&lt;h2 id=&#34;搭建-docker-mailserver-郵件服務器前的準備&#34;&gt;搭建 docker-mailserver 郵件服務器前的準備&#xA;&lt;/h2&gt;&lt;h3 id=&#34;防火牆的處理&#34;&gt;防火牆的處理&#xA;&lt;/h3&gt;&lt;p&gt;首先先開放防火牆的郵件服務常用端口，分別是&lt;code&gt;25&lt;/code&gt;、&lt;code&gt;143&lt;/code&gt;、&lt;code&gt;465&lt;/code&gt;、&lt;code&gt;587&lt;/code&gt;、&lt;code&gt;993&lt;/code&gt;，各分支開放端口的操作不同，請自行谷歌或詢問 AI，以 Ubuntu 使用的&lt;code&gt;ufw&lt;/code&gt;為例：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;ufw allow 25&#xA;ufw allow 143&#xA;ufw allow 465&#xA;ufw allow 587&#xA;ufw allow 993&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我的防火牆比較特殊，用到了&lt;code&gt;ufw&lt;/code&gt;和 &lt;a class=&#34;link&#34; href=&#34;https://github.com/chaifeng/ufw-docker#solving-ufw-and-docker-issues&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;To Fix The Docker and UFW Security Flaw Without Disabling Iptables&lt;/a&gt; 一文中提到的配置。所以當我想要把 Docker 容器的端口暴露在外時需要做出額外的配置。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# 開放郵件服務器所需的端口&#xA;ufw route allow proto tcp from any to any port 25&#xA;ufw route allow proto tcp from any to any port 143&#xA;ufw route allow proto tcp from any to any port 465&#xA;ufw route allow proto tcp from any to any port 587&#xA;ufw route allow proto tcp from any to any port 993&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;驗證你的服務器是否具備搭建條件&#34;&gt;驗證你的服務器是否具備搭建條件&#xA;&lt;/h3&gt;&lt;p&gt;很多服務器，尤其是各種廉價的 VPS，並不適合搭建 docker-mailserver，各大郵件服務商為了過濾垃圾郵件，索性將所有曾經有過 Spam 行為的 IP 全部列入黑名單。如果你的 IP 被某個 Spammer 使用過，而恰好又被你的服務器提供商分配給了你，而你折騰了幾個小時的郵件服務器卻愣是收不到郵件，最後發現竟是 IP 被列入黑名單的原因，我不知道你會作何感想。&lt;/p&gt;&#xA;&lt;p&gt;有的服務器提供商的 IP 乍一看確實不在郵件黑名單中，但實際上搭建好之後還是無法發送郵件，為什麼？因為服務器提供商擔心 IP 被濫用，索性將郵件服務的端口全都封鎖了。&lt;/p&gt;&#xA;&lt;p&gt;使用如下腳本檢測你的服務器是否具備搭建郵件服務器的條件：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;bash &lt;(curl -sL IP.Check.Place)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/09/docker-mailserver-deployment-recap/image-3_hu_6dacc5e9abb4b1ca.webp&#34; alt=&#34;不能搭建郵件服務器&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/09/docker-mailserver-deployment-recap/image-4_hu_fb1b1c9dd78cde41.webp&#34; alt=&#34;可以搭建郵件服務器&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;如果在這一步發現你的服務器不適合搭建甚至不能搭建&lt;code&gt;docker-mailserver&lt;/code&gt;，那就趁早打住吧，想辦法換一個更乾淨、限制更少的 IP 再說。&lt;/p&gt;&#xA;&lt;h2 id=&#34;搭建過程&#34;&gt;搭建過程&#xA;&lt;/h2&gt;&lt;h3 id=&#34;拉取配置文件&#34;&gt;拉取配置文件&#xA;&lt;/h3&gt;&lt;p&gt;docker-mailserver 需要配置的配置文件有兩個，&lt;code&gt;compose.yaml&lt;/code&gt;和&lt;code&gt;mailserver.env&lt;/code&gt;，分別拉取配置文件：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;mkdir mailserver &amp;&amp; cd mailserver&#xA;sudo apt install wget&#xA;wget -O compose.yaml &#34;https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/master/compose.yaml&#34;&#xA;wget -O mailserver.env &#34;https://github.com/docker-mailserver/docker-mailserver/blob/master/mailserver.env&#34;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;根據需求，可以修改不同的配置，下面是我修改的部分：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;POSTMASTER_ADDRESS=webmaster@l3zc.com&#xA;TZ=Asia/Shanghai&#xA;SSL_TYPE=manual&#xA;SSL_CERT_PATH=/certs/cert.crt&#xA;SSL_KEY_PATH=/certs/cert.key&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;用-caddy-準備和自動維護-tls-證書&#34;&gt;用 Caddy 準備和自動維護 TLS 證書&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&#xA;&lt;/h3&gt;&lt;p&gt;因為我已經在使用 Caddy，所以我這次就打算直接使用 Caddy 來自動維護證書。根據項目的官方文檔，我只需要在 Caddyfile 裡新增一個子域名，Caddy 就可以自動幫我維護證書。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-caddyfile&#34;&gt;mail.example.com {&#xA;  tls internal {&#xA;    # ?? 這不是直接生成的內部證書麼&#xA;    key_type rsa2048&#xA;  }&#xA;&#xA;  # Optional, can be useful for troubleshooting&#xA;  # connection to Caddy with correct certificate:&#xA;  respond &#34;Hello DMS&#34;&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;當我更新完 DNS 記錄，實際操作起來發現，我的情況比較特殊：我這臺服務器上並沒有用到 Caddy 的自動申請證書功能，而是使用了15年有效期的泛域名 Cloudflare Origin Certificate。即使我不為這個子域名指定證書文件，Caddy 也會在加載這張泛域名證書後認為無需再申請新的證書。這就很尷尬了，用 Caddy 的時候 Cloudflare Origin Certificate 和郵件服務器只能二選一。只好安裝了&lt;code&gt;dns.providers.cloudflare&lt;/code&gt;，並放棄使用 Cloudflare Origin Certificate，轉而讓 Caddy 自動為每個子域名申請和維護 TLS/SSL 證書，反正都是自動的，眼不見心不煩。&lt;/p&gt;&#xA;&lt;p&gt;安裝&lt;code&gt;dns.providers.cloudflare&lt;/code&gt;後的全局配置：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-caddyfile&#34;&gt;{&#xA;        acme_dns cloudflare {API_KEY}&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;最後把 Caddy 的證書存儲位置映射到 docker-mailserver 容器內。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;volumes:&#xA;  - /home/user/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/yourdomain.tld/:/certs/&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果 Caddy 以 root 用戶運行，則證書的位置應當為&lt;code&gt;/root/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/yourdomain.tld/&lt;/code&gt;，做出相應的修改即可。&lt;/p&gt;&#xA;&lt;p&gt;然後就是之前提到過的配置，我將證書存儲位置映射到了容器內的&lt;code&gt;/certs/&lt;/code&gt;目錄，所以配置為：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;SSL_CERT_PATH=/certs/cert.crt&#xA;SSL_KEY_PATH=/certs/cert.key&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;首次啟動容器&#34;&gt;首次啟動容器&#xA;&lt;/h3&gt;&lt;p&gt;首次啟動容器需要立即（120 秒內）創建新的 Postmaster 賬號。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;docker compose up -d&#xA;docker exec -it &lt;CONTAINER NAME&gt; setup email add &lt;postmaster@yourdomain.tld&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;優化送達率&#34;&gt;優化送達率&#xA;&lt;/h2&gt;&lt;h3 id=&#34;dns-記錄&#34;&gt;DNS 記錄&#xA;&lt;/h3&gt;&lt;ul&gt;&#xA;&lt;li&gt;一條 A 記錄，名稱隨意（假設為&lt;code&gt;mail&lt;/code&gt;），指向服務器 IP（Cloudflare 需使用「僅 DNS」）&lt;/li&gt;&#xA;&lt;li&gt;一條 MX 記錄，Apex 域（@），指向&lt;code&gt;mail.yourdomain.tld&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;一條 TXT 記錄（SPF），Apex 域（@），可以在&lt;a class=&#34;link&#34; href=&#34;https://mxtoolbox.com/SPFRecordGenerator.aspx&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;這裡&lt;/a&gt;生成(可選)。&lt;/li&gt;&#xA;&lt;li&gt;一條 TXT 記錄（DMARC），名稱&lt;code&gt;_dmarc&lt;/code&gt;，可以在&lt;a class=&#34;link&#34; href=&#34;https://mxtoolbox.com/DMARCRecordGenerator.aspx&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;這裡&lt;/a&gt;生成（可選）。&lt;/li&gt;&#xA;&lt;li&gt;一條 TXT 記錄（DKIM），名稱&lt;code&gt;mail._domainkey&lt;/code&gt;，具體配置在後文（可選）。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;dkim-spf-和-dmarc&#34;&gt;DKIM, SPF 和 DMARC&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&#xA;&lt;/h3&gt;&lt;p&gt;什麼是 DKIM，SPF 和 DMARC？簡單來說，DKIM 是一個數字簽名機制；SPF 類似於「員工名單」，記錄所有合法的發件服務器；DMARC 則用於指示收件方的服務器如何處理未通過 DKIM 和 SPF 驗證的郵件。&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#xA;&lt;p&gt;首先設置 DKIM，在服務器上生成公私鑰對。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;docker exec -it &lt;CONTAINER NAME&gt; setup config dkim&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;生成後的公私鑰對可以在映射的容器目錄&lt;code&gt;./docker-data/dms/config/opendkim/keys/&lt;/code&gt;下找到。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/09/docker-mailserver-deployment-recap/image_hu_1f64fffc01f8db13.webp&#34; alt=&#34;mail.txt 便是公鑰的 DNS Zone file&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/09/docker-mailserver-deployment-recap/image-1_hu_d6c86630e3947c78.webp&#34; alt=&#34;Cloudflare 的導入入口&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;下載 mail.txt（複製其內所有內容粘貼到本地文件也可以），將其導入到 Cloudflare Dashboard 即可完成 DKIM 設置。需要注意配置完成 DKIM 後需要重啟容器才能生效。&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;docker compose up -d --force-recreate&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;前面提到 SPF 相當於一個「員工名單」，既然我合法的發件服務器只有一臺，那麼 DNS 填寫如下設置即可。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;&#34;v=spf1 mx ~all&#34;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;再就是 DMARC，可以使用前面提到的模板生成，這裡不再贅述。&lt;/p&gt;&#xA;&lt;p&gt;如果你的域名服務商擁有自己的設置嚮導（例如 Cloudflare），亦可以使用它們簡化設置流程。&lt;/p&gt;&#xA;&lt;h2 id=&#34;客戶端設置&#34;&gt;客戶端設置&#xA;&lt;/h2&gt;&lt;p&gt;客戶端設置相對就很簡單了，IMAP 和 SMTP 服務器都是&lt;code&gt;mail.yourdomain.ltd&lt;/code&gt;，開啟 SSL 後端口分別是 465 和 993，登錄即可。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/09/docker-mailserver-deployment-recap/image-2_hu_51756253bf099359.webp&#34; alt=&#34;設置示例&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h2 id=&#34;善用-mailtester&#34;&gt;善用 MailTester&#xA;&lt;/h2&gt;&lt;p&gt;至此我們已經完成了搭建 docker-mailserver 郵件服務器的整個過程，在開始發送郵件之前，我們可以使用 &lt;a class=&#34;link&#34; href=&#34;https://www.mail-tester.com/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;MailTester&lt;/a&gt; 測試我們的配置是否正確。如果測試結果是 10/10，那麼我們的 docker-mailserver 郵件服務器應該會擁有漂亮的送達率，好好享受吧。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/09/docker-mailserver-deployment-recap/image-5_hu_6de856f3e3b52ca0.webp&#34; alt=&#34;完美的評分&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;如果不是 10/10，也不要著急，認真查看扣分的原因，對症下藥，一項一項解決即可。&lt;/p&gt;&#xA;&lt;h2 id=&#34;todo-list&#34;&gt;TODO-List&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 配套 WebUI&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;參考: &lt;a class=&#34;link&#34; href=&#34;https://docker-mailserver.github.io/docker-mailserver/latest/config/security/ssl/#caddy&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://docker-mailserver.github.io/docker-mailserver/latest/config/security/ssl/#caddy&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;參考: &lt;a class=&#34;link&#34; href=&#34;https://docker-mailserver.github.io/docker-mailserver/latest/config/best-practices/dkim_dmarc_spf/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://docker-mailserver.github.io/docker-mailserver/latest/config/best-practices/dkim_dmarc_spf/&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;更加具體的介紹: &lt;a class=&#34;link&#34; href=&#34;https://www.cloudflare.com/learning/email-security/dmarc-dkim-spf/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://www.cloudflare.com/learning/email-security/dmarc-dkim-spf/&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:4&#34;&gt;&#xA;&lt;p&gt;在&lt;a class=&#34;link&#34; href=&#34;https://docker-mailserver.github.io/docker-mailserver/latest/config/best-practices/dkim_dmarc_spf/#generating-keys&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;官方文檔&lt;/a&gt;中有提及。&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
        </item></channel>
</rss>
