Bind 0.0.0.0 to IPv4 only #536

Merged
clawbot merged 1 commits from issue-528-ipv4-only-wildcard into next 2026-10-15 15:44:43 +02:00
Collaborator

closes #528

Go listens on any unspecified address with one socket for both IPv4 and IPv6, so BIND_ADDRESS=0.0.0.0 also bound every IPv6 address, where the README says IPv4 only.

  • internal/server/http.go (package server, which runs the HTTP listener): the listener opens on network tcp4 when BIND_ADDRESS is an IPv4 address, plain or IPv4-mapped (::ffff:0.0.0.0), and on tcp otherwise. ListenAndServe always uses tcp, so it is no longer called.
  • internal/server/bind_address_test.go: with 0.0.0.0 or ::ffff:0.0.0.0, 127.0.0.1 answers and ::1 on the same port is refused. With ::, both answer. Both tests skip on a host without IPv6.
  • Dockerfile: the health check requests the container's own address instead of localhost: the IPv4 address hostname -i prints, or on an IPv6-only network the IPv6 address /etc/hosts lists for the container's hostname. An empty or loopback bind still goes unhealthy.
  • README.md: the Bind address list, that an IPv6-only Docker network needs ::, an ss -ltn check, and what address the health check requests.

Disclosures:

  • Judgement call: a container started with --network none has no address of its own, so it goes unhealthy whatever its bind.
  • Judgement call: the :: bullet drops its net.ipv6.bindv6only=0 condition; Go sets that socket option itself.
  • Rule suppressed: contextcheck on the server's start hook, as the other start hooks do. The listener outlives the hook's context.

Model: opus-5-5

closes https://git.eeqj.de/sneak/webhooker/issues/528 Go listens on any unspecified address with one socket for both IPv4 and IPv6, so `BIND_ADDRESS=0.0.0.0` also bound every IPv6 address, where the README says IPv4 only. - `internal/server/http.go` (package `server`, which runs the HTTP listener): the listener opens on network `tcp4` when `BIND_ADDRESS` is an IPv4 address, plain or IPv4-mapped (`::ffff:0.0.0.0`), and on `tcp` otherwise. `ListenAndServe` always uses `tcp`, so it is no longer called. - `internal/server/bind_address_test.go`: with `0.0.0.0` or `::ffff:0.0.0.0`, `127.0.0.1` answers and `::1` on the same port is refused. With `::`, both answer. Both tests skip on a host without IPv6. - `Dockerfile`: the health check requests the container's own address instead of `localhost`: the IPv4 address `hostname -i` prints, or on an IPv6-only network the IPv6 address `/etc/hosts` lists for the container's hostname. An empty or loopback bind still goes `unhealthy`. - `README.md`: the Bind address list, that an IPv6-only Docker network needs `::`, an `ss -ltn` check, and what address the health check requests. Disclosures: - Judgement call: a container started with `--network none` has no address of its own, so it goes `unhealthy` whatever its bind. - Judgement call: the `::` bullet drops its `net.ipv6.bindv6only=0` condition; Go sets that socket option itself. - Rule suppressed: `contextcheck` on the server's start hook, as the other start hooks do. The listener outlives the hook's context. Model: opus-5-5
clawbot added the needs-review label 2026-10-15 11:08:58 +02:00
clawbot self-assigned this 2026-10-15 11:08:58 +02:00
Author
Collaborator
  • Dockerfile, HEALTHCHECK: a probe of 127.0.0.1 passes when the listener answers only on loopback. An empty BIND_ADDRESS in a container, which the README calls the one quiet failure of this setting, now gives a healthy container that nothing outside it can reach. Under upaas, that deploy is now reported as successful. Before, the container went unhealthy and the upaas deploy failed. The issue did not ask for that signal to go. Acceptable: a probe that fails when the listener answers only on loopback and passes for 0.0.0.0 and ::. One way is to request the container's own address from hostname -i. Keep the README's Running with Docker passage saying that any loopback bind goes unhealthy.

Model: opus-5-5

- `Dockerfile`, `HEALTHCHECK`: a probe of `127.0.0.1` passes when the listener answers only on loopback. An empty `BIND_ADDRESS` in a container, which the README calls the one quiet failure of this setting, now gives a `healthy` container that nothing outside it can reach. Under upaas, that deploy is now reported as successful. Before, the container went `unhealthy` and the upaas deploy failed. The issue did not ask for that signal to go. Acceptable: a probe that fails when the listener answers only on loopback and passes for `0.0.0.0` and `::`. One way is to request the container's own address from `hostname -i`. Keep the README's Running with Docker passage saying that any loopback bind goes `unhealthy`. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-15 11:58:31 +02:00
clawbot force-pushed issue-528-ipv4-only-wildcard from 615ba5bf72 to c36b984ee4 2026-10-15 12:29:18 +02:00 Compare
clawbot force-pushed issue-528-ipv4-only-wildcard from c36b984ee4 to dae20faa24 2026-10-15 12:42:22 +02:00 Compare
Author
Collaborator

Rework: the health check now requests the container's own address (the first one hostname -i prints) instead of 127.0.0.1, so an empty or loopback BIND_ADDRESS goes unhealthy again while 0.0.0.0 and :: pass. The README's Running with Docker passage again says a loopback bind goes unhealthy, and the PR body's disclosure about it is gone. Rebased onto current next.

Model: opus-5-5

Rework: the health check now requests the container's own address (the first one `hostname -i` prints) instead of `127.0.0.1`, so an empty or loopback `BIND_ADDRESS` goes `unhealthy` again while `0.0.0.0` and `::` pass. The README's Running with Docker passage again says a loopback bind goes `unhealthy`, and the PR body's disclosure about it is gone. Rebased onto current `next`. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-10-15 13:05:22 +02:00
Author
Collaborator

FAIL, needs-rework.

  1. Dockerfile, HEALTHCHECK and the comment above it: in a container on an IPv6-only Docker network, hostname -i prints no address (it reports IPv4 only) and exits with an error. The probe then requests http://:8080/... and fails whatever the bind. So BIND_ADDRESS=::, the one bind that container can be reached on, stays unhealthy for good, and under upaas its deploy is marked failed. Before this change the probe reached ::1, which a :: bind answers. The comment's "one IPv4 address per network the container is on" is wrong on such a network. Acceptable: a probe that requests the container's own address in either family (an IPv6 one in brackets; /etc/hosts lists it under the container's hostname), so that on an IPv6-only network :: passes and a loopback bind still fails, with the comment saying so.

  2. README.md, Bind address ("In a container the answer is 0.0.0.0") and the 0.0.0.0 bullet: now that 0.0.0.0 binds IPv4 only, the image default leaves a container on an IPv6-only Docker network unreachable from outside, where before it answered. The README still gives 0.0.0.0 as the container answer without exception. Acceptable: one sentence there saying a container on an IPv6-only network needs BIND_ADDRESS=::.

  3. internal/server/http.go, listenNetwork: BIND_ADDRESS=::ffff:0.0.0.0 passes configuration and is the IPv4 wildcard to Go's listener, but it is not Is4, so it is listened on as tcp and binds every IPv6 address: the defect this issue fixes, in another spelling. Acceptable: treat an IPv4-mapped address as IPv4 (for example addr.Unmap().Is4()), with a test case for it.

Model: opus-5-5

FAIL, needs-rework. 1. `Dockerfile`, `HEALTHCHECK` and the comment above it: in a container on an IPv6-only Docker network, `hostname -i` prints no address (it reports IPv4 only) and exits with an error. The probe then requests `http://:8080/...` and fails whatever the bind. So `BIND_ADDRESS=::`, the one bind that container can be reached on, stays `unhealthy` for good, and under upaas its deploy is marked failed. Before this change the probe reached `::1`, which a `::` bind answers. The comment's "one IPv4 address per network the container is on" is wrong on such a network. Acceptable: a probe that requests the container's own address in either family (an IPv6 one in brackets; `/etc/hosts` lists it under the container's hostname), so that on an IPv6-only network `::` passes and a loopback bind still fails, with the comment saying so. 2. `README.md`, Bind address ("In a container the answer is `0.0.0.0`") and the `0.0.0.0` bullet: now that `0.0.0.0` binds IPv4 only, the image default leaves a container on an IPv6-only Docker network unreachable from outside, where before it answered. The README still gives `0.0.0.0` as the container answer without exception. Acceptable: one sentence there saying a container on an IPv6-only network needs `BIND_ADDRESS=::`. 3. `internal/server/http.go`, `listenNetwork`: `BIND_ADDRESS=::ffff:0.0.0.0` passes configuration and is the IPv4 wildcard to Go's listener, but it is not `Is4`, so it is listened on as `tcp` and binds every IPv6 address: the defect this issue fixes, in another spelling. Acceptable: treat an IPv4-mapped address as IPv4 (for example `addr.Unmap().Is4()`), with a test case for it. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-15 13:48:13 +02:00
clawbot added 1 commit 2026-10-15 14:32:12 +02:00
Bind 0.0.0.0 to IPv4 only (closes #528)
check / check (push) Waiting to run
3f8afa2f28
Go listens on any unspecified address with one socket that answers
IPv4 and IPv6, so BIND_ADDRESS=0.0.0.0 also bound every IPv6 address,
which the README says it does not. The listener now uses tcp4 for an
IPv4 address, written plain or IPv4-mapped; :: still binds both. The
server's start hook suppresses contextcheck, as the other start hooks
do.

The image's health check asked for localhost, which wget reached over
::1 only. It now asks for the container's own address: its IPv4
address, or its IPv6 address on an IPv6-only network. A loopback bind
answers neither. The README says such a network needs ::, drops the
bindv6only condition from :: (Go sets that socket option itself) and
names an ss check.

Model: opus-5-5
clawbot force-pushed issue-528-ipv4-only-wildcard from dae20faa24 to 3f8afa2f28 2026-10-15 14:32:12 +02:00 Compare
Author
Collaborator

Rework:

  1. Dockerfile, HEALTHCHECK: the probe requests the IPv4 address hostname -i prints, or, when it prints none (an IPv6-only network), the IPv6 address /etc/hosts lists for the container's hostname, in brackets. The comment above it says 0.0.0.0 and :: pass on a network with IPv4, only :: passes on an IPv6-only one, and a loopback or empty bind fails.
  2. README.md, Bind address: a sentence after the container paragraph says a container on an IPv6-only Docker network needs BIND_ADDRESS=::. Running with Docker now names the address the health check requests in either case.
  3. internal/server/http.go: listenNetwork checks addr.Unmap().Is4(), so ::ffff:0.0.0.0 listens on tcp4. TestBindAddress_WildcardV4IsIPv4Only now runs on it as well as on 0.0.0.0.

Model: opus-5-5

Rework: 1. `Dockerfile`, `HEALTHCHECK`: the probe requests the IPv4 address `hostname -i` prints, or, when it prints none (an IPv6-only network), the IPv6 address `/etc/hosts` lists for the container's hostname, in brackets. The comment above it says `0.0.0.0` and `::` pass on a network with IPv4, only `::` passes on an IPv6-only one, and a loopback or empty bind fails. 2. `README.md`, Bind address: a sentence after the container paragraph says a container on an IPv6-only Docker network needs `BIND_ADDRESS=::`. Running with Docker now names the address the health check requests in either case. 3. `internal/server/http.go`: `listenNetwork` checks `addr.Unmap().Is4()`, so `::ffff:0.0.0.0` listens on `tcp4`. `TestBindAddress_WildcardV4IsIPv4Only` now runs on it as well as on `0.0.0.0`. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-10-15 14:41:46 +02:00
Author
Collaborator

FAIL, needs-rework.

  1. PR body: at about 260 words it is over the limit of about 250 words for a PR body. Acceptable: the same content in at most about 250 words. The Dockerfile bullet's explanation of why the probe does not request the hostname itself is the easiest place to cut.

Model: opus-5-5

FAIL, needs-rework. 1. PR body: at about 260 words it is over the limit of about 250 words for a PR body. Acceptable: the same content in at most about 250 words. The `Dockerfile` bullet's explanation of why the probe does not request the hostname itself is the easiest place to cut. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-15 15:27:46 +02:00
Author
Collaborator

PR body cut to under 250 words by dropping the Dockerfile bullet's explanation of why the probe does not request the hostname; no code change.

Model: opus-5-5

PR body cut to under 250 words by dropping the Dockerfile bullet's explanation of why the probe does not request the hostname; no code change. Model: opus-5-5
clawbot merged commit dfe8f3138f into next 2026-10-15 15:44:43 +02:00
clawbot deleted branch issue-528-ipv4-only-wildcard 2026-10-15 15:44:43 +02:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/webhooker#536