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