<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
	<title>陪她去流浪</title>
	<link>https://blog.twofei.com</link>
	<description>博客建站🔟周年快乐🎉！圣诞快乐🎄！</description>
	<lastBuildDate>Mon, 15 Jun 2026 22:19:42 HKT</lastBuildDate>
	<item>
		<title>让Codex修复了8年前写的NES游戏模拟器的Bugs</title>
		<link>https://blog.twofei.com/2304/</link>
		<pubDate>Tue, 09 Jun 2026 17:06:16 HKT</pubDate>
		<description><![CDATA[<p>两个问题：图形处理器有奇怪的显示问题、没有音乐处理器。</p>
<h2>图形处理器奇怪的形变</h2>
<p>当我看到下面这个形变的鸭子时还是很震惊的。</p>
<div class="gallery"><img src="old.avif" alt="" width="736" height="768" data-metadata="[&#34;名字&#34;,&#34;old.avif&#34;,&#34;大小&#34;,&#34;19.8KB&#34;,&#34;尺寸&#34;,&#34;736x768&#34;,&#34;类型&#34;,&#34;image/avif&#34;]" data-thumb-hash="YQaKHQA4V2iKdoBLqJiDnw3qCDiJd3GKNw==" loading="lazy"/><img src="new.avif" alt="" width="736" height="768" data-metadata="[&#34;名字&#34;,&#34;new.avif&#34;,&#34;大小&#34;,&#34;19.5KB&#34;,&#34;尺寸&#34;,&#34;736x768&#34;,&#34;类型&#34;,&#34;image/avif&#34;]" data-thumb-hash="YQaGHQA4V2iKdoBLqJhznwzaCDiJd3GKNw==" loading="lazy"/></div>
<p>那时候还没有代码编码助手，只是不停地看手册堆表，完全不知道错在哪里。没想到是抄错了指令表？</p>
<pre><code class="language-diff">taones (master) → git diff
diff --git a/cpu.go b/cpu.go
index 6fecf45..77f692c 100644
--- a/cpu.go
+++ b/cpu.go
@@ -42,7 +42,7 @@ type OpCode struct {
 
 var opcodesTable = [...]OpCode{
        // adc
-       {0x69, &#34;ADC&#34;, 2, 3, 0, amImmediate},
+       {0x69, &#34;ADC&#34;, 2, 2, 0, amImmediate},
        {0x65, &#34;ADC&#34;, 2, 3, 0, amZero},
        {0x75, &#34;ADC&#34;, 2, 4, 0, amZeroX},
        {0x6D, &#34;ADC&#34;, 3, 4, 0, amAbsolute},
@@ -57,12 +57,12 @@ var opcodesTable = [...]OpCode{
        {0x35, &#34;AND&#34;, 2, 4, 0, amZeroX},
        {0x2D, &#34;AND&#34;, 3, 4, 0, amAbsolute},
        {0x3D, &#34;AND&#34;, 3, 4, 1, amAbsoluteX},
-       {0x39, &#34;AND&#34;, 2, 4, 1, amAbsoluteY},
+       {0x39, &#34;AND&#34;, 3, 4, 1, amAbsoluteY},
        {0x21, &#34;AND&#34;, 2, 6, 0, amIndexedIndirect},
        {0x31, &#34;AND&#34;, 2, 5, 1, amIndirectIndexed},
 
        // asl
-       {0x0A, &#34;ASL&#34;, 1, 3, 0, amAccumulator},
+       {0x0A, &#34;ASL&#34;, 1, 2, 0, amAccumulator},
        {0x06, &#34;ASL&#34;, 2, 5, 0, amZero},
        {0x16, &#34;ASL&#34;, 2, 6, 0, amZeroX},
        {0x0E, &#34;ASL&#34;, 3, 6, 0, amAbsolute},
</code></pre>
<blockquote>
<p>问题主要不在 PPU sprite 位序，而在 CPU 指令表。</p>
<p>我修了 cpu.go 里的三个错误表项：</p>
<ul class="marker-minus">
<li><code>ADC #imm</code> 周期从 <code>3</code> 改成 <code>2</code></li>
<li><code>AND $addr,Y</code> 长度从 <code>2</code> 改成 <code>3</code></li>
<li><code>ASL A</code> 周期从 <code>3</code> 改成 <code>2</code></li>
</ul>
<p>其中 <code>AND $addr,Y</code> 长度错最危险，会让 <code>PC</code> 少前进 1 字节，后续执行流可能跑偏，间接导致 OAM/图形数据异常；两个周期错会让 CPU/PPU 时序偏移，也容易出现 sprite 或画面更新时机问题。</p>
</blockquote>
<p>错了3️⃣条指令，但是问题居然并不是特别明显：</p>
<ul class="marker-asterisk">
<li>其中两条只是影响了指令周期，问题不大。</li>
<li>而指令长度会完全影响指令指针（PC）后续的所有指令，CPU直接跑飞……</li>
</ul>
<h2>增加音乐处理器</h2>
<blockquote>
<p>不然的话，音乐的模拟怎么也不应该缺席。不会只写了个开头就八年没再动过了。当然，现在更不会有时间去研究了。</p>
</blockquote>
<p>前几天<sup id="fn:03988a28"><a href="#fn:6268fbc1" class="footnote-ref" role="doc-noteref">1</a></sup>才报怨自己没时间再去研究，没想到反手就让Codex给写了一个。代码全部是Codex写的，我一行没写，全程不到🔟️分钟。</p>
<p><video controls="" src="smb.mp4" width="551" height="430" preload="none"></video></p>
<h2>结语</h2>
<p>太好了，现在终于勉强算得上是一个没有遗憾的NES模拟器了。</p>
<div class="footnotes" role="doc-endnotes">
<hr/>
<ol>
<li id="fn:6268fbc1">
<p><a href="/2036/">强如苹果词典中的牛津词典对“Attack”在乐理中的含义也没有比较准确的...</a> <a href="#fn:03988a28" class="footnote-backref" role="doc-backlink">^</a></p>
</li>
</ol>
</div>
]]></description>
	</item>
	<item>
		<title>用qemu在终端直接启动Linux虚拟机系统</title>
		<link>https://blog.twofei.com/2303/</link>
		<pubDate>Sun, 07 Jun 2026 16:47:46 HKT</pubDate>
		<description><![CDATA[<p>经常用UTM在MacOS创建虚拟机Linux系统做一些相关测试，最近才发现UTM本身其实就是个QEMU的简单包装。所以，我直接把UTM创建好后的虚拟机命令（用ps命令拿到的）让Codex给我简化了一番，我现在可以非常简单快速的用命令创建、启动虚拟机了。</p>
<div class="image-scroll-outer"><p><img src="login.avif?og=" alt="" width="608" height="359" data-metadata="[&#34;名字&#34;,&#34;login.avif&#34;,&#34;大小&#34;,&#34;44.8KB&#34;,&#34;尺寸&#34;,&#34;1216x718&#34;,&#34;类型&#34;,&#34;image/avif&#34;]" data-thumb-hash="EAgGBICPd3eIeHeHZ4kAAAAAAA==" data-contrast-ratio="0.9958592" loading="lazy"/></p></div>
<h2>创建硬盘</h2>
<p>创建的硬盘用于安装系统。使用如下命令：</p>
<pre><code class="language-bash">$ qemu-img create -f qcow2 alpine.qcow2 1G
Formatting &#39;alpine.qcow2&#39;, fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib size=1073741824 lazy_refcounts=off refcount_bits=16
</code></pre>
<p>参数“<code>alpine.qcow2</code>”表示硬盘在当前文件系统的文件名，<code>1G</code>表示总大小<sup id="fn:dffb6c2d"><a href="#fn:56bf5d1a" class="footnote-ref" role="doc-noteref">1</a></sup>。</p>
<p>可以把这个文件看作是一个类似Linux上的稀疏文件。刚创建好后可能非常小，后期写入后才会慢慢变大。</p>
<p>用ls/du可以看到真实大小，file命令可以分析文件结构并显示最终大小。</p>
<pre><code class="language-bash">$ du -sh alpine.qcow2
208M	alpine.qcow2
$ file alpine.qcow2
alpine.qcow2: QEMU QCOW2 Image (v3), 1073741824 bytes
</code></pre>
<h2>安装系统</h2>
<p>安装系统的过程可以简单分成以下两个过程：</p>
<ol class="marker-period">
<li>就是运行一个指定需求的虚拟机、挂载上硬盘、挂载上光碟💿️（系统ISO镜像）</li>
<li>把光碟上的系统安装到硬盘</li>
</ol>
<p>我使用了如下的命令行（由Codex从UTM命令行精简得来）：</p>
<pre><code class="language-bash">$ sudo qemu-system-aarch64 \
    -machine virt \
    -accel hvf \
    -cpu host \
    -smp 1  \
    -m 512 \
    -nographic \
    -serial mon:stdio \
    -drive if=pflash,format=raw,readonly=on,file=/opt/homebrew/share/qemu/edk2-aarch64-code.fd \
    -drive if=virtio,file=alpine.qcow2,format=qcow2 \
    -device virtio-net-pci,netdev=net0 \
    -netdev vmnet-bridged,id=net0,ifname=en0 \
    -cdrom alpine-virt-3.23.4-aarch64.iso
</code></pre>
<p>我简单描述一下上面各参数的含义：</p>
<ol class="marker-period">
<li>使用ARM的Virt虚拟机方案，HyperVisor.framework框架，主机CPU；</li>
<li>1个CPU核心、512MB内存；</li>
<li>不进入图形系统（进入终端）；</li>
<li>把串口和qemu管理命令/终端绑定到一起（默认是进入终端，按Ctrl-A + H可以查看qemu帮助）；</li>
<li>挂载UEFI固件（就是上面那个edk2文件），路径可能与你自己安装的Homebrew相关，按需修改；</li>
<li>挂载指定路径的硬盘；</li>
<li>创建一张网卡；</li>
<li>创建一张网桥，并绑定前面的网卡和宿主机的桥接网卡（MacOS的Wi-Fi叫en0）；</li>
<li>插入CD光碟，用于提供系统安装来源；</li>
</ol>
<p>我使用的是Alpine的Virt版精简系统，ISO文件不足100MB，以下是系统安装过程：</p>
<ol class="marker-period">
<li>
<p>运行上面的命令后，就会进入登录提示符，输入<code>root</code>（无密码）进入到Live系统（运行在RAM内）；</p>
</li>
<li>
<p>运行<code>setup-alpine</code>就可以安装系统了。</p>
</li>
<li>
<p>主机名随意写，默认就好；</p>
</li>
<li>
<p>网络接口唯一可能用的就是上面创建的<code>eth0</code>；</p>
</li>
<li>
<p>由于刚刚创建了网桥把虚拟机的eth0和宿主机的en0桥接起来，所以DHCP可以分配到和宿主同网段的IP地址；</p>
<pre><code class="language-text"> Interface
-----------
Available interfaces are: eth0.
Enter &#39;?&#39; for help on bridges, bonding and vlans.
Which one do you want to initialize? (or &#39;?&#39; or &#39;done&#39;) [eth0]
Ip address for eth0? (or &#39;dhcp&#39;, &#39;none&#39;, &#39;?&#39;) [dhcp]
Do you want to do any manual network configuration? (y/n) [n]
udhcpc: started, v1.37.0
udhcpc: broadcasting discover
udhcpc: broadcasting select for 192.168.10.231, server 192.168.10.1
udhcpc: lease of 192.168.10.231 obtained from 192.168.10.1, lease time 43200
</code></pre>
</li>
<li>
<p>然后是设置root的密码；</p>
</li>
<li>
<p>时区设置。我在中国，先写Asia，再填Shanghai；</p>
</li>
<li>
<p>按需填写代理配置；</p>
</li>
<li>
<p>时间同步协议客户端（NTP）默认就好；</p>
</li>
<li>
<p>镜像源在国内填<code>14</code>（即“mirrors.ustc.edu.cn”）比较快；</p>
</li>
<li>
<p>无需设置独立用户；</p>
</li>
<li>
<p>使用OpenSSH作为SSH服务器；</p>
</li>
<li>
<p>是否允许root登录？填“yes”吧，可以允许密码登录，虚拟机无所谓安全了；</p>
</li>
<li>
<p>要使用哪块磁盘？直接填唯一的一块“vda”即可；</p>
</li>
<li>
<p>如何使用选中的磁盘？填“sys”即可。</p>
</li>
<li>
<p>擦除磁盘并继续？“y”。</p>
</li>
<li>
<p>然后就是安装过程，很快完成。</p>
</li>
</ol>
<p>提示安装完成后，不要按提示执行“reboot”，而是“poweroff”。因为这时候光碟还在，重启后可能再次进入系统安装过程。</p>
<h2>启动系统</h2>
<p>“poweroff”后，去掉命令行的最后一个选项（及其参数）————光碟，即“<code>-cdrom alpine-virt-3.23.4-aarch64.iso</code>”。再次执行命令，这下进入的就是刚安装好的、最终的系统了。</p>
<p>以后也仅需重复执行上述命令即可开启系统。</p>
<h2>退出系统</h2>
<p>在终端里面按“Ctrl-c”只能取消运行命令，无法退出虚拟机。这时候需要先按qemu的前缀键“Ctrl-a”，再按“x”即可退出（按“h”可以看到全部的帮助）。</p>
<p>但是不建议这样“暴力”退出，建议执行“poweroff”安全关机，否则下次开机时会提示文件系统损坏（但是会修复成功）。</p>
<h2>终端尺寸问题</h2>
<p>由于是把串口接入到终端的，无法向ssh的pty那样有窗口大小同步协议。可以尝试执行<code>resize</code>命令自动缩放窗口大小。</p>
<pre><code class="language-bash">localhost:~# resize
COLUMNS=238;LINES=59;export COLUMNS LINES;
</code></pre>
<p>如果没有<code>resize</code>命令，也可以手动调整：</p>
<pre><code class="language-bash">localhost:~# stty rows 25 cols 80
</code></pre>
<div class="footnotes" role="doc-endnotes">
<hr/>
<ol>
<li id="fn:56bf5d1a">
<p>建议多分配一些，就我测试用的Alpine Linux精简版而言，100+分给了UEFI、200+分给了Swap，根文件系统只剩下了600+（但是对我来说够用了）。 <a href="#fn:dffb6c2d" class="footnote-backref" role="doc-backlink">^</a></p>
</li>
</ol>
</div>
]]></description>
	</item>
	<item>
		<title>像写Python的文档注释一样写Markdown的块引用</title>
		<link>https://blog.twofei.com/2290/</link>
		<pubDate>Fri, 05 Jun 2026 23:07:22 HKT</pubDate>
		<description><![CDATA[<p>我很早就在文章《<a href="/1690/">我也希望 Markdown 能够支持“内容引用/嵌入”</a>》中表明过对目前的Markdown语法对于块引用较复杂的内容时的写法过于复杂的担忧。今天在写前一篇文章《<a href="/2301/">排查Linux一处网络设备命名问题</a>》时我又遇到了这个困境。所以在坐地铁闲得没事儿做的时候，我给Codex下达了一个命令计划，并尝试帮我实现我提到的想法。</p>
<p>目前Markdown写块引用最大的问题在于<strong>需要给每一行内容加上“<code>&gt; </code>”前缀</strong>。如果是完全手写，那没有一点问题；但是如果是粘贴引用一段外部的内容，那就是灾难（而我经常引用ChatGPT生成的内容）。每到这种时候，我都是切换到支持VIM绑定的编辑器，按“VG”进入块选中状态，然后按“I”进入块编辑状态，这样可以一次性给所有选中的行插入“<code>&gt; </code>”前缀。然后再粘贴回文章编辑器。我只能说，任务确实是完成了，但就体验来说那是真的非常割裂。</p>
<p>先对比一下两种写法：</p>
<div style="display:flex;gap:16px;overflow-x:auto;">
<div style="flex:1;">
<pre><code class="language-markdown">&gt; This is a paragraph.
&gt;
&gt; ```go
&gt; func main() { }
&gt; ```
&gt;
&gt; 这是一个列表：
&gt;
&gt; * item1
&gt; * item2
</code></pre>
</div>
<div style="flex:1;">
<pre><code class="language-markdown">&#34;&#34;&#34;
This is a paragraph.

```go
func main() { }
```

这是一个列表：

* item1
* item2
&#34;&#34;&#34;
</code></pre>
</div>
</div>
<p>Codex一次性就成功地实现了我的需求，非常精炼，加上测试才200+行代码，我一行代码都没有写：<a href="https://github.com/movsb/taoblog/commit/4dbca032e46a51ddc34836ac3a2df695e8e80306">markdown: extension: fenced blockquote</a>。使用“<code>&#34;&#34;&#34;</code>”而不是“<code>&gt;&gt;&gt;</code>”的原因是后者其实是现有的标准Markdown语法，可能会有冲突。而使用“<code>&#34;&#34;&#34;</code>”这种看起来像Python文档注释的写法确实是一种全新的语法。这可能是我几十个Markdown扩展中第一次出现新的不兼容语法。</p>
]]></description>
	</item>
	<item>
		<title>排查Linux一处网络设备命名问题</title>
		<link>https://blog.twofei.com/2301/</link>
		<pubDate>Thu, 04 Jun 2026 22:04:37 HKT</pubDate>
		<description><![CDATA[<p>我的小主机接入了两个网络：一个是通过网线有线接入的（流量有限，速度较快），一个是通过WiFi无线接入的（流量较多，速度较慢）。这样的网络配置我用了很久，但是某天重启了系统后，突然发现处于同一有线网络内的SSH居然连不上它了。以下是排查问题过程。</p>
<h2>背景</h2>
<p>文件“<code>/etc/network/interfaces</code>”的内容如下：</p>
<pre><code class="language-text">auto lo
iface lo inet loopback

auto eth0
iface eth0 inet dhcp
  metric 500

auto wlan0
iface wlan0 inet dhcp
  metric 300
</code></pre>
<p>简单解释一下：</p>
<ul class="marker-asterisk">
<li><code>eth0</code>和<code>wlan0</code>分别是有线和无线网络；</li>
<li>均设置成DHCP自动获取IP地址；</li>
<li>无线网络的优先级更高（metric更低）；</li>
</ul>
<p>有线网络网段是<code>192.168.10.0/24</code>，无线网络网段是<code>192.168.0.0/24</code>。</p>
<h2>现象</h2>
<p>SSH走TCP连接不上后，我尝试用SSH走蓝牙连接<sup id="fn:b8006b37"><a href="#fn:6f304a38" class="footnote-ref" role="doc-noteref">1</a></sup>，查看当前的IP地址时，发现两个IP地址均在<code>192.168.0.0/24</code>。太神奇了不是吗？我的<code>eth0</code>明明是走有线连接到路由器的，路由器完全正常工作（有子网、有DHCP……）。</p>
<pre><code class="language-bash">$ ifconfig eth0; ifconfig wlan0;
eth0      Link encap:Ethernet  HWaddr 34:4B:50:00:00:00
          inet addr:192.168.0.104  Bcast:0.0.0.0  Mask:255.255.255.0
          inet6 addr: fe80::364b:50ff:fe00:0/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:1542 errors:0 dropped:0 overruns:0 frame:0
          TX packets:374 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:419837 (409.9 KiB)  TX bytes:217831 (212.7 KiB)

wlan0     Link encap:Ethernet  HWaddr F8:AC:65:DA:62:40
          inet addr:192.168.0.102  Bcast:0.0.0.0  Mask:255.255.255.0
          inet6 addr: fe80::faac:65ff:feda:6240/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:144725816 errors:0 dropped:0 overruns:0 frame:0
          TX packets:50473211 errors:0 dropped:1 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:171787073182 (159.9 GiB)  TX bytes:5691888559 (5.3 GiB)
</code></pre>
<p>让Codex排查了一番，它居然跟我说是因为我的路由器把小主机的DHCP请求转发/桥接给了路由器的上级，所以IP是来自……上级的上级。我网络中上级的上级的IP网段确实在<code>192.168.0.0/24</code>，这一度让我差点信以为真。我最终没有相信Codex的说法，因为：</p>
<ul class="marker-asterisk">
<li>我从未听说过DHCP请求还能被路由器转发/桥接……；</li>
<li>我目前的路由器网络中已有很多设备在正常工作中，完全没有出现过此类问题；</li>
<li>我在路由器上抓包并没有抓到来自小主机的DHCP请求（包含转发/桥接的）。</li>
</ul>
<h2>排查</h2>
<p>对比小主机的<code>eth0</code>和路由器的<code>eth1</code>（它们“物理”相连）的<code>ifconfig</code>后我发现，路由器的<code>eth1</code>显示“NO-CARRIER”。也就是说，路由器的LAN口并没有“承载网络”————没有和任何设备相连。所以，Codex前面说路由器转发/桥接了小主机的DHCP请求完全是一派胡言。</p>
<p>斩断这条路线后，我马上有了自己的结论：<strong>这两个IP地址都来自无线网络设备（分配的）</strong>。</p>
<p>等等，<code>eth0</code>一听就是有线网络设备（例如:RJ45端口）的名字啊，怎么可能也来自无线网络？好问题，我“隐瞒”了一个事实：此无线网络是一个USB随身WiFi设备，是插在小主机的USB上供电的。</p>
<p>也就是说，我这个便宜的随身WiFi设备<strong>可能支持USB供网</strong>；同时，我的小主机也<strong>安装了USB CDC 以太网络驱动</strong>。</p>
<p>然后我再次让Codex根据我的思路重新查了一下，果然不出我所料：</p>
<blockquote>
<p>因为 Linux 里的 eth* 不等于“RJ45 口”。任何看起来像以太网的设备都可能叫 ethX。</p>
<p>我查到小主机上实际是：</p>
<pre><code class="language-bash">eth0: USB CDC Ethernet
driver: cdc_ether
device: ZTE CDC Ethernet Device / USB Mobile Broadband
MAC: 34:4b:50:00:00:00
state: up

eth1: 板载 Intel 有线网卡，也就是外壳上的 RJ45
driver: e1000e
MAC: 1c:69:7a:6d:27:2e
state: down
</code></pre>
<p>所以答案是：你的 eth0 不是那个 RJ45 口，而是一个 USB 设备暴露出来的“虚拟以太网”接口，看起来像 ZTE 移动宽带/USB 网络设备。它先被内核注册，所以拿到了名字 eth0；真正的 RJ45 反而叫 eth1。</p>
</blockquote>
<p>另外，一开始我没主动意识到这个问题还有另外一个原因：<code>ifconfig</code>不会显示“DOWN”状态的网卡，eth0被顶替后我没发现；而<code>ip link</code>即便没有IP地址也会显示网卡信息。我第一次用的是前者，没有想到这一层问题。</p>
<p>我好奇Codex查了什么数据库知道那个<code>eth0</code>的详细描述信息的。发现它查了以下信息：</p>
<pre><code class="language-bash">/sys/class/net/eth0/device -&gt; /sys/devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.0
</code></pre>
<p>然后查到了这是个USB CDC（Communications Device Class）设备，然后就查了驱动描述符信息，找到了设备厂商等较详细的信息。</p>
<p>如此一来，我前面配置在<code>/etc/network/interfaces</code>中的带（自动分配的）具体名字的配置就可能已经失效了。</p>
<h2>网络接口名字被抢占的问题</h2>
<p>我的小主机物理自带一个RJ45网口，挂在PCIe上。按理说会被内核极早地识别并加载（然后命名），为什么会被插入的USB抢了先？难以理解。（更新：USB其实也是挂在PCIe上的，所以是顺序不确定问题。）</p>
<p>所以，我觉得<strong>手动永久地给我的RJ45端口绑定一个固定的名字</strong>是一个非常好的做法。</p>
<p>我用的系统是AlpineLinux，Codex告诉内核的接口热插拔管理是mdev负责的：</p>
<pre><code class="language-bash">root@nuc:~ → cat /proc/sys/kernel/hotplug
/sbin/mdev
root@nuc:~ → ls -lh /sbin/mdev
lrwxrwxrwx    1 root     root          12 Apr 22 08:32 /sbin/mdev -&gt; /bin/busybox
</code></pre>
<p><code>mdev</code>会读取<code>/etc/mdev.conf</code>文件，其中配置了网络接口如何管理，如下：</p>
<pre><code class="language-bash"># net devices
# 666 is fine: https://www.kernel.org/doc/Documentation/networking/tuntap.txt
net/tun[0-9]*	root:netdev 0666
net/tap[0-9]*	root:netdev 0666
SUBSYSTEM=net;INTERFACE=.*;.*   root:root 600 @test -r /etc/mactab &amp;&amp; nameif -s
</code></pre>
<p>其中的<code>/etc/mactab</code>文件用于列出接口名与MAC地址的对应规则，<code>nameif</code>命令按此规则给新的接口命名。</p>
<p>当然，不再建议使用<code>ethX</code>这种命名规则，太容易冲突了。所以我选择了“lanX”：</p>
<pre><code class="language-bash">$ cat /etc/mactab
lan0 1C:69:7A:6D:27:2E
</code></pre>
<p>重启系统或者网络后，一切就回归正常了。</p>
<div class="footnotes" role="doc-endnotes">
<hr/>
<ol>
<li id="fn:6f304a38">
<p><a href="/1488/">bletun: SSH 通过蓝牙连接树莓派/任意Linux设备</a> <a href="#fn:b8006b37" class="footnote-backref" role="doc-backlink">^</a></p>
</li>
</ol>
</div>
]]></description>
	</item>
	<item>
		<title>使用字符模式访问UTM创建的虚拟机终端</title>
		<link>https://blog.twofei.com/2284/</link>
		<pubDate>Mon, 06 Apr 2026 09:55:09 HKT</pubDate>
		<description><![CDATA[<p>常规模式使用UTM创建的虚拟机模拟的是标准的显卡（可以简单理解为屏幕），如果运行是服务器版本操作系统，则无法使用鼠标操作：选中文本、复制、粘贴等。这非常不便，特别是还没配置好网络、需要粘贴SSH公钥时……</p>
<p>UTM允许使用串口连接到物理终端，这使得用户界面非常像SSH登录后字符模式界面一样。只需要像下面这样操作。</p>
<div class="image-scroll-outer"><p><img src="Screenshot%202026-04-06%20at%2009.52.08.avif" alt="" width="646" height="478" data-metadata="[&#34;名字&#34;,&#34;Screenshot 2026-04-06 at 09.52.08.avif&#34;,&#34;大小&#34;,&#34;78.3KB&#34;,&#34;尺寸&#34;,&#34;1616x1196&#34;,&#34;类型&#34;,&#34;image/avif&#34;]" data-thumb-hash="CAiGBIIrm3aIh2Col/rLeIZwcpJ3B4h5AQ==" data-contrast-ratio="1" loading="lazy"/></p></div>
<h2>简单方式</h2>
<p>只需要在创建的时候取消勾选“Enable display output”即可。</p>
<div class="image-scroll-outer"><p><img src="disable-display-output.avif" alt="" width="771" height="611" data-metadata="[&#34;名字&#34;,&#34;disable-display-output.avif&#34;,&#34;大小&#34;,&#34;21.6KB&#34;,&#34;尺寸&#34;,&#34;1928x1528&#34;,&#34;类型&#34;,&#34;image/avif&#34;]" data-thumb-hash="NfiFBIAsCKd6hnOLaDbfvB90cqJ4CIeJAQ==" data-contrast-ratio="1" loading="lazy"/></p></div>
<h2>已经创建</h2>
<p>这适合于后期修改。</p>
<ol class="marker-period">
<li>在运行虚拟机前，打开虚拟机设置；</li>
<li>在“设备（Devices）”一栏右键删除原有的“Display”；</li>
<li>然后增加一个“串口（Serial）”设备。</li>
</ol>
<p>把增加的Serial串口设备的“Mode”改为“内置终端（Built-in Terminal）”即可。</p>
<div class="image-scroll-outer"><p><img src="Screenshot%202026-04-06%20at%2009.44.46.avif?og=" alt="" width="771" height="611" data-metadata="[&#34;名字&#34;,&#34;Screenshot 2026-04-06 at 09.44.46.avif&#34;,&#34;大小&#34;,&#34;33.6KB&#34;,&#34;尺寸&#34;,&#34;1928x1528&#34;,&#34;类型&#34;,&#34;image/avif&#34;]" data-thumb-hash="CAiCBIAsaaeYd4CKeGcIIIh7cpJ4CId5AQ==" data-contrast-ratio="1" loading="lazy"/></p></div>
<p>保存配置后启动，就会发现鼠标可以操作了。</p>
]]></description>
	</item>
	<item>
		<title>对exec.Cmd.CombinedOutput的一点研究</title>
		<link>https://blog.twofei.com/2280/</link>
		<pubDate>Thu, 12 Mar 2026 22:14:16 HKT</pubDate>
		<description><![CDATA[<p><code>CombinedOutput</code>会自动设置命令的<code>stdout</code>和<code>stderr</code>为内存内的缓冲区，然后等待进程运行结束。其相关代码如下：</p>
<pre><code class="language-go">// CombinedOutput runs the command and returns its combined standard
// output and standard error.
func (c *Cmd) CombinedOutput() ([]byte, error) {
	// ...省略部分不相关代码...
	var b bytes.Buffer
	c.Stdout = &amp;b
	c.Stderr = &amp;b
	err := c.Run()
	return b.Bytes(), err
}
</code></pre>
<p>这段代码虽然看起来十分简单，但是实际上极其容易造成一种误解：<strong>把<code>stdout</code>和<code>stderr</code>设置成“同一个Writer”是安全的</strong>。</p>
<p>所以我也在我自己的代码中写出了类似下面这样看起来很像又不完全像的代码：</p>
<pre><code class="language-go">func main() {
	cmd := exec.Command(`echo`, `123`)
	b := bytes.Buffer{}
	cmd.Stdout = io.MultiWriter(os.Stdout, &amp;b)
	cmd.Stderr = &amp;b
	cmd.Run()
	output := b.String()
	fmt.Println(&#34;output:&#34;, output)
	if output == `` {
		panic(`should not be empty`)
	}
}
</code></pre>
<p>在一台全新的服务器上，给出了如下的运行结果：时而正常、时而崩溃。</p>
<pre><code class="language-bash">root@iv-yeh9qbzz7kh2cbeuhzh4:~# ./go1.26.1/bin/go run a.go
123
output: 123

root@iv-yeh9qbzz7kh2cbeuhzh4:~# ./go1.26.1/bin/go run a.go
123
output:
panic: should not be empty

goroutine 1 [running]:
main.main()
	/root/a.go:20 +0x415
exit status 2
</code></pre>
<p>这让我很费解。按照正常理解，<code>stdout</code>和<code>stderr</code>在进程内是两个不同的描述符，对应到两个文件，也就意味着：它们可以被多线程同时写。所以，上述写法一定是线程不安全的，会造成数据竞态。但是Go语言本身就能把它们设置为同一个Write呢？而我，只是模仿Go的标准库写了差不多的代码。</p>
<p>如果再去看看<code>Cmd.Stdout/Stderr</code>的文档，有下面的说明：</p>
<pre><code class="language-go">type Cmd struct {
	// If Stdout and Stderr are the same writer, and have a type that can
	// be compared with ==, at most one goroutine at a time will call Write.
	Stdout io.Writer
	Stderr io.Writer
}
</code></pre>
<p>这里的文档正好解释了上面的问题：如果是同一个Writer且<code>==</code>，<code>Write</code>同一时刻只会被一个goroutine调用。这就保证了数据的安全。但这是如何保证的呢？</p>
<p>当Stdout和Stderr设置到同一个Writer时，下面的Go语言代码保证了只会创建唯一一个共享的<code>os.File</code>：</p>
<pre><code class="language-go">func (c *Cmd) childStdout() (*os.File, error) {
	return c.writerDescriptor(c.Stdout)
}

func (c *Cmd) childStderr(childStdout *os.File) (*os.File, error) {
	if c.Stderr != nil &amp;&amp; interfaceEqual(c.Stderr, c.Stdout) {
		return childStdout, nil
	}
	return c.writerDescriptor(c.Stderr)
}

// interfaceEqual protects against panics from doing equality tests on
// two interfaces with non-comparable underlying types.
func interfaceEqual(a, b any) bool {
	defer func() {
		recover()
	}()
	return a == b
}
</code></pre>
<p>而<code>os.File</code>的<code>Write</code>方法又调用了<code>poll.FD.Write</code>方法：</p>
<pre><code class="language-go">// write writes len(b) bytes to the File.
// It returns the number of bytes written and an error, if any.
func (f *File) write(b []byte) (n int, err error) {
	n, err = f.pfd.Write(b)
	runtime.KeepAlive(f)
	return n, err
}
</code></pre>
<p><code>poll.FD</code>内部一进来就对写操作进行了加锁：</p>
<pre><code class="language-go">// Write implements io.Writer.
func (fd *FD) Write(p []byte) (int, error) {
	if err := fd.writeLock(); err != nil {
		return 0, err
	}
	defer fd.writeUnlock()
	// ...
</code></pre>
<p>所以综合各种以上措施后，把stdout和stderr设置成<strong>完全相同的同一个Writer</strong>是安全的。否则，应该自行加锁。</p>
]]></description>
	</item>
	<item>
		<title>一个观察：实锤MacOS真的会偷跑流量</title>
		<link>https://blog.twofei.com/2279/</link>
		<pubDate>Tue, 10 Mar 2026 15:38:14 HKT</pubDate>
		<description><![CDATA[<p>到手不到一天的流量卡，总共220G，就已经只剩下100G，感觉哪里不对。于是上路由器看了一下实时网速，其中一台Mac电脑持续以5-10MB/s的速度跑了很久很久。心痛❤️‍🩹。</p>
<p>活动监视器的网络一栏非常离谱：右下角的数据接收速度明明显示10MB/s，但是上面列表里面的“接收字节数”一直不变（已排序）。第一反应就是有些进程被系统隐藏了。</p>
<div class="image-scroll-outer"><figure><img src="Screenshot%202026-03-10%20at%2015.14.00.avif" alt="" width="1724" height="1106" data-metadata="[&#34;名字&#34;,&#34;Screenshot 2026-03-10 at 15.14.00.avif&#34;,&#34;大小&#34;,&#34;41.4KB&#34;,&#34;尺寸&#34;,&#34;1724x1106&#34;,&#34;类型&#34;,&#34;image/avif&#34;]" data-thumb-hash="OfiFA4ArZ2d1iICIj3oJSDc3eXdwmCc=" loading="lazy"/><figcaption><p>非真实统计图，现场已经被破坏了。</p></figcaption></figure></div>
<p>然后我就尝试安装各种工具看看是哪个进程在跑流量（因为是一台非开发机，什么开发工具都没有装），然后才发现，
MacOS上的各种工具对网络的调试也太难受了：netstat、ss、lsof 居然要么不能查看端口对应的进程，要么直接查无此端口……而且和linux/ai给出的用法有非常大的冲突。</p>
<p>然后终于找到一个名叫“nettop”的自带工具（我错了，不该安装其它的），才终于找到罪魁祸首：</p>
<pre><code class="language-bash">$ sudo nettop -m tcp -n

                                                                                    interface         state        bytes_in       bytes_out   rx_dupe    rx_ooo     re-tx   rtt_avg   rcvsize    tx_win  tc_class    tc_mgt   cc_algo P C R W
launchd.1                                                                                                             0 B             0 B       0 B       0 B       0 B
   tcp4 127.0.0.1:8021&lt;-&gt;*:*                                                              lo0        Listen                                                                                                               -     cubic - - - -
   tcp6 ::1.8021&lt;-&gt;*.*                                                                    lo0        Listen                                                                                                               -     cubic - - - -
   tcp4 *:22&lt;-&gt;*:*                                                                                   Listen                                                                                                               -     cubic - - - -
   tcp6 *.22&lt;-&gt;*.*                                                                                   Listen                                                                                                               -     cubic - - - -
apsd.603                                                                                                           1772 KiB        1128 KiB    10 KiB   150 KiB    20 KiB
   tcp4 192.168.10.230:59045&lt;-&gt;17.57.145.152:443                                          en0   Established        1772 KiB        1128 KiB    10 KiB   150 KiB    20 KiB   3.50 ms   128 KiB    44 KiB        RD         -    prague - - - -
symptomsd.669                                                                                                         0 B             0 B       0 B       0 B       0 B
   tcp6 *.64301&lt;-&gt;*.*                                                                                Listen                                                                                                              BG    prague - - - -
searchpartyd.773                                                                                                   9774 B          8585 B       0 B       0 B       0 B
rapportd.1092                                                                                                       174 KiB         150 KiB  2856 B       0 B    4845 B
   tcp6 *.64349&lt;-&gt;*.*                                                                                Listen                                                                                                               -    prague - - - -
   tcp6 *.64348&lt;-&gt;*.*                                                                                Listen                                                                                                               -    prague - - - -
   tcp6 fe80::1cde:a36f:4902:1e95%en0.57836&lt;-&gt;fe80::84a:b198:e9cd:91d7%en0.50635          en0   Established          85 KiB         104 KiB     0 B       0 B    1702 B   239.00 ms   128 KiB   128 KiB        BE         -    prague - - - -
   tcp6 fe80::1cde:a36f:4902:1e95%en0.57836&lt;-&gt;fe80::1897:c369:d7bb:aa07%en0.63315         en0   Established          88 KiB          46 KiB  2856 B       0 B    3143 B    60.56 ms   128 KiB   128 KiB        BE         -    prague - - - -
   tcp6 *.57836&lt;-&gt;*.*                                                                                Listen                                                                                                               -    prague - - - -
   tcp4 *:57836&lt;-&gt;*:*                                                                                Listen                                                                                                               -    prague - - - -
identityservice.1114                                                                                                 10 KiB        3946 B    1569 B       0 B     651 B
   tcp6 fe80::3f65:cae2:46a8:ff50%utun4.1025&lt;-&gt;fe80::ec40:c110:5d9:6c75%utun4.1026      utun4   Established        9003 B          2697 B    1308 B       0 B     415 B    45.88 ms   128 KiB   127 KiB        RV         -    prague - - - -
   tcp6 fe80::3f65:cae2:46a8:ff50%utun4.1024&lt;-&gt;fe80::ec40:c110:5d9:6c75%utun4.1024      utun4   Established        1282 B          1249 B     261 B       0 B     236 B     0.00 ms   128 KiB   128 KiB       CTL         -    prague - - - -
ControlCenter.1210                                                                                                    0 B             0 B       0 B       0 B       0 B
   tcp6 *.5000&lt;-&gt;*.*                                                                                 Listen                                                                                                               -    prague - - - -
   tcp4 *:5000&lt;-&gt;*:*                                                                                 Listen                                                                                                               -    prague - - - -
   tcp6 *.7000&lt;-&gt;*.*                                                                                 Listen                                                                                                               -    prague - - - -
   tcp4 *:7000&lt;-&gt;*:*                                                                                 Listen                                                                                                               -    prague - - - -
firefox.1451                                                                                                        238 KiB         582 KiB  3494 B    5032 B    1448 B
   tcp4 192.168.10.230:58250&lt;-&gt;34.120.208.123:443                                         en0   Established        2404 B          9443 B       0 B       0 B       0 B     2.91 ms   128 KiB    72 KiB        BE         -    prague - - - -
   tcp4 192.168.10.230:58248&lt;-&gt;151.101.129.91:443                                         en0   Established          30 KiB          24 KiB   815 B       0 B       0 B     3.59 ms   128 KiB    68 KiB        BE         -    prague - - - -
   tcp4 192.168.10.230:58247&lt;-&gt;151.101.65.91:443                                          en0   Established        2203 B          3596 B      93 B       0 B       0 B     2.69 ms   128 KiB    67 KiB        BE         -    prague - - - -
   tcp4 192.168.10.230:58245&lt;-&gt;23.217.118.24:443                                          en0   Established        1009 B          3454 B       0 B       0 B       0 B     4.81 ms   128 KiB    71 KiB        BE         -    prague - - - -
   tcp4 192.168.10.230:58231&lt;-&gt;45.12.90.213:443                                           en0   Established         176 KiB         531 KiB  2493 B    5032 B    1448 B     6.91 ms   128 KiB   372 KiB        BE         -    prague - - - -
   tcp4 192.168.10.230:58225&lt;-&gt;34.107.243.93:443                                          en0   Established        1290 B          3475 B       0 B       0 B       0 B     3.00 ms   128 KiB    72 KiB        BE         -    prague - - - -
   tcp4 192.168.10.230:58252&lt;-&gt;151.101.129.91:443                                         en0   Established        5084 B          3030 B      93 B       0 B       0 B     3.12 ms   128 KiB    67 KiB        BE         -    prague - - - -
Code Helper.1609                                                                                                     14 KiB        7824 B     854 B       0 B       0 B
   tcp4 192.168.10.230:58243&lt;-&gt;13.66.138.105:443                                          en0   Established        6949 B          3494 B     279 B       0 B       0 B     3.12 ms   128 KiB    60 KiB        BE         -    prague - - - -
ChatGPTHelper.1620                                                                                                 5961 B          4682 B     793 B       0 B       0 B
   tcp4 192.168.10.230:60744&lt;-&gt;104.18.32.47:443                                           en0   Established        5961 B          4682 B     793 B       0 B       0 B     3.69 ms   128 KiB    60 KiB        BE         -    prague - - - -
Raycast.1635                                                                                                          0 B             0 B       0 B       0 B       0 B
   tcp6 *.7265&lt;-&gt;*.*                                                                                 Listen                                                                                                               -    prague - - - -
maha-darwin-arm.1725                                                                                                  0 B             0 B       0 B       0 B       0 B
   tcp6 *.6242&lt;-&gt;*.*                                                                                 Listen                                                                                                               -    prague - - - -
nginx.1765                                                                                                            0 B             0 B       0 B       0 B       0 B
   tcp4 *:443&lt;-&gt;*:*                                                                                  Listen                                                                                                               -    prague - - - -
ChatGPT.3532                                                                                                       4919 B          5906 B       0 B       0 B       0 B
WeChat.55887                                                                                                        262 KiB          89 KiB   177 B      12 KiB  8489 B
   tcp4 127.0.0.1:14023&lt;-&gt;*:*                                                             lo0        Listen                                                                                                               -    prague - - - -
   tcp4 127.0.0.1:14022&lt;-&gt;*:*                                                             lo0        Listen                                                                                                               -    prague - - - -
   tcp4 127.0.0.1:14019&lt;-&gt;*:*                                                             lo0        Listen                                                                                                               -    prague - - - -
   tcp4 127.0.0.1:14016&lt;-&gt;*:*                                                             lo0        Listen                                                                                                               -    prague - - - -
   tcp4 127.0.0.1:14013&lt;-&gt;*:*                                                             lo0        Listen                                                                                                               -    prague - - - -
   tcp4 192.168.10.230:57856&lt;-&gt;157.255.191.88:80                                          en0   Established         262 KiB          89 KiB   177 B      12 KiB  8489 B    91.25 ms   256 KiB   324 KiB        BE         -    prague - - - -
</code></pre>
<p>这玩意儿的输出倒是非常的清晰明了，一眼就看出了哪个进程在跑流量：其中一个名为“idleassetsd”的进程（现场已破坏）。但是……搞笑的事情出现了：此进程在活动监视器里面不存在，是一个被系统隐藏了的进程！（无论是按名字搜、还是PID排序后人肉搜。）</p>
<p>网上说这是一个系统下载高清壁纸和动态壁纸的进程……啊？你是怎么敢偷偷跑我超过80G流量的？OnlyAppleCanDo。</p>
<p>另1：把Wi-Fi设置为低数据模式（low data rate）无效。<br/>
另2：我以为OpenWRT天生就很厉害，结果发现连个按设备查看网速的功能都没有，最终还是祭出了上古时代的iftop工具。</p>
]]></description>
	</item>
	<item>
		<title>关闭OpenSSH的后量子密码学安全警告</title>
		<link>https://blog.twofei.com/2274/</link>
		<pubDate>Wed, 28 Jan 2026 22:23:44 HKT</pubDate>
		<description><![CDATA[<p>继OpenSSH在v9.0中关闭了RSA/SHA1算法<sup id="fn:1f6d23a0"><a href="#fn:cae51869" class="footnote-ref" role="doc-noteref">1</a></sup>后，MacOS Tahoe Beta 26.3中的OpenSSH(v10.0)又多出了下面的警告：</p>
<pre><code class="language-bash">$ ssh cudy
** WARNING: connection is not using a post-quantum key exchange algorithm.
** This session may be vulnerable to &#34;store now, decrypt later&#34; attacks.
** The server may need to be upgraded. See https://openssh.com/pq.html
</code></pre>
<p>大意是：当前服务器使用了非后量子安全的密钥交换算法，虽然现在能破解此算法的量子计算机还没有被发明出来，但是数据可以先被存储起来，等能破解此密钥的量子计算机发明出来以后再被解密。即：先窃取、后解密。</p>
<p>我以为我一直最喜欢的ED25519算法已经不能用了，其实不完全算是，看了上面的链接发现是又新出来了两个算法：</p>
<ol class="marker-period">
<li>
<p>sntrup761x25519-sha512</p>
<pre><code class="language--">sntrup761   x   25519   -   sha512
│                │          │
│                │          └─ 哈希函数
│                └─ 经典 ECDH
└─ NTRU 家族后量子 KEM
</code></pre>
</li>
<li>
<p>mlkem768x25519-sha256</p>
<pre><code class="language--">mlkem768   x   25519   -   sha256
│               │          │
│               │          └─ 哈希函数
│               └─ 经典椭圆曲线 DH
└─ 后量子 KEM（NIST 标准）
</code></pre>
</li>
</ol>
<p>ED25519没有被废除，只是又引入了两个长度超级超级长的组合算法。前者是固定的32字节，而后两者的长度竟然达到了≈1024字节。注意是字节，不是位。逆天。但是最终在交换时的密码是它们结合起来后再经SHA256/SHA512用KDF算法派生出来的新密钥。</p>
<p>太强大了。但是对我目前的内网主机来说用处应该不大，目前也没有必要升级使用，所以我决定关闭警告提示。</p>
<p>做法非常简单，只需要在“<code>~/.ssh/config</code>”的最最最前面加上以下配置就可以了：</p>
<pre><code class="language-text">Host *
	WarnWeakCrypto no
</code></pre>
<p>加在最前面的目的是为了对所有机器生效。</p>
<div class="footnotes" role="doc-endnotes">
<hr/>
<ol>
<li id="fn:cae51869">
<p><a href="/881/">MacOS Ventura 系统 ssh 不再支持 ssh-rsa 的原因及解决办法</a> <a href="#fn:1f6d23a0" class="footnote-backref" role="doc-backlink">^</a></p>
</li>
</ol>
</div>
]]></description>
	</item>
	<item>
		<title>在OpenWRT上使用Samba是真的容易啊</title>
		<link>https://blog.twofei.com/2265/</link>
		<pubDate>Tue, 06 Jan 2026 10:54:28 HKT</pubDate>
		<description><![CDATA[<p>曾经尝试过多次在Linux上用Samba分享文件，均以失败告终，各种权限设置，搞不懂，真麻烦，屡战屡败。
MacOS本身一直没有正经的方式挂载ext4文件系统，我一直很纳闷。有MacFUSE的时候，一直靠它续命，但是自从MacOS强制开启了SIP后，普通用户（甚至是我）也很难说服自己再继续用MacFUSE了。所以后来我自己用Go的WebDAV几行代码写了个服务器，一直用到现在仍然在服役<sup id="fn:9f47476e"><a href="#fn:de21a223" class="footnote-ref" role="doc-noteref">1</a></sup>。坏消息是，只有MacOS支持WebDAV，iOS不支持。</p>
<p>厌倦了Intel NUC的风扇狂转，突发奇想把32TB的硬盘柜插在了路由器屁股后面，一句话安装了USB存储模块<sup id="fn:9e4745db"><a href="#fn:df21a3b6" class="footnote-ref" role="doc-noteref">2</a></sup>后，居然秒识别？？！那我就得想办法榨干它的性能价值了！然后就是如何把它分享给局域网，OpenWRT的包管理这么好用，应该有好用的UI？安装Samba的时候竟然让我选Samba还是Ksmbd<sup id="fn:9d474448"><a href="#fn:e021a549" class="footnote-ref" role="doc-noteref">3</a></sup>。前者让我不适；第一次听说后者，仔细研究了一下：</p>
<ol class="marker-period">
<li>太好了，内核模块，性能提升，没有历史包袱（旧协议支持）；</li>
<li>能用 <code>ksmbd.adduser &lt;username&gt;</code> 一键添加用户；</li>
<li>第一次让我明白Samba的用户系统是和Linux分开的；<sup id="fn:a4474f4d"><a href="#fn:e121a6dc" class="footnote-ref" role="doc-noteref">4</a></sup></li>
</ol>
<p>小而美的东西，依旧让我着迷：</p>
<div class="image-scroll-outer"><p><img src="pkgs.avif" alt="" width="641" height="144" data-metadata="[&#34;名字&#34;,&#34;pkgs.avif&#34;,&#34;大小&#34;,&#34;17.1 KiB&#34;,&#34;尺寸&#34;,&#34;1604x362&#34;,&#34;类型&#34;,&#34;image/avif&#34;]" data-thumb-hash="PAgCAoJZBqhKmDVo4JYJbpk=" data-contrast-ratio="0.45213848" loading="lazy"/></p></div>
<p>然后用<code>luci-app-ksmbd</code>界面上操作分享，简直不要太简单：</p>
<div class="image-scroll-outer"><p><img src="ksmbd.avif?og=" alt="" width="686" height="560" data-metadata="[&#34;名字&#34;,&#34;ksmbd.avif&#34;,&#34;大小&#34;,&#34;41.4 KiB&#34;,&#34;尺寸&#34;,&#34;1716x1402&#34;,&#34;类型&#34;,&#34;image/avif&#34;]" data-thumb-hash="+vcFBoB3h4dxh4eAdwh3F3iCey6Q2AM=" data-contrast-ratio="0.30327344" class="border" loading="lazy"/></p></div>
<p>虽然我很希望SFTP(ssh)能一统天下，但是为了跨平台兼容性考虑，我还是折服了。</p>
<div class="footnotes" role="doc-endnotes">
<hr/>
<ol>
<li id="fn:de21a223">
<p>访达（Finder）在枚举WebDAV的较大目录时，会超级卡。但是神经的是，我在日志里面并没有看到任何奇怪的请求，不知道它在发什么神经。访（不）达真的很垃圾。 <a href="#fn:9f47476e" class="footnote-backref" role="doc-backlink">^</a></p>
</li>
<li id="fn:df21a3b6">
<p><a href="https://openwrt.org/docs/guide-user/storage/usb-drives-quickstart">[OpenWrt Wiki] Quick Start for Adding a USB drive</a> <a href="#fn:9e4745db" class="footnote-backref" role="doc-backlink">^</a></p>
</li>
<li id="fn:e021a549">
<p><a href="https://openwrt.org/docs/guide-user/services/nas/ksmbd">[OpenWrt Wiki] ksmbd</a> <a href="#fn:9d474448" class="footnote-backref" role="doc-backlink">^</a></p>
</li>
<li id="fn:e121a6dc">
<p>但是如果Ksmbd的用户名在Linux上有同名的，则会继承其权限位（即：划等号）。 <a href="#fn:a4474f4d" class="footnote-backref" role="doc-backlink">^</a></p>
</li>
</ol>
</div>
]]></description>
	</item>
	<item>
		<title>Cudy TR3000路由器刷OpenWRT系统笔记</title>
		<link>https://blog.twofei.com/2260/</link>
		<pubDate>Mon, 29 Dec 2025 05:30:11 HKT</pubDate>
		<description><![CDATA[<p>这大概是鸽了好些年的内容了，老早就该实操并记录的，因为没有找到比较好的机子。</p>
<p>起因是：最近些年一直旅居比较多，家里的宽带太贵被我停掉了，换成了随身WiFi➕️旅行/便携/迷你路由器的组合套装。没错，我选择了大家在推特上推荐得很多的一套组合：中兴F50随身WiFi➕️Cudy TR3000路由器，网速快时能跑到 150Mb+，差强人意。</p>
<div class="image-scroll-outer"><figure><img src="IMG_0052.avif?og=" alt="" width="614" height="345" data-metadata="[&#34;名字&#34;,&#34;IMG_0052.avif&#34;,&#34;大小&#34;,&#34;44.7 KiB&#34;,&#34;尺寸&#34;,&#34;2048x1152&#34;,&#34;类型&#34;,&#34;image/avif&#34;]" data-thumb-hash="5PcJFIJweoNHyZmaVqqFUFcJdQ==" loading="lazy"/><figcaption><p>图是盗的推特上别人的。[doge]</p></figcaption></figure></div>
<p>我的套装中，除了这两个外，还有一个树莓派Zero 2W盒子，非常小巧，作用：旁路由网关。长期使用了一年左右，发现其在大流量时不稳定，容易死机。所以闲鱼卖掉了，这才准备折腾OpenWRT的，从此又可以少一个设备了。<sup id="fn:e9e8d375"><a href="#fn:2b0d1772" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<div class="image-scroll-outer"><p><img src="/1483/IMG_7076.avif" alt="" width="453" height="254" data-metadata="[&#34;名字&#34;,&#34;IMG_7076.avif&#34;,&#34;大小&#34;,&#34;84.2 KiB&#34;,&#34;尺寸&#34;,&#34;3024x1696&#34;,&#34;类型&#34;,&#34;image/avif&#34;,&#34;时间&#34;,&#34;2024-11-23T16:30:39+08:00&#34;,&#34;设备&#34;,&#34;Apple / iPhone XS Max&#34;,&#34;参数&#34;,&#34;f/1.8, 4.2 mm, 1/60s, ISO/125&#34;]" data-thumb-hash="VvgNDIDHuaiGeHePd3n6h+p++A==" data-contrast-ratio="1" loading="lazy"/></p></div>
<h2>准备内容</h2>
<h3>Cudy TR3000 一台</h3>
<p>建议上256MB大闪存版，这样的话，安装完标准版本布局后还剩下大约200MB可自由使用。对于128MB的版本，安装完后仅剩下~40MB可使用。两者的差价仅20元左右。本文的内容目前只针对256MB大闪存版。</p>
<p>另外，Cudy从2025年第44周开始生产的设备使用了新的闪存芯片，OpenWRT从<code>24.10.5</code>(版本号虽然看似是去年的，实质是2025年12月发布的)才开始支持，以往的旧版本是刷不进去的，刷写时会被警告，强刷也没有效果。具体的生产批次可以从外壳背后的序列号(SN)看到，形如：<code>SN: TR300025XX...</code>，其中的<code>25XX</code>即是生产年份和周数。</p>
<h3>固件：中间固件、OpenWRT固件</h3>
<p>正常来说，固件更新只需要一次：从设备管理后台选择“固件升级”，选择新的固件，等待升级完成。但是Cudy有点儿不一样，它需要两次。为什么？因为：Cudy官方默认只允许固件升级到带RSA签名的官方固件。显然，社区没有Cudy的签名私钥是不可能给出正确签名的固件的，所以不能一步到位。但是官方比较良心，给出了一个所谓的“中间固件”，它是签过名的。并且，它的后台允许通过它升级到未签名的OpenWRT固件。</p>
<p>中间固件官方下载网盘：<a href="https://drive.google.com/drive/folders/1eQ9v8c0UwivRTR7QlUpD2R36WGYqC8sh">TR3000 256MB Flash V1 (not for TR3000 V1) - Google 云端硬盘</a>。文件名：<code>cudy_tr3000-256mb-v1-sysupgrade.bin</code>。变更过，OpenWRT官方引用的名字已经不对了。</p>
<p>OpenWRT固件官方下载地址：<a href="https://firmware-selector.openwrt.org/?version=24.10.5&amp;target=mediatek%2Ffilogic&amp;id=cudy_tr3000-256mb-v1">OpenWrt Firmware Selector</a>。搜索“Cudy TR3000 256mb v1”即可找到预编译好的固件。点击“SysUpgrade”即可下载到固件，名为：<code>openwrt-24.10.5-mediatek-filogic-cudy_tr3000-256mb-v1-squashfs-sysupgrade.bin</code>。从名字中的“squashfs”可以看到这是一个持久的文件系统，而不是另外一个“kernel”按钮下载到的“initramfs”。后者用于直接在内存中运行整个文件系统，用于系统维护，而不是长久使用。</p>
<p>第一次下载这个OpenWRT固件的时候太震惊我了，因为它仅仅只有9MB大小！其中：Linux Kernel 4.5MB，OpenWRT 根文件系统 4.5MB。要知道，我一个用Go写的几百上千行的小程序，已经11MB，辣鸡啊。</p>
<h3>有线网络环境</h3>
<p>中间固件、最终的OpenWRT默认均不开启WiFi网络，所以如果没有有线网络环境的话，升级过程是无法完成的。</p>
<p>如果是常规的台式机电脑，基本上都配有RJ45规格的网口。找一根网线把它和Cudy背后的LAN口连起来即可在没有WiFi的情况下直接访问管理后台。</p>
<p>我没有台式机，只有一台MacBook，它没有网口。所以我翻出了一个古老的TP-LINK迷你路由器，型号为<code>TL-WR702N</code><sup id="fn:e6e8cebc"><a href="#fn:2a0d15df" class="footnote-ref" role="doc-noteref">2</a></sup>。官方早已停产，但是在闲鱼上二手的价格不足¥10即可拿下，货源非常多买一个备用还是非常不错的。把此路由器的工作模式换成“AP(Access Point)”即可实现：有线网络↔️无线网络。只做数据接入转发，无DHCP、NAT等任何功能。它的出厂默认应该就是AP默认，如果不是的话，连上背后的WiFi后改一下即可；或者按一下重置按钮恢复出厂设置。</p>
<p>因为这台AP一直处于上电模式，所以WiFi一直存在。所以更建议在升级的过程中使用此WiFi访问Cudy路由器的后台，而不是直接连接Cudy的WiFi。</p>
<h3>相关网页</h3>
<ul class="marker-asterisk">
<li>OpenWRT官方的Cudy产品硬件介绍页面：<a href="https://openwrt.org/toh/cudy/tr3000#oem_easy_installation">[OpenWrt Wiki] Cudy TR3000</a></li>
<li>Cudy官方的TR3000固件下载页面：<a href="https://www.cudy.com/en-us/pages/download-center/tr3000-1-0">Downloads for TR3000 1.0 – Cudy</a></li>
</ul>
<h2>固件升级</h2>
<p>前面介绍的准备工作内容准备好后就可以着手固件升级了。</p>
<p>首先是中间固件的刷写：</p>
<ol class="marker-period">
<li>
<p>进入Cudy的路由器后台 <a href="http://192.168.10.1/">http://192.168.10.1/</a>，依次找到“高级设置”➡️“系统”➡️“固件升级”。</p>
</li>
<li>
<p>浏览并找到固件文件：<code>cudy_tr3000-256mb-v1-sysupgrade.bin</code>。</p>
<div class="image-scroll-outer"><p><img src="select-intermediate-openwrt.avif" alt="" width="480" height="301" data-metadata="[&#34;名字&#34;,&#34;select-intermediate-openwrt.avif&#34;,&#34;大小&#34;,&#34;19.5 KiB&#34;,&#34;尺寸&#34;,&#34;1200x754&#34;,&#34;类型&#34;,&#34;image/avif&#34;]" data-thumb-hash="vOcFDIL3Zohog3mIaI/4bIOvRg==" data-contrast-ratio="1" loading="lazy"/></p></div>
<p>然后点“继续”就可以开始刷写中间固件了。</p>
</li>
<li>
<p>等待几分钟就可以刷写成功了。（建议尽量用有线网络环境（含AP）连接刷新，否则可能看不到图二刷写结束的画面时WiFi就被断掉了。但是没关系，盲等待几分钟也可以完成刷写过程。）</p>
<div class="image-scroll-outer"><p><img src="flashing1.avif" alt="" width="300" height="206" data-metadata="[&#34;名字&#34;,&#34;flashing1.avif&#34;,&#34;大小&#34;,&#34;9.8 KiB&#34;,&#34;尺寸&#34;,&#34;500x344&#34;,&#34;类型&#34;,&#34;image/avif&#34;]" data-thumb-hash="/QcGDYLHWbZ0h3iKdtiIj/ePivuI" data-contrast-ratio="0.001" class="border" loading="lazy"/></p></div>
<div class="image-scroll-outer"><p><img src="completed1.avif" alt="" width="300" height="230" data-metadata="[&#34;名字&#34;,&#34;completed1.avif&#34;,&#34;大小&#34;,&#34;8.6 KiB&#34;,&#34;尺寸&#34;,&#34;448x344&#34;,&#34;类型&#34;,&#34;image/avif&#34;]" data-thumb-hash="/fcFDYL2aJl1eJmLddhoj9ePan+3" data-contrast-ratio="0.001" class="border" loading="lazy"/></p></div>
</li>
<li>
<p>中间固件是没有启动WiFi的，只能通过有线网络访问。新的管理后台地址：<a href="http://192.168.1.1/">http://192.168.1.1/</a>。用户名是<code>root</code>，没有密码。</p>
<div class="image-scroll-outer"><p><img src="login1.avif" alt="" width="480" height="164" data-metadata="[&#34;名字&#34;,&#34;login1.avif&#34;,&#34;大小&#34;,&#34;6.2 KiB&#34;,&#34;尺寸&#34;,&#34;1200x412&#34;,&#34;类型&#34;,&#34;image/avif&#34;]" data-thumb-hash="N9gNAoqHh4d/dzd3d4B9+Dg=" data-contrast-ratio="1" loading="lazy"/></p></div>
<p>中间固件其实也是个OpenWRT变种，体验上和完整的标准版没有太大差异，可以把玩一下。但是它的文件体积比标准版本大了一半，我不太理解，明明它的唯一用途就是拿来升级到正式的OpenWRT系统？（有点想阴阳IE浏览器只是拿来下载Chrome的一样）</p>
<div class="image-scroll-outer"><p><img src="op1.avif" alt="" width="782" height="341" data-metadata="[&#34;名字&#34;,&#34;op1.avif&#34;,&#34;大小&#34;,&#34;26.3 KiB&#34;,&#34;尺寸&#34;,&#34;1956x854&#34;,&#34;类型&#34;,&#34;image/avif&#34;]" data-thumb-hash="9ggSK4KHd4dweCiIaI+J9pg=" data-contrast-ratio="0.38212252" class="border" loading="lazy"/></p></div>
<p>注意其中的版本号，是<code>23.05-SNAPSHOT</code>。因为它和最终的版本长相几乎完全一样，所以好像以为没有升级成功。</p>
</li>
</ol>
<p>然后就是通过中间固件升级到最终正式版本OpenWRT系统了：</p>
<ol class="marker-period">
<li>
<p>在新系统后台内依次找到“系统”➡️“备份与更新”➡️“更新固件”，并选择固件：<code>openwrt-24.10.5-mediatek-filogic-cudy_tr3000-256mb-v1-squashfs-sysupgrade.bin</code>。注意核对名字，然后确认上传。</p>
<div class="image-scroll-outer"><p><img src="select2.avif" alt="" width="720" height="169" data-metadata="[&#34;名字&#34;,&#34;select2.avif&#34;,&#34;大小&#34;,&#34;10.8 KiB&#34;,&#34;尺寸&#34;,&#34;1200x282&#34;,&#34;类型&#34;,&#34;image/avif&#34;]" data-thumb-hash="/fcFCoAISZkpSolpHy714WI=" data-contrast-ratio="1" loading="lazy"/></p></div>
</li>
<li>
<p>备份选项：无须备份现有配置</p>
<div class="image-scroll-outer"><p><img src="backup.avif" alt="" width="720" height="352" data-metadata="[&#34;名字&#34;,&#34;backup.avif&#34;,&#34;大小&#34;,&#34;27.0 KiB&#34;,&#34;尺寸&#34;,&#34;1200x588&#34;,&#34;类型&#34;,&#34;image/avif&#34;]" data-thumb-hash="/PcBA4Agi6s2OHlfHw/x8RA=" data-contrast-ratio="1" loading="lazy"/></p></div>
</li>
<li>
<p>等待刷写新系统。期间红灯会闪烁。</p>
<div class="image-scroll-outer"><p><img src="flashing2.avif" alt="" width="720" height="136" data-metadata="[&#34;名字&#34;,&#34;flashing2.avif&#34;,&#34;大小&#34;,&#34;13.2 KiB&#34;,&#34;尺寸&#34;,&#34;1200x228&#34;,&#34;类型&#34;,&#34;image/avif&#34;]" data-thumb-hash="/AcGAYCAd7h+ZMaLAAAAAAA=" data-contrast-ratio="1" loading="lazy"/></p></div>
</li>
<li>
<p>等待几分钟后刷新一下浏览器，就可以进入新的后台，还是没有密码。</p>
<div class="image-scroll-outer"><p><img src="login2.avif" alt="" width="720" height="244" data-metadata="[&#34;名字&#34;,&#34;login2.avif&#34;,&#34;大小&#34;,&#34;7.3 KiB&#34;,&#34;尺寸&#34;,&#34;1200x408&#34;,&#34;类型&#34;,&#34;image/avif&#34;]" data-thumb-hash="N9gNAoqHiIh/hyeHd4B9+Dg=" data-contrast-ratio="1" loading="lazy"/></p></div>
<p>看起来是不是长得一模一样？</p>
</li>
<li>
<p>不过，版本号变了，现在是<code>24.10.5</code>。</p>
<div class="image-scroll-outer"><p><img src="final2.avif" alt="" width="780" height="360" data-metadata="[&#34;名字&#34;,&#34;final2.avif&#34;,&#34;大小&#34;,&#34;28.4 KiB&#34;,&#34;尺寸&#34;,&#34;1952x900&#34;,&#34;类型&#34;,&#34;image/avif&#34;]" data-thumb-hash="9ggOK4KHh4dweCiIaI+Z9pg=" data-contrast-ratio="0.3722807" class="border" loading="lazy"/></p></div>
</li>
</ol>
<h2>初印象</h2>
<h3>主题不对？</h3>
<p>默认的主题看起来可能不像OpenWRT？没错。更常见的应该是这个：<a href="https://github.com/jerrykuku/luci-theme-argon" title="Argon is a clean and tidy OpenWrt LuCI theme that allows users to customize their login interface with images or videos. It also supports automatic and manual switching between light and dark modes.">jerrykuku/luci-theme-argon</a>。</p>
<h3>SSH访问</h3>
<p>SSH应该是默认就打开的。如果不是：“系统”➡️“管理”➡️“SSH Access”启用即可。</p>
<pre><code class="language-text">Last login: Mon Dec 29 02:04:08 on ttys011
Downloads → ssh root@192.168.1.1


BusyBox v1.36.1 (2025-10-19 16:37:45 UTC) built-in shell (ash)

  _______                     ________        __
 |       |.-----.-----.-----.|  |  |  |.----.|  |_
 |   -   ||  _  |  -__|     ||  |  |  ||   _||   _|
 |_______||   __|_____|__|__||________||__|  |____|
          |__| W I R E L E S S   F R E E D O M
 -----------------------------------------------------
 OpenWrt 24.10.4, r28959-29397011cc
 -----------------------------------------------------
root@cudy:~#
</code></pre>
<h3>建立WiFi</h3>
<p>在“Network”➡️“Wireless”处可以看到当前板子的硬件支持的无线硬件（长得像个信号塔、天线一样的东西）。在Cudy TR3000上，<code>radio0</code>是2.4GHz设备、<code>radio1</code>是5GHz设备。</p>
<p>点右边的“Add”，可以新建一个热点。可以想建多少个就建多少个！！！太神奇了。</p>
<div class="image-scroll-outer"><p><img src="wireless_overview.avif" alt="" class="padding  border" width="940" height="326" data-metadata="[&#34;名字&#34;,&#34;wireless_overview.avif&#34;,&#34;大小&#34;,&#34;27.0 KiB&#34;,&#34;尺寸&#34;,&#34;1880x652&#34;,&#34;类型&#34;,&#34;image/avif&#34;]" data-thumb-hash="+/cFCoBZj6WJZ6d3D3j4cIk=" data-contrast-ratio="0.017588932" loading="lazy"/></p></div>
<p>另外，我的宽带接入是走的随身WiFi，所以我的radio1下面有一个<code>Mode：Client</code>的WiFi，这是用来做宽带接入的（即：WISP），其它两个才是家庭网络WiFi。</p>
<h3>那……啥？</h3>
<p>不行，我这个文章是用来安装非常标准的OpenWRT的，有需要的话，可以换其它的发行版，比如：ImmortalWRT、KWRT……</p>
<p>我不一样，我还是喜欢干净纯净的系统，我选择自己修改iptables，用自己写的隧道工具🤪。</p>
<p>在OpenWRT内开启之后，所有连接到WiFi的设备就自动翻🧱了。对象再也不会嫌弃每次都要手动改网关和DNS才能做到了。</p>
<h2>恢复原厂固件（网络方式）</h2>
<p>啊……不是刚刚才安装完成吗？这就要恢复了？</p>
<p>开机时，Bootloader会检测机身左侧的Reset键是否被按下，如果有被按下，则会尝试下载新固件并安装。过程如下：</p>
<ol class="marker-period">
<li>它会把路由器自身的IP地址设置成<code>192.168.1.112</code>；</li>
<li>向固定目标为<code>192.168.1.88:96</code>端口处的TFTP服务器下载名为<code>recovery.bin</code>的固件；</li>
<li>下载成功后便开始全新安装新的系统。</li>
</ol>
<p>由于是通过网络下载固件，所以还是需要用网线把Cudy连接到电脑上：</p>
<ol class="marker-period">
<li>如果电脑有LAN口，则直接用网络连接即可；</li>
<li>如果没有LAN口（大多数笔记本），则可以用AP把LAN转成WiFi后连接。</li>
</ol>
<p>搭建一个TFTP服务器，把<code>recovery.bin</code>放在其工作目录内即可。记得把电脑的IP地址手动改成<code>192.168.1.88</code>哦。</p>
<p>由于我是在MacBook上使用的，很多教程推荐使用的Windows版tftp64.exe我无法使用。并且在试用过几个开源版本后我都不满意，所以我自己也写了一个，足够简单、小巧、易用。开源地址：</p>
<p><a href="https://github.com/movsb/tts">movsb/tts: A tiny/trivial² TFTP server that just works.</a></p>
<p>在右边的Release下载页面即可下载到主流各平台的预编译的二进制。随意找个空目录，放置好需要被下载的文件，然后运行即可。</p>
<p>值得注意的是，网络很多教程和视频说需要按下RESET键10秒。在我实践看来，这是完全没必要的，仅仅需要按下RESET并插上电源，直到TFTP服务器接收到下载请求了即可松开，这只需要大概2秒钟的时间。向TFTP请求下载时其使用的块大小选项只有1KB左右，而recovery文件大概有30MB，所以还是需要一定的时间来下载的。不过好在我前面写的TFTP服务器有实时进度显示，不用担心是卡住了。</p>
<p>等待几分钟便会下载完成，并且会继续花几分钟时间来重写系统。等待红灯闪烁完毕白灯(或红灯)常亮即代表系统重装完成。</p>
<h2>没了</h2>
<p>嗯。</p>
<div class="footnotes" role="doc-endnotes">
<hr/>
<ol>
<li id="fn:2b0d1772">
<p><a href="/1483/">电子垃圾➕1（树莓派 Zero 2 W）</a> <a href="#fn:e9e8d375" class="footnote-backref" role="doc-backlink">^</a></p>
</li>
<li id="fn:2a0d15df">
<p><a href="/2093/">我见过的最小巧的路由器：TP-LINK TL-WR702N</a> <a href="#fn:e6e8cebc" class="footnote-backref" role="doc-backlink">^</a></p>
</li>
</ol>
</div>
]]></description>
	</item>
</channel>
</rss>
