zstd icon indicating copy to clipboard operation
zstd copied to clipboard

ZSTD make command failing during installation

Open jlaungos opened this issue 9 months ago • 7 comments

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:

  1. Follow the installation process for zstd in the xSqueezeIt repository

Expected behavior It would proceed without errors.

Screenshots and charts

Image

Image

Image

Image

Image

Image

Desktop (please complete the following information):

  • OS: Windows but installing it into WSL
  • Windows 11
  • Build system: Makefile

jlaungos avatar Apr 05 '25 03:04 jlaungos

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.

Mahdi-zarei avatar Dec 07 '25 01:12 Mahdi-zarei

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.

fyeeme avatar Dec 07 '25 06:12 fyeeme

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:

  • DIRECT to 172.10.8.6 → OS chooses utun6.
  • DIRECT to www.baidu.com → OS chooses en7.

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 tun inbound 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/16utun6.

With auto_detect_interface: true enabled:

  1. sing-box detects the default NIC (the interface used for 0.0.0.0/0) — in my case, en7.

  2. Any outbound without an explicit bind_interface (including <type: "direct">) gets bound to en7.

  3. Now when I visit 172.20.8.6:

    • My routing rules say: ip_is_private: trueoutbound: direct.
    • But direct has been bound to en7 by auto_detect_interface.
    • There is no valid route to 172.20.8.6 via en7, because that subnet is only reachable via utun6.
    • Result: timeouts like i/o timeout in the log.

So in system-proxy mode, the thing that broke Tunnelblick for me wasn’t the routing rules themselves, but:

route.auto_detect_interface: true forcing 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:

  1. For corporate VPN internal subnets (172.10/16, 172.11/16):

    • Match the first rule → use outbound direct-vpn.
    • direct-vpn is explicitly bind_interface: utun6.
    • Once an outbound has its own bind_interface, the global auto_detect_interface doesn’t override it.
    • So these ranges always go through Tunnelblick (utun6).
  2. For other private IPs (192.168.x.x, 10.x.x.x, other 172.* ranges that don’t match the first rule):

    • Match the second rule → ip_is_private: trueoutbound: direct.
    • direct still gets bound by auto_detect_interface to the default NIC (e.g. en7).
    • That’s acceptable for my local/private but non-VPN subnets.
  3. 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:

  • direct no longer has a forced NIC.

  • It simply does connect(dst) and lets the OS routing table decide the interface — just like Clash’s DIRECT in 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.
  • 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:PORT and is set as the system HTTP/HTTPS/SOCKS proxy.

What I will do:

  1. Disable route.auto_detect_interface:

    "route": {
      "rules": [
        {
          "ip_is_private": true,
          "outbound": "direct"
        }
        // ...other rules...
      ],
      "final": "proxy",
      "auto_detect_interface": false
    }
    
  2. Keep direct as a normal outbound without any bind_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 utun6 or 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: true to 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_cidr ranges there with higher-priority rules.

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.
  • TUN mode (if I ever switch to it):

    • Keep auto_detect_interface on.
    • Use bind_interface + ip_cidr on special outbounds for Tunnelblick (utun6).

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: true forced all DIRECT traffic onto the default NIC, effectively ignoring the OS’s more specific routes to utun6.

  • After disabling auto_detect_interface in pure system-proxy mode, sing-box now behaves correctly without any ip_cidr + bind_interface hacks, and Tunnelblick works as expected. @Mahdi-zarei

fyeeme avatar Dec 07 '25 07:12 fyeeme