本文的封面图来源于Pixiv,原作者是二狗子茶壶BRH

不知从何时开始,当启用代理软件并使用TUN虚拟网卡时,NetworkManager会错误地提示「网络连接受限」,但事实上网络能够正常使用。这不仅会让人困惑,还会影响到依赖网络状态通知的应用(例如调度网络服务)。

解决方法

connectivity对应服务器的IP地址绕过TUN网卡即可。Arch Linux所使用的默认服务器为ping.archlinux.org,其IP地址为95.216.195.1332a01:4f9:c010:2636::1。其他发行版可能需要根据实际情况调整。对于sing-box配置文件:

在入站的tun网卡配置中加入:

1
"route_exclude_address_set": ["arch-linux-ping"]

同时添加对应的规则集:

1
2
3
4
5
6
7
8
9
10
11
{
"tag": "arch-linux-ping",
"rules": [
{
"ip_cidr": [
"95.216.195.133/32",
"2a01:4f9:c010:2636::1/128"
]
}
]
}

探究过程

Arch Linux上,与网络连通性相关的配置文件位于/usr/lib/NetworkManager/conf.d/20-connectivity.conf,其中指定了urihttp://ping.archlinux.org/nm-check.txt。根据NetworkManager的文档,程序会使用SO_BINDDEVICE检查网络是否通畅[1]。源代码中,其nm-connectivity.c中调用libcurl相关函数,使用curl_easy_setopt(ehandle, CURLOPT_INTERFACE, cb_data->ifspec)绑定网络接口,随后检查是否能收到响应[2]。而libcurl会调用setsockopt,以实现绑定[3]。使用strace跟踪系统调用时,发现其调用了setsockopt,和先前分析一致:

1
2
3
821   setsockopt(30, SOL_SOCKET, SO_BINDTODEVICE, "enp55s0\0", 8) = 0
821 setsockopt(24, SOL_SOCKET, SO_BINDTODEVICE, "wlan0\0", 6) = 0
821 setsockopt(32, SOL_SOCKET, SO_BINDTODEVICE, "sing-box\0", 9) = 0

依此,我编写了一个类似的测试程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <curl/curl.h>
#include <stdio.h>

int main(void)
{
CURL *curl;
CURLcode res;

curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if (curl)
{
curl_easy_setopt(curl, CURLOPT_URL, "https://deepchirp.com/");
curl_easy_setopt(curl, CURLOPT_INTERFACE, "wlan0");
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5L);

res = curl_easy_perform(curl);

if (res != CURLE_OK)
{
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
}
else
{
char *local_ip;
long local_port;
curl_easy_getinfo(curl, CURLINFO_LOCAL_IP, &local_ip);
curl_easy_getinfo(curl, CURLINFO_LOCAL_PORT, &local_port);
printf("Local bind: %s:%ld\n", local_ip, local_port);
}

curl_easy_cleanup(curl);
}
curl_global_cleanup();
return 0;
}

使用tcpdump监听网卡流量时,我发现TUN接口sing-box上存在与deepchirp.com相关的多路流量,但物理网卡wlan0上似乎并无相关流量。而在关闭sing-box后,wlan0上则能捕获到相关流量。由此可见,sing-box会将流量劫持到其TUN网卡上,导致绑定物理网卡的setsockopt调用失效,从而使NetworkManager误判网络状态。

因此,只需将检查连通性的相关流量绕过TUN网卡即可。我一开始考虑使用策略路由,为ping.archlinux.org的流量打上路由标记(使用default_mark即可),并使用nftablesip rule将其路由到物理网卡,但感觉不够优雅:因为当sing-box退出后,相关的路由规则无用但仍存在。后来发现,ping.archlinux.org的IP地址似乎并不常变动——至少2022年8月的IP地址即与今相同[4]——因此选择直接将其IP地址绕过TUN网卡。不过,其他发行版的连通性检查服务器地址可能不同,IP地址若时常变动,可能还是使用策略路由更加方便。


  1. NetworkManager. NetworkManager.conf — NetworkManager configuration file [EB/OL]. (n.d.)[2025-10-20]. ↩︎

  2. NetworkManager. src/core/nm-connectivity.c — apertis/v2026pre, pkg/network-manager (GitLab) [EB/OL]. (n.d.)[2025-10-20]. ↩︎

  3. Android Open Source Project. lib/connect.c — platform/external/curl (main) [EB/OL]. (n.d.)[2025-10-20]. ↩︎

  4. Andreiva. [SOLVED] Arch makes many reverse dns lookups for redirect.archlinux.org [EB/OL]. Arch Linux Forums, (2022-08-22)[2025-10-20]. ↩︎