ZSTD make command failing during installation
Describe the bug Hello! Has anyone encountered the issue
collect2: error: ld returned 1 exit status
make[2]: *** [Makefile:161: obj/conf_20daa47e8058f816896e54abab38685b/zstd] Error 1
make[1]: *** [Makefile:149: zstd] Error 2
make: *** [Makefile:67: zstd-release] Error 2
when installing ZSTD? I have tried the steps in the xSqueezeIt repository and it worked on 2 different machine with Ubuntu 24 and Ubuntu 20. However, it's always in my device where it encounters this error when running the make command of the zstd repo, not the sudo make install. I am sure that I followed all the steps given in the ReadMe and terminal prompts during installation.
To Reproduce Steps to reproduce the behavior:
- Follow the installation process for zstd in the xSqueezeIt repository
Expected behavior It would proceed without errors.
Screenshots and charts
Desktop (please complete the following information):
- OS: Windows but installing it into WSL
- Windows 11
- Build system: Makefile
There is a simpler solution, if you are using Tun, just add the private ip you want sing-box to let alone to the Tun's route_exclude_address section, that way sing-box will not hijack them, and they will be properly routed by the system's routing table. If you are using system proxy (which seems you are not), simply setting the private ip range to use the direct outbound should work.
You mean configuring ip_is_private, right? That probably won’t solve the problem.
{
"route": {
"rules": [
{
"ip_is_private": true,
"action": "route",
"outbound": "direct"
},
{
"action": "route",
"outbound": "proxy"
}
]
}
}
Maybe I didn’t explain it clearly. Some of the internal network traffic needs to go out via Tunnelblick’s proxy interface (utun6), while most of the other internal traffic should use the default utun7.
So if I simply set "outbound": "direct", Tunnelblick will stop working. This is also why, even though everything is using the "direct" outbound, I still configure bind_interface to make certain internal networks use utun6.
Also, clients like Clash can actually solve this problem out of the box. But with sing-box, I haven’t found a better way so far.
As a result, if I use some sing-box clients that auto-update, they overwrite my rules and break the Tunnelblick configuration. The only way to fix it is to disable updates, but then my “airport” / subscription stops working when the server addresses change.
I’d like to clarify my setup and what I’ve observed, because my earlier comments might have created some confusion around TUN vs system proxy.
In both Clash and sing-box, I am using system proxy mode, not TUN mode. The comparison and all the issues I’m seeing are based on that.
What confused me initially is that with the same basic networking environment (Tunnelblick + macOS), Clash “just works” with my corporate VPN, while sing-box required extra configuration (like bind_interface + ip_cidr). After digging into it, I think I understand why.
1. What actually happens in system-proxy mode on macOS
In system-proxy mode, there are really two layers deciding where traffic goes:
(1) The OS decides whether to send traffic to the proxy
The GUI (Clash or sing-box) calls macOS APIs (like networksetup) to set HTTP/HTTPS/SOCKS proxy to something like:
127.0.0.1:PORT
—that’s usually an inbound like mixed / http / socks in the config.
At the same time, the GUI can also configure a bypass list, e.g.:
127.0.0.1, localhost, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, <local>, ...
Anything matching the bypass list never touches the proxy at all. The app connects directly, and the OS routing table decides whether it goes through utun6 (Tunnelblick), a physical NIC, etc.
(2) Once traffic hits the proxy, the proxy decides: “Proxy or DIRECT?”
If the OS does send a connection to the local proxy, then the app’s connection hits an inbound (e.g. mixed), and now the proxy’s own routing logic (rules, rule-sets, etc.) decides:
- send it through a remote outbound (proxy), or
- treat it as DIRECT (connect directly to the target from the local machine).
In sing-box, that might look like:
{
"route": {
"rules": [
{
"ip_cidr": [
"172.20.0.0/16",
"172.31.0.0/16"
],
"outbound": "direct-vpn"
},
{
"ip_is_private": true,
"outbound": "direct"
}
]
}
}
Both direct and direct-vpn are “local connects”; they call connect() from the local machine, and then the OS routing table + interface binding decide which NIC (e.g. en7, utun6) actually sends the packets.
The problem I hit is specifically inside step (2): how sing-box chooses the NIC for “DIRECT” outbounds.
2. Why Clash “automatically” works with Tunnelblick
From what I can tell, there are two reasons why Clash “just works” in my environment.
2.1 OS-level bypass: Clash often never sees corporate VPN traffic
Many Clash macOS clients (Clash Verge, older ClashX, etc.) automatically add a bunch of private ranges into the system proxy bypass list when system proxy is enabled, for example:
-
10.0.0.0/8 -
172.16.0.0/12 -
192.168.0.0/16 -
<local>
These already cover a large chunk of “internal” ranges.
In my case, when I access something like 172.20.x.x / 172.31.x.x (corporate VPN subnets), the system may simply decide:
Don’t use the HTTP/SOCKS proxy for this. Connect directly.
Then macOS looks at its routing table, sees that those prefixes are routed via utun6 (Tunnelblick), and sends traffic that way. Clash never even sees those connections. From my point of view, it looks like “Clash handled corporate VPN traffic automatically”, but in reality:
The OS + Tunnelblick handled it; Clash was bypassed.
2.2 Even when traffic reaches Clash, DIRECT usually doesn’t bind a NIC
On top of that, Clash’s DIRECT outbound, in common configs, doesn’t usually bind a specific interface. It just does a normal connect(dst) and lets the OS routing table decide per destination.
For example, on my machine:
route get 172.10.8.6 # corporate VPN host
# -> interface: utun6
route get www.baidu.com
# -> interface: en7
If Clash does:
-
DIRECTto172.10.8.6→ OS choosesutun6. -
DIRECTtowww.baidu.com→ OS choosesen7.
So even if some 172.x traffic does go into Clash, as long as Clash doesn’t override the interface, the actual behavior still matches the OS routing table. That again feels “automatic”.
3. Why sing-box breaks Tunnelblick in system-proxy mode
The key difference I ran into is sing-box’s routing feature:
route.auto_detect_interface / route.default_interface.
The official docs explain auto_detect_interface like this (simplified):
Bind outbound connections to the default NIC by default, to prevent routing loops when using TUN.
This makes perfect sense if sing-box is running its own TUN inbound and wants to avoid traffic looping back into itself.
However, in my setup:
- I am not using a sing-box
tuninbound at all. - I’m using Tunnelblick as the VPN (utun6) for some internal ranges.
- I’m using sing-box only as a system proxy (mixed/http inbound +
set_system_proxy).
On macOS in this configuration:
- The default route (0.0.0.0/0) is through my main NIC, e.g.
en7. - Tunnelblick just adds a few more specific routes like
172.20.0.0/16→utun6.
With auto_detect_interface: true enabled:
-
sing-box detects the default NIC (the interface used for 0.0.0.0/0) — in my case,
en7. -
Any outbound without an explicit
bind_interface(including<type: "direct">) gets bound toen7. -
Now when I visit
172.20.8.6:- My routing rules say:
ip_is_private: true→outbound: direct. - But
directhas been bound toen7byauto_detect_interface. - There is no valid route to
172.20.8.6viaen7, because that subnet is only reachable viautun6. - Result: timeouts like
i/o timeoutin the log.
- My routing rules say:
So in system-proxy mode, the thing that broke Tunnelblick for me wasn’t the routing rules themselves, but:
route.auto_detect_interface: trueforcing DIRECT traffic onto the default NIC, ignoring the OS routing table’s more specific routes for the corporate VPN ranges.
That’s a great safety feature for sing-box TUN scenarios, but in my “Tunnelblick + system proxy only” setup, it actually works against the OS routing.
4. Why my manual ip_cidr + bind_interface workaround fixed it (before)
Before I realized the auto_detect_interface issue, I tried this workaround:
"outbounds": [
{
"tag": "direct",
"type": "direct"
},
{
"tag": "direct-vpn",
"type": "direct",
"bind_interface": "utun6"
}
],
"route": {
"rules": [
{
"ip_cidr": ["172.10.0.0/16", "172.11.0.0/16"],
"outbound": "direct-vpn"
},
{
"ip_is_private": true,
"outbound": "direct"
}
]
}
From sing-box’s point of view this means:
-
For corporate VPN internal subnets (
172.10/16,172.11/16):- Match the first rule → use outbound
direct-vpn. -
direct-vpnis explicitlybind_interface: utun6. - Once an outbound has its own
bind_interface, the globalauto_detect_interfacedoesn’t override it. - So these ranges always go through Tunnelblick (
utun6).
- Match the first rule → use outbound
-
For other private IPs (
192.168.x.x,10.x.x.x, other172.*ranges that don’t match the first rule):- Match the second rule →
ip_is_private: true→outbound: direct. -
directstill gets bound byauto_detect_interfaceto the default NIC (e.g.en7). - That’s acceptable for my local/private but non-VPN subnets.
- Match the second rule →
-
Public IPs fall back to whatever outbound I assign in
final(usually some proxy node).
Effectively, I was re-implementing the OS routing logic inside sing-box:
“These specific 172.x ranges belong to Tunnelblick → bind them to utun6. Other internal ranges use the default NIC.”
This worked, but it meant I had to maintain corporate subnets manually in my sing-box config.
5. The key experiment: disabling auto_detect_interface in pure system-proxy mode
Then I tried the obvious experiment:
- Keep using only system-proxy mode in sing-box (no sing-box TUN).
- Disable
route.auto_detect_interface.
In other words:
"route": {
"rules": [
{
"ip_is_private": true,
"outbound": "direct"
}
// plus other usual rules: CN direct, others proxy, etc.
],
"final": "proxy",
"auto_detect_interface": false
}
With this change:
-
directno longer has a forced NIC. -
It simply does
connect(dst)and lets the OS routing table decide the interface — just like Clash’sDIRECTin most cases. -
For corporate VPN ranges like
172.20.x.x:- The OS knows they should go to
utun6(Tunnelblick). - sing-box doesn’t override that anymore.
- The OS knows they should go to
-
For normal public traffic:
- The OS chooses the normal default NIC.
At that point, I no longer needed ip_cidr + bind_interface: utun6 at all in pure system-proxy mode.
The behavior became very similar to what I see with Clash.
6. My current conclusions and suggested configuration
Based on all this, here’s how I understand it and how I plan to configure things.
A. If I’m using sing-box only as system proxy (no sing-box TUN)
In this scenario:
- Tunnelblick owns its own TUN (
utun6) and routes some internal subnets. - sing-box just listens on
127.0.0.1:PORTand is set as the system HTTP/HTTPS/SOCKS proxy.
What I will do:
-
Disable
route.auto_detect_interface:"route": { "rules": [ { "ip_is_private": true, "outbound": "direct" } // ...other rules... ], "final": "proxy", "auto_detect_interface": false } -
Keep
directas a normal outbound without anybind_interface."outbounds": [ { "type": "direct", "tag": "direct" }, { "type": "socks", "tag": "proxy", "server": "your-server", "server_port": 12345 } ]
This way:
- Any DIRECT (including internal VPN ranges) is handed back to the OS routing table.
- The OS + Tunnelblick decide whether the traffic goes out via
utun6or a physical NIC. - The behavior matches my expectations and is very close to Clash in system-proxy mode.
I no longer need bind_interface: utun6 or per-subnet ip_cidr rules inside sing-box for the corporate VPN case.
B. If I ever use sing-box TUN plus Tunnelblick (dual tunneling)
This is a different scenario (I haven’t fully switched to it, but conceptually):
- sing-box runs its own TUN inbound and wants to capture most traffic.
- Tunnelblick is another TUN (
utun6) for a subset of routes.
In that case, I now understand:
-
It does make sense to keep
auto_detect_interface: trueto avoid loops. -
And for the subnets that must go through Tunnelblick, I should:
- create a dedicated outbound with
bind_interface: "utun6", and - route specific
ip_cidrranges there with higher-priority rules.
- create a dedicated outbound with
For example:
"outbounds": [
{
"type": "direct",
"tag": "direct"
},
{
"type": "direct",
"tag": "direct-vpn",
"bind_interface": "utun6"
}
],
"route": {
"rules": [
{
"ip_cidr": [
"172.10.0.0/16",
"172.11.0.0/16"
],
"outbound": "direct-vpn"
},
{
"ip_is_private": true,
"outbound": "direct"
}
],
"final": "proxy",
"auto_detect_interface": true
}
In short:
-
System-proxy only (my current real-world setup):
- Turn off
auto_detect_interface. - Don’t bind interfaces on
direct. - Let macOS + Tunnelblick handle which NIC to use.
- Turn off
-
TUN mode (if I ever switch to it):
- Keep
auto_detect_interfaceon. - Use
bind_interface+ip_cidron special outbounds for Tunnelblick (utun6).
- Keep
TL;DR
-
The reason Clash “just worked” with Tunnelblick while sing-box did not was not that Clash magically understood my corporate VPN, but that:
- Clash either bypassed those subnets at the system-proxy level, or
- didn’t bind a specific NIC for DIRECT and simply respected the OS routing table.
-
In my sing-box setup,
route.auto_detect_interface: trueforced all DIRECT traffic onto the default NIC, effectively ignoring the OS’s more specific routes toutun6. -
After disabling
auto_detect_interfacein pure system-proxy mode, sing-box now behaves correctly without anyip_cidr + bind_interfacehacks, and Tunnelblick works as expected. @Mahdi-zarei