HTTPie 启动耗时太慢

$ Measure-Command { http --version } | % TotalSeconds
0.5114763  
$ Measure-Command { xh --version } | % TotalSeconds
0.0157881
$ Measure-Command { curl --version } | % TotalSeconds
0.0131859

估计是 import 链拖了后退

HTTPie/cli 最近的版本还是去年的 3.2.4 ,并且 13 年前的对 IPv6 only 支持的需求 Add -4 and -6 switches to force IP protocol version · Issue #94 · httpie/cli 到现在都没加上,换成 ducaale/xh: Friendly and fast tool for sending HTTP requests Rust 版了。

然后为了避免肌肉记忆再回到旧版本,添加个软链接

uv tool uninstall httpie
Set-Alias -Name http -Value xh -Description "a Rust version of HTTPie"

让局域网设备的 DNS 解析响应 IPv6 地址

不想一个个配,也不想让设备 DDNS,核心是让路由器的 DNSmasq DHCP 处理 IPv6 的注册。开启 IPv6 Native 模式(原先是 Passthrough)
这样只能让路由器自身解析成功,其他来自 DHCP 的设备还是无效,需要启用 DHCPv6,那就没必要

# tail -F /var/log/dnsmasq.log | grep kokomi  
Aug 10 00:03:14 dnsmasq[6567]: query[A] rt-ax86u-ce58.home.kokomi.site from 2409:8a1e:6e71:5eb1:7152:1656:72b5:5f9a  
Aug 10 00:03:14 dnsmasq[6567]: /etc/hosts rt-ax86u-ce58.home.kokomi.site is 192.168.9.1  
Aug 10 00:03:14 dnsmasq[6568]: query[AAAA] rt-ax86u-ce58.home.kokomi.site from 2409:8a1e:6e71:5eb1:7152:1656:72b5:5f9a  
Aug 10 00:03:14 dnsmasq[6568]: /etc/hosts rt-ax86u-ce58.home.kokomi.site is 2409:8a1e:6e71:5eb1::1  
Aug 10 00:03:21 dnsmasq[5045]: query[A] amd-9700x-wlan.home.kokomi.site from 192.168.9.6  
Aug 10 00:03:21 dnsmasq[5045]: DHCP amd-9700x-wlan.home.kokomi.site is 192.168.9.18  
Aug 10 00:03:21 dnsmasq[5045]: query[AAAA] amd-9700x-wlan.home.kokomi.site from 192.168.9.6  
Aug 10 00:03:21 dnsmasq[5045]: config amd-9700x-wlan.home.kokomi.site is NODATA-IPv6

Dnsmasq local hostnames with IPv6 | SNBForums 设置了 ra-names 没生效

UPDATED:解决了 让 DNSmasq 响应 DHCPv4 主机的 IPv6 地址

Merlin Clash 的 TProxy IPTables 影响局域网内跨子网访问

在开启了 Merlin Clash + Tproxy mode 的路由器上挂了一个与路由器同网段的 192.168.9.17 设备,上面运行了处在 192.168.2.0/24 网段的虚拟机,并且设备当做路由器处理转发。奇怪的是两个网段可以 ping 通但是无法建立 TCP 连接。

+----------------+----------------+----------------+
| Router R2      | Router R1      | Device A       |
| (192.168.9.17) | (192.168.9.1)  | (192.168.9.18) |
+----------------+----------------+----------------+
         |
         |
         V
+----------------+
|  Device B      |
| (192.168.2.82) |
+----------------+

2025-09-26 在 192.168.9.201 上配置一个子网为 192.168.5.0/24 routed 模式的 Docker Bridge,宿主机和容器正常访问,但局域网内其他设备能 PING 通容器,无法建立 TCP 连接。实测是相同问题

iptables -t mangle -L
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
merlinclash_divert  tcp  --  anywhere             anywhere
merlinclash_divert  udp  --  anywhere             anywhere
merlinclash_PREROUTING  tcp  --  anywhere             anywhere
merlinclash_PREROUTING  udp  --  anywhere             anywhere
 
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
 
Chain FORWARD (policy ACCEPT)
target     prot opt source               destination
 
Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
 
Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
 
Chain PControls (0 references)
target     prot opt source               destination
 
Chain merlinclash (6 references)
target     prot opt source               destination
MARK       all  --  anywhere             anywhere             ctstate NEW MARK set 0x2333
CONNMARK   all  --  anywhere             anywhere             CONNMARK save
TPROXY     tcp  --  anywhere             anywhere             mark match 0x2333 TPROXY redirect 127.0.0.1:23458 mark 0x0/0x0
TPROXY     udp  --  anywhere             anywhere             mark match 0x2333 TPROXY redirect 127.0.0.1:23458 mark 0x0/0x0
 
Chain merlinclash_PREROUTING (2 references)
target     prot opt source               destination
RETURN     udp  --  anywhere             anywhere             udp dpt:domain
RETURN     all  --  anywhere             anywhere
RETURN     all  --  anywhere             anywhere
merlinclash  tcp  --  anywhere             anywhere             match-set ipset_proxy dst tcpflags: FIN,SYN,RST,ACK/SYN
merlinclash  udp  --  anywhere             anywhere             match-set ipset_proxy dst ctstate NEW
RETURN     tcp  --  anywhere             anywhere             match-set ipset_proxyarround dst
RETURN     udp  --  anywhere             anywhere             match-set ipset_proxyarround dst
RETURN     tcp  --  anywhere             anywhere             match-set direct_list dst
RETURN     udp  --  anywhere             anywhere             match-set direct_list dst
RETURN     tcp  --  anywhere             anywhere             match-set china_ip_route dst
RETURN     udp  --  anywhere             anywhere             match-set china_ip_route dst
RETURN     tcp  --  anywhere             anywhere             match-set lan_blacklist src
RETURN     udp  --  anywhere             anywhere             match-set lan_blacklist src
merlinclash  tcp  --  anywhere             anywhere             match-set lan_whitelist src
merlinclash  udp  --  anywhere             anywhere             match-set lan_whitelist src
merlinclash  tcp  --  anywhere             anywhere
merlinclash  udp  --  anywhere             anywhere
 
Chain merlinclash_divert (2 references)
target     prot opt source               destination
CONNMARK   all  --  anywhere             anywhere             CONNMARK restore
TPROXY     tcp  --  anywhere             anywhere             mark match 0x2333 ctstate RELATED,ESTABLISHED TPROXY redirect 127.0.0.1:23458 mark 0x0/0x0
TPROXY     udp  --  anywhere             anywhere             mark match 0x2333 ctstate RELATED,ESTABLISHED TPROXY redirect 127.0.0.1:23458 mark 0x0/0x0
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
DROP       all  --  anywhere             anywhere             ctstate INVALID

现象有了,那么为什么来自局域网另一网段的流量会走到 Tproxy 去呢,

这里从设备上发了个内网请求,在路由器上抓包,在路由器上一见到链接就是 ESTABLISHED 状态,而不是 SYN_SENT ,所以进入了 INVALID state;要么就是只发送 SYN 收不到 SYN_ACK

# conntrack -E | grep 192.168.2.82
    [NEW] tcp      6 300 ESTABLISHED src=192.168.9.18 dst=192.168.2.82 sport=8126 dport=39490 [UNREPLIED] src=192.168.2.82 dst=192.168.9.18 sport=39490 dport=8126
 [UPDATE] tcp      6 120 FIN_WAIT    src=192.168.9.18 dst=192.168.2.82 sport=8126 dport=39490 [UNREPLIED] src=192.168.2.82 dst=192.168.9.18 sport=39490 dport=8126
 [UPDATE] tcp      6 60  CLOSE_WAIT  src=192.168.9.18 dst=192.168.2.82 sport=8126 dport=39490 [UNREPLIED] src=192.168.2.82 dst=192.168.9.18 sport=39490 dport=8126
 
# conntrack -E -d 192.168.2.82
    [NEW] tcp      6 120 SYN_SENT src=192.168.9.19 dst=192.168.2.82 sport=5031 dport=80 [UNREPLIED] src=192.168.2.82 dst=192.168.9.19 sport=80 dport=5031
[DESTROY] tcp      6 src=192.168.9.19 dst=192.168.2.82 sport=5031 dport=80 [UNREPLIED] src=192.168.2.82 dst=192.168.9.19 sport=80 dport=5031

路由器能抓到包的也只有单向的

admin@RT-AX86U-CE58:/tmp/home/root# tcpdump -n -i eth3 port 80
15:29:05.342739 IP 192.168.9.19.8921 > 192.168.10.1.80: Flags [S], seq 498293776, win 65535, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
15:29:05.344154 IP 192.168.9.19.8921 > 192.168.10.1.80: Flags [.], ack 1209595379, win 255, length 0
15:29:05.344228 IP 192.168.9.19.8921 > 192.168.10.1.80: Flags [P.], seq 0:76, ack 1, win 255, length 76: HTTP: GET / HTTP/1.1
15:29:05.694978 IP 192.168.9.19.8921 > 192.168.10.1.80: Flags [F.], seq 76, ack 16236, win 255, length 0
15:29:05.698767 IP 192.168.9.19.8921 > 192.168.10.1.80: Flags [.], ack 16237, win 255, length 0
root@OpenWrt:/# tcpdump -n port 80
15:29:05.337479 IP 192.168.9.19.8921 > 192.168.10.1.80: Flags [S], seq 498293776, win 65535, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
15:29:05.337816 IP 192.168.10.1.80 > 192.168.9.19.8921: Flags [S.], seq 1209595378, ack 498293777, win 29200, options [mss 1460,nop,nop,sackOK,nop,wscale 6], length 0
15:29:05.338855 IP 192.168.9.19.8921 > 192.168.10.1.80: Flags [.], ack 1, win 255, length 0
15:29:05.338866 IP 192.168.9.19.8921 > 192.168.10.1.80: Flags [P.], seq 1:77, ack 1, win 255, length 76: HTTP: GET / HTTP/1.1
15:29:05.546610 IP 192.168.9.19.8921 > 192.168.10.1.80: Flags [P.], seq 1:77, ack 1, win 255, length 76: HTTP: GET / HTTP/1.1
15:29:05.546829 IP 192.168.10.1.80 > 192.168.9.19.8921: Flags [.], ack 77, win 457, length 0
15:29:05.686806 IP 192.168.10.1.80 > 192.168.9.19.8921: Flags [P.], seq 1:71, ack 77, win 457, length 70: HTTP: HTTP/1.1 200 OK
15:29:05.687182 IP 192.168.10.1.80 > 192.168.9.19.8921: Flags [.], seq 71:2991, ack 77, win 457, length 2920: HTTP
15:29:05.687586 IP 192.168.10.1.80 > 192.168.9.19.8921: Flags [.], seq 2991:7371, ack 77, win 457, length 4380: HTTP
15:29:05.687853 IP 192.168.10.1.80 > 192.168.9.19.8921: Flags [.], seq 7371:11751, ack 77, win 457, length 4380: HTTP
15:29:05.688353 IP 192.168.9.19.8921 > 192.168.10.1.80: Flags [.], ack 2991, win 255, length 0
15:29:05.688573 IP 192.168.10.1.80 > 192.168.9.19.8921: Flags [P.], seq 11751:16236, ack 77, win 457, length 4485: HTTP
15:29:05.688833 IP 192.168.9.19.8921 > 192.168.10.1.80: Flags [.], ack 7371, win 255, length 0
15:29:05.689002 IP 192.168.9.19.8921 > 192.168.10.1.80: Flags [.], ack 10291, win 255, length 0
15:29:05.689449 IP 192.168.9.19.8921 > 192.168.10.1.80: Flags [.], ack 13211, win 255, length 0
15:29:05.689589 IP 192.168.9.19.8921 > 192.168.10.1.80: Flags [.], ack 16236, win 255, length 0
15:29:05.689720 IP 192.168.9.19.8921 > 192.168.10.1.80: Flags [F.], seq 77, ack 16236, win 255, length 0
15:29:05.690006 IP 192.168.10.1.80 > 192.168.9.19.8921: Flags [F.], seq 16236, ack 78, win 457, length 0
15:29:05.693405 IP 192.168.9.19.8921 > 192.168.10.1.80: Flags [.], ack 16237, win 255, length 0

怀疑是 A(192.168.9.18) to B(192.168.2.82) 跨网段,A 没有相关路由表,交给默认路由器路由;B to A 链路因为 B 有多个 IP(192.168.2.82 和 192.168.9.17),发现和 A 在同网段,用 ARP 取得 MAC 目标 MAC 地址后,响应包未经过路由器,导致的这个现象。
这问题有个名词 非对称路由,互联网有很多讨论,会影响带有状态检测的防火墙工作。

rtaImage (487×581)

难以关闭路由器的硬件加速,手上也没有可用的交换机,难以抓到 LAN 设备间的流量来验证。
不过通过在 192.168.9.18 上添加一条 192.168.2.82/32 的静态路由,也是能通的说明确实出在路由器上。

root@OpenWrt:/# ip r
default via 192.168.9.1 dev br-lan  proto static  src 192.168.9.6
192.168.9.0/24 dev br-lan  proto kernel  scope link  src 192.168.9.6
192.168.9.1 dev br-lan  proto static  scope link  src 192.168.9.6
192.168.10.0/24 dev br-lan  proto kernel  scope link  src 192.168.10.1

另外有可能是 NAT 没配置好?暂时没去验证
Linux 2.4 NAT HOWTO: Destination NAT Onto the Same Network

那么有几个解决思路,建议采用第三、五项,我直接双管齐下。

  1. 让 192.168.9.17 做 SNAT,对外屏蔽子网信息,相当于同一子网间设备互访
    当然能不 NAT 就不 NAT
在 192.168.9.17 上设置防火墙规则
iptables -t nat -A POSTROUTING -s 192.168.2.0/24 -o vmbr0 -j MASQUERADE
  1. 路由器防火墙不要丢弃本地子网之间的流量
    也就是把 DROP 指令删掉。但指令是 Merlin Clash 加的,要改它脚本,每次更新都得做一遍。
删除 DROP 规则
iptables -t mangle -D merlinclash_divert -m conntrack --ctstate INVALID -j DROP

工欲善其事,必先利其器 —— 我的家庭网络组网方案 | Sukka’s Blog 这里也提到了 CONNTRACK DROP 的关键字,难道丢弃 Invalid 是某种最佳实践?

  1. 提前绕过透明代理,本地流量不 mangle
    只需要修改主路由一处,虽然回程流量不会在 conntrack 表上留记录,但绕过 iptables drop 部分不就好啦。
流量绕行
iptables -t mangle -I PREROUTING -d 192.168.2.0/24 -j RETURN
# 或者更大点
iptables -t mangle -I PREROUTING -d 192.168.0.0/16 -j RETURN

问题是怎么把这段添加进防火墙规则,好在手动新增的规则并且不会被 MerlinClash 删除,那就考虑路由器脚本。刚好之前留了个位置 Homelab/Network/router/RT-AX86U/koolshare/init.d/N200enihsyou.sh at main · enihsyou/Homelab,加在最后就行

  1. 让 192.168.2.82 访问 192.168.9.18 也经过路由器转发,在 conntrack 表上留下记录
    多绕个圈子,感觉非常蠢。修改网络拓扑结构避免非对称路由,不知道怎么实现

  2. 让 192.168.2.82 访问 192.168.9.18 不经过路由器转发,添加静态路由
    通过 DHCP 的 121 classless-static-route option,把静态路由表发送到 DHCP 客户端去。这样客户端发送到另一子网的流量不会经过默认路由。

用 DHCP option 121 发送静态路由
dhcp-option=lan,option:classless-static-route,192.168.2.0/24,192.168.9.207