sourceIp causes uv_tcp_open EBUSY on ziti-edge-tunnel 1.18.7

I’m trying to use intercept.v1.sourceIp so one OpenZiti service egresses to a local PowerDNS instance from 127.0.0.2 instead of 127.0.0.1.

Environment:

  • Ubuntu 24.04
  • ziti-edge-tunnel v1.18.7
  • Controller v2.0.6
  • Router v2.0.6

The use case is a hidden PowerDNS authoritative server. I want normal DNS/AXFR traffic to hit PowerDNS from 127.0.0.1, while nsupdate traffic comes from 127.0.0.2, so I can apply separate ACLs in PowerDNS.

My nsupdate intercept is:

{
  "portRanges": [
    {
      "high": 53,
      "low": 53
    }
  ],
  "addresses": [
    "10.53.53.10"
  ],
  "protocols": [
    "tcp",
    "udp"
  ],
  "sourceIp": "127.0.0.2"
}

The host config is:

{
  "address": "127.0.0.1",
  "port": 53,
  "forwardProtocol": true,
  "allowedProtocols": [
    "tcp",
    "udp"
  ],
  "allowedSourceAddresses": [
    "127.0.0.1/32",
    "127.0.0.2/32"
  ]
}

The hosting tunneler loads the config correctly, and with verbose logging I can see the source address is being passed through as:

"source_addr":"127.0.0.2"

But every TCP connection fails with:

INFO tunnel-cbs:ziti_hosting.c:751 on_hosted_client_connect()
hosted_service[pdns-auth.nsupdate]
client[stalwart1]
client_src_addr[tcp:100.64.0.1:43299]
dst_addr[tcp:127.0.0.1:53]: incoming connection

ERROR tunnel-cbs:ziti_hosting.c:284 on_hosted_tcp_connector_connect()
uv_tcp_open failed: resource busy or locked (e=-16)

I checked the obvious host-side issues.

127.0.0.2 works fine as a source address:

sudo -u ziti python3 - <<'PY'
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("127.0.0.2", 0))
s.connect(("127.0.0.1", 53))

print(s.getsockname(), "->", s.getpeername())
PY

Output:

('127.0.0.2', 35869) -> ('127.0.0.1', 53)

So the same ziti user running ziti-edge-tunnel can make the exact connection manually.

I also checked systemd/AppArmor:

User=ziti
AmbientCapabilities=cap_net_admin
PrivateNetwork=no
NoNewPrivileges=no
RestrictAddressFamilies=~

There are no AppArmor/audit denials.

Other hosted services on the same machine work fine, including:

127.0.0.1:53
127.0.0.1:22

The problem only shows up when sourceIp is set.

Has anyone seen this with 1.18.7? Is sourceIp currently expected to work with a host.v1 terminator for TCP?

The UV_EBUSY from uv_tcp_open() makes me wonder if the TCP handle already has a socket associated with it by the time the connector tries to open the outbound socket, but I haven’t dug far enough into the code to prove that.

I can provide the full verbose log if useful.

Thanks,
Kris

Hi Kris,

Thanks for letting us know about this! I think I see what happened. We recently started setting up outbound tcp connections with the tlsuv connector. The main reason for doing this was to make it possible for the hosting tunneler to use a proxy when making its connections. In the course of doing this we lost the ability to bind an IP to the outbound connection.

I can't promise you any firm dates for a fix, but I can say that I understand the problem and will add this to my short list of things that need doing.

Thanks!