From a5d54e4b703ebc9a9048d59166c156c123322201 Mon Sep 17 00:00:00 2001 From: claude-hdb Date: Tue, 14 Jul 2026 20:45:09 +0000 Subject: [PATCH] =?UTF-8?q?fix(expose):=20it=20was=20pointing=20the=20prox?= =?UTF-8?q?y=20at=20docker0=20=E2=80=94=20the=20drill's=20own=20oldest=20t?= =?UTF-8?q?rap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Incus finally said it, once the drill stopped swallowing the error: Connect IP "172.17.0.1" must be one of the instance's static IPv4 addresses 172.17.0.1 is DOCKER0. box_ipv4() returns whatever Incus lists first, and a box running docker lists docker0 first — so expose has been aiming the proxy at the wrong interface all along. This is drill trap 4, verbatim ('docker0 (172.17.x) is the decoy'), which the drill has known since run 4 and the CLI never learned. Now bin/box has its own box_net_ip(): the address ON boxnet, with the prefix derived from the network rather than hardcoded. And the connect address is now the wildcard 0.0.0.0: in NAT mode Incus resolves the instance's own current address off the bridge's neighbour table. Naming an address makes it demand a *static* one — the very demand that produced the error, for an address that was wrong anyway. Ask Incus for less and it finds the box itself. --- bin/box | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/bin/box b/bin/box index 9e82090..29bd23c 100755 --- a/bin/box +++ b/bin/box @@ -762,6 +762,21 @@ box_ipv4() { # first address only; strips Incus's " (iface)" suffix. "-" if no | grep . || echo "-" } +# The box's address ON BOXNET — which is NOT the same as "its first address". +# A box running docker also carries 172.17.0.1 (docker0), and Incus happily +# lists that FIRST. box_ipv4() hands you the decoy, and pointing anything at it +# is pointing at the wrong interface: 'box expose' did exactly that until Incus +# refused with `Connect IP "172.17.0.1" must be one of the instance's static +# IPv4 addresses`. The drill has known this trap since run 4; the CLI had not. +# Derive the prefix from the network rather than hardcoding it. +box_net_ip() { + local pfx + pfx="$(incus network get boxnet ipv4.address 2>/dev/null | cut -d/ -f1 | cut -d. -f1-3)" + [ -n "$pfx" ] || return 1 + incus list "$1" --format csv --columns 4 2>/dev/null \ + | tr -d '"' | tr ' ,' '\n\n' | grep -E "^${pfx//./\\.}\.[0-9]+$" | head -n1 | grep . +} + # VIRTUAL-MACHINE is a mouthful in a table; anything unexpected passes through. short_type() { case "$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" in @@ -981,8 +996,8 @@ cmd_expose() { && echo "box: closed the door on port $port" # Remove the scoped ACL allow, if one was added. Best-effort: its absence # is not an error (the drill may show the allow was never needed). - local ip; ip="$(box_ipv4 "$inst")" - [ "$ip" != "-" ] && incus network acl rule remove box-isolate ingress \ + local ip; ip="$(box_net_ip "$inst" || true)" + [ -n "$ip" ] && incus network acl rule remove box-isolate ingress \ action=allow "destination=$ip/32" "destination_port=$port" protocol=tcp >/dev/null 2>&1 return 0 fi @@ -992,8 +1007,8 @@ cmd_expose() { [ -n "$port" ] || usage_error "usage: $(synopsis_of expose)" case "$port$hport" in *[!0-9]*) usage_error "ports must be numbers — got port='$port' host-port='$hport'" ;; esac - local ip; ip="$(box_ipv4 "$inst")" - [ "$ip" != "-" ] || die "$box has no IP yet — is it running? (box info $box)" + local ip; ip="$(box_net_ip "$inst")" \ + || die "$box has no boxnet address yet — is it running? (box info $box)" local dev; dev="$(exposure_dev "$port")" if incus config device get "$inst" "$dev" listen >/dev/null 2>&1; then @@ -1016,8 +1031,12 @@ cmd_expose() { # unnecessary, and it is what made expose fail before it ever reached the # proxy. Ask for less; the docs said so. local err; err="$(mktemp)" + # connect=0.0.0.0 is deliberate: in NAT mode Incus resolves the instance's + # OWN current address off the bridge's neighbour table. Naming an address + # here instead makes Incus demand it be a *static* one — and the address we + # would have named was docker0's. Let it find the box; it knows where it is. if incus config device add "$inst" "$dev" proxy \ - "listen=tcp:127.0.0.1:$hport" "connect=tcp:$ip:$port" \ + "listen=tcp:127.0.0.1:$hport" "connect=tcp:0.0.0.0:$port" \ bind=host nat=true >/dev/null 2>"$err"; then rm -f "$err" echo "box: 127.0.0.1:$hport → $box:$port"