1944 字
10 分钟
OpenClash 在 Dnsmasq 转发模式下基于 MAC 绕过设备

前言#

所以我放弃了旁路由 中我曾提到过,OpenClash 在 Dnsmasq 转发模式下,无法设置不走代理的设备 MAC。我非常需要这个功能,但同时我又无法放弃 Dnsmasq 转发模式。如果无法根据设备 Mac 绕过 Clash 内核,NAS 的 BT/PT 等大量 P2P 连接会进入内核,降低了连接效率,还徒增内核的负担;如果放弃 Dnsmasq 转发模式,绕过中国大陆功能会失效,实测无论国内外域名都会被解析为 Fake IP,意味着大量直连流量会进入内核。

Bypass by MAC
Bypass by MAC

于是我便探索如何在 Dnsmasq 转发模式下根据 Mac 地址绕过设备。思路是参考 OpenClash 的实现方式,设计在 Dnsmasq 转发模式下可用的防火墙规则,利用 OpenClash 的开发者选项自动添加这些规则。

分析 OpenClash 的实现#

在防火墙转发模式下,开启黑名单模式,添加一个 MAC 地址。使用nft list ruleset查看防火墙规则如下(节选):

table inet fw4 {
# === 黑名单 MAC 集合 ===
set lan_ac_black_macs {
type ether_addr
elements = { 0d:00:07:21:ca:fe }
}
set china_ip_route {}
set localnetwork {}
set china_ip6_route {}
set localnetwork6 {}
# === 入站/转发/出站:OpenClash 的 ICMP/QUIC 拦截 ===
chain input {
type filter hook input priority filter; policy drop;
ip protocol icmp icmp type echo-request ip daddr 198.18.0.0/16 reject with icmp port-unreachable comment "OpenClash ICMP INPUT REJECT"
iifname "pppoe-wan" ip6 saddr != @localnetwork6 jump openclash_wan6_input
udp dport 443 ip6 daddr != @china_ip6_route reject with icmpv6 port-unreachable comment "OpenClash QUIC REJECT"
udp dport 443 ip daddr != @china_ip_route reject with icmp port-unreachable comment "OpenClash QUIC REJECT"
iifname "pppoe-wan" ip saddr != @localnetwork jump openclash_wan_input
}
chain forward {
type filter hook forward priority filter; policy drop;
ip protocol icmp icmp type echo-request ip daddr 198.18.0.0/16 reject with icmp port-unreachable comment "OpenClash ICMP FORWARD REJECT"
}
chain output {
type filter hook output priority filter; policy accept;
ip protocol icmp icmp type echo-request ip daddr 198.18.0.0/16 reject with icmp port-unreachable comment "OpenClash ICMP OUTPUT REJECT"
}
# === DNAT 阶段:DNS 劫持 & TCP 代理 ===
chain dstnat {
type nat hook prerouting priority dstnat; policy accept;
ip6 nexthdr { tcp, udp } th dport 53 jump openclash_dns_redirect
meta l4proto { tcp, udp } th dport 53 jump openclash_dns_redirect
ip protocol tcp jump openclash
}
# === Mangle 阶段:透明代理(TPROXY) ===
chain mangle_prerouting {
type filter hook prerouting priority mangle; policy accept;
ip protocol udp jump openclash_mangle
meta nfproto ipv6 jump openclash_mangle_v6
}
# === DNS 劫持(基于 MAC 的绕过,不在集合里才劫持到 7874) ===
chain openclash_dns_redirect {
meta l4proto { tcp, udp } th dport 53 ether saddr != @lan_ac_black_macs redirect to :7874 comment "OpenClash DNS Hijack"
ip6 nexthdr { tcp, udp } th dport 53 ether saddr != @lan_ac_black_macs redirect to :7874 comment "OpenClash DNS Hijack"
}
# === TCP 代理(REDIRECT 到 clash 内核;命中黑名单 MAC 直接 return) ===
chain openclash {
ip daddr @localnetwork return
ct direction reply return
ip protocol tcp ip daddr 198.18.0.0/16 redirect to :7892
ether saddr @lan_ac_black_macs return
ip daddr @china_ip_route return
ip protocol tcp redirect to :7892
}
# === UDP 代理(IPv4 TPROXY 到 clash 内核;命中黑名单 MAC 直接 return) ===
chain openclash_mangle {
meta nfproto ipv4 udp sport 500 return
meta nfproto ipv4 udp sport 68 return
ip daddr @localnetwork return
ct direction reply return
meta l4proto udp ip daddr 198.18.0.0/16 meta mark set 0x00000162 tproxy ip to 127.0.0.1:7895 accept
ether saddr @lan_ac_black_macs return
ip daddr @china_ip_route return
ip protocol udp jump openclash_upnp
meta l4proto udp meta mark set 0x00000162 tproxy ip to 127.0.0.1:7895 accept
}
# === UDP/TCP 代理(IPv6 TPROXY 到 clash 内核;命中黑名单 MAC 直接 return) ===
chain openclash_mangle_v6 {
meta nfproto ipv6 udp sport 500 return
meta nfproto ipv6 udp sport 546 return
ip6 daddr @localnetwork6 return
ct direction reply return
ether saddr @lan_ac_black_macs return
ip6 daddr @china_ip6_route return
ip6 nexthdr tcp meta mark set 0x00000162 tproxy ip6 to :7895 accept comment "OpenClash TCP Tproxy"
ip6 nexthdr udp meta mark set 0x00000162 tproxy ip6 to :7895 accept comment "OpenClash UDP Tproxy"
}
}

分析可知其根据 MAC 绕过设备的核心思路是,把需要绕过的设备 MAC 放进一个集合,随后在 DNS 劫持与 TCP/UDP 透明代理中,凡是命中该 MAC 集合的数据包都 return 早退。

先定义一个需要绕过的 MAC 集合,用于后续匹配。

set lan_ac_black_macs {
type ether_addr
elements = { 0d:00:07:21:ca:fe }
}

dstnat 链中将 DNS 流量劫持到 openclash_dns_redirect 链处理,只有源 MAC 不在黑名单中的流量才会被 DNS 劫持,避免被绕过的设备 DNS 解析为 Fake IP。

chain openclash_dns_redirect {
meta l4proto { tcp, udp } th dport 53 ether saddr != @lan_ac_black_macs redirect to :7874 comment "OpenClash DNS Hijack"
ip6 nexthdr { tcp, udp } th dport 53 ether saddr != @lan_ac_black_macs redirect to :7874 comment "OpenClash DNS Hijack"
}

对于 TCP 流量,在 dstnat 链中被劫持到 openclash 链处理,如果源 MAC 在黑名单集合中,直接 return 早退。

chain openclash {
ip daddr @localnetwork return
ct direction reply return
ip protocol tcp ip daddr 198.18.0.0/16 redirect to :7892
ether saddr @lan_ac_black_macs return
ip daddr @china_ip_route return
ip protocol tcp redirect to :7892
}

对于 UDP 流量,在 mangle_prerouting 链中劫持到 OpenClash 的规则处理,如果源 MAC 在黑名单集合中,直接 return 早退。IPv6 流量同理,不再赘述。

chain openclash_mangle {
meta nfproto ipv4 udp sport 500 return
meta nfproto ipv4 udp sport 68 return
ip daddr @localnetwork return
ct direction reply return
meta l4proto udp ip daddr 198.18.0.0/16 meta mark set 0x00000162 tproxy ip to 127.0.0.1:7895 accept
ether saddr @lan_ac_black_macs return
ip daddr @china_ip_route return
ip protocol udp jump openclash_upnp
meta l4proto udp meta mark set 0x00000162 tproxy ip to 127.0.0.1:7895 accept
}

复刻实现#

可见 OpenClash 的 MAC 黑名单实现还是简单易懂的,那么按照这个思路复刻一个实现,在 Dnsmasq 转发模式下能否达到这个效果呢?实际上是可以的。如下图在被绕过的设备上,DNS 没有被劫持,流量也没有进入 Clash 内核,但仍然可以使用 OpenClash 提供的代理服务。

防火墙复刻结果
防火墙复刻结果

只需模仿官方的实现,先创建一个黑名单 MAC 集合,然后在 DNS 劫持与 TCP/UDP 透明代理中绕过这些源 MAC 地址即可。需要注意的是,在 Dnsmasq 转发模式没有 openclash_dns_redirect 链,DNS 劫持直接写在了 dstnat 链中,感兴趣的话可自行查看防火墙规则。

下面给出一份我的实现,将以下内容放到 OpenClash -> 插件设置 -> 开发者选项 中即可。

#!/bin/sh
. /usr/share/openclash/log.sh
. /lib/functions.sh
# This script is called by /etc/init.d/openclash
# Add your custom firewall rules here, they will be added after the end of the OpenClash iptables rules
LOG_OUT "Tip: Start Add Custom Firewall Rules..."
# >>> 在这里填需要绕过 Clash 的设备 MAC,空格分隔
MACS="0d:00:07:21:ca:fe"
# 1) 集合:lan_bypass_macs(存在则清空,确保可重复执行)
if nft list set inet fw4 lan_bypass_macs >/dev/null 2>&1; then
nft flush set inet fw4 lan_bypass_macs || :
else
nft add set inet fw4 lan_bypass_macs '{ type ether_addr; flags interval; }' || :
fi
# 填充集合
for mac in $MACS; do
# 正规化为小写
m=$(echo "$mac" | tr 'A-F' 'a-f')
nft add element inet fw4 lan_bypass_macs "{ $m }" 2>/dev/null || :
done
# 2) 删除所有旧的“ether saddr @lan_bypass_macs return”,再插到链首
nft -a list chain inet fw4 dstnat 2>/dev/null | awk '
/ether saddr @lan_bypass_macs/ && / return/ {
for (i=1;i<=NF;i++) if ($i=="handle") {print $(i+1)}
}' | while read -r h; do
[ -n "$h" ] && nft delete rule inet fw4 dstnat handle "$h" 2>/dev/null || :
done
nft insert rule inet fw4 dstnat ether saddr @lan_bypass_macs return || :
# 3) 对 UDP/TCP 的 tproxy/redirect 入口做早退
nft insert rule inet fw4 openclash_mangle ether saddr @lan_bypass_macs return || : # IPv4 TPROXY
nft insert rule inet fw4 openclash_mangle_v6 ether saddr @lan_bypass_macs return || : # IPv6 TPROXY
nft insert rule inet fw4 openclash ether saddr @lan_bypass_macs return || : # TCP REDIRECT
LOG_OUT "Done: MAC bypass rules loaded."
exit 0

值得注意的是,OpenClash 在启动时添加的防火墙规则,在关闭时都会被清理掉,包括 openclash*的几个链和dstnat 中插入的规则。而我们手动添加的规则并不会被 OpenClash 清理,所以在第二步先清理掉旧的规则,再重新插入到链首,避免反复重启后 dstnat中有冗余的规则。

除了防火墙规则之外,可能还需要修改 Dnsmasq 的设置,让其为指定设备下发指定的DNS服务器,而不是网关自身。因为有一些系统,即使你手动指定了 DNS 服务器,如果DHCP下发了其他的DNS服务器,系统依旧会优先去请求DHCP下发的DNS服务器,导致域名被解析成 Fake IP。

/etc/dnsmasq.conf
dhcp-host=AA:BB:CC:DD:EE:FF,set:dnsbypass,192.168.0.123
dhcp-option=tag:dnsbypass,option:dns-server,223.5.5.5,119.29.29.29
dhcp-option=tag:dnsbypass,option6:dns-server,[2400:3200::1],[2400:3200:baba::1]

参考资料#

本文没有参考资料,我在网上找了很久,没找到解决方案,于是自己动手丰衣足食。

OpenClash 在 Dnsmasq 转发模式下基于 MAC 绕过设备
https://kasuha.com/posts/openclash-dns-forward-bypass-by-mac/
作者
霞葉
发布于
2025-10-24
许可协议
CC BY-NC-SA 4.0
评论加载中...