mirror of
https://github.com/docker/compose.git
synced 2026-09-29 02:56:55 +00:00
fix(provider): relay rewrites host-relative upstreams to host.docker.internal
A provider legitimately expresses its endpoints from the host's perspective — "localhost:5734" is where its resource listens, on the machine compose runs on. But the relay dials from its own network namespace, where loopback names the relay container itself: routes were passed verbatim, so every connection died on the relay's own empty loopback while the host.docker.internal ExtraHosts mapping provisioned for exactly this purpose sat unused. relayRoutesSpec now rewrites host-relative upstreams (localhost, any loopback IP, unspecified or empty host) to host.docker.internal before rendering; LAN IPs and DNS names still pass verbatim. The rewrite happens before the identity hash, so existing relays carrying the old routes are recreated on the next up. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
aab5819147
commit
fc72c165aa
2 changed files with 57 additions and 1 deletions
|
|
@ -21,6 +21,7 @@ import (
|
|||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
|
@ -50,6 +51,8 @@ func relayImage() string {
|
|||
|
||||
// relayRoutesSpec renders endpoints as the relay's RELAY_ROUTES value,
|
||||
// canonically ordered so it doubles as the identity the relay label hashes.
|
||||
// Upstreams are rewritten for the relay's vantage point (relayUpstream)
|
||||
// before rendering, so the identity follows what the relay actually dials.
|
||||
func relayRoutesSpec(endpoints map[int]string) string {
|
||||
ports := make([]int, 0, len(endpoints))
|
||||
for port := range endpoints {
|
||||
|
|
@ -58,11 +61,35 @@ func relayRoutesSpec(endpoints map[int]string) string {
|
|||
sort.Ints(ports)
|
||||
routes := make([]string, 0, len(ports))
|
||||
for _, port := range ports {
|
||||
routes = append(routes, fmt.Sprintf("%d=%s", port, endpoints[port]))
|
||||
routes = append(routes, fmt.Sprintf("%d=%s", port, relayUpstream(endpoints[port])))
|
||||
}
|
||||
return strings.Join(routes, ",")
|
||||
}
|
||||
|
||||
// relayUpstream rewrites a host-relative upstream for the relay's vantage
|
||||
// point. Providers express endpoints from the host's perspective — a loopback
|
||||
// or unspecified address names the machine compose runs on — but the relay
|
||||
// dials from its own network namespace, where those addresses name the relay
|
||||
// container itself. host.docker.internal resolves natively on Docker Desktop
|
||||
// and is provisioned through ExtraHosts (host-gateway) on plain Linux
|
||||
// engines. Anything else (a LAN IP, a DNS name) is reachable as-is from the
|
||||
// relay and passes verbatim.
|
||||
func relayUpstream(endpoint string) string {
|
||||
host, port, err := net.SplitHostPort(endpoint)
|
||||
if err != nil {
|
||||
// validated at parse time (parseEndpointMessage); keep verbatim
|
||||
return endpoint
|
||||
}
|
||||
hostRelative := host == "" || strings.EqualFold(host, "localhost")
|
||||
if ip := net.ParseIP(host); ip != nil && (ip.IsLoopback() || ip.IsUnspecified()) {
|
||||
hostRelative = true
|
||||
}
|
||||
if !hostRelative {
|
||||
return endpoint
|
||||
}
|
||||
return net.JoinHostPort("host.docker.internal", port)
|
||||
}
|
||||
|
||||
func relayIdentity(routes string) string {
|
||||
digest := sha256.Sum256([]byte(relayImage() + "|" + routes))
|
||||
return hex.EncodeToString(digest[:])[:12]
|
||||
|
|
|
|||
|
|
@ -67,6 +67,35 @@ func TestRelayRoutesSpec(t *testing.T) {
|
|||
assert.Assert(t, id1 != relayIdentity("80=host.docker.internal:49153"))
|
||||
}
|
||||
|
||||
// Providers express endpoints from the host's perspective, the relay dials
|
||||
// from a container: host-relative addresses must be rewritten to
|
||||
// host.docker.internal, everything else passes verbatim.
|
||||
func TestRelayUpstream(t *testing.T) {
|
||||
for endpoint, want := range map[string]string{
|
||||
"localhost:5734": "host.docker.internal:5734",
|
||||
"LOCALHOST:5734": "host.docker.internal:5734",
|
||||
"127.0.0.1:5734": "host.docker.internal:5734",
|
||||
"127.1.2.3:5734": "host.docker.internal:5734",
|
||||
"[::1]:5734": "host.docker.internal:5734",
|
||||
"0.0.0.0:5734": "host.docker.internal:5734",
|
||||
"[::]:5734": "host.docker.internal:5734",
|
||||
":5734": "host.docker.internal:5734",
|
||||
"192.168.1.10:5734": "192.168.1.10:5734",
|
||||
"[fdcb::2]:5734": "[fdcb::2]:5734",
|
||||
"some.host.corp:5734": "some.host.corp:5734",
|
||||
"host.docker.internal:5734": "host.docker.internal:5734",
|
||||
} {
|
||||
assert.Equal(t, relayUpstream(endpoint), want, "endpoint %q", endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
// The rewrite happens inside relayRoutesSpec, so the relay identity hashes
|
||||
// what the relay actually dials.
|
||||
func TestRelayRoutesSpecRewritesHostRelativeUpstreams(t *testing.T) {
|
||||
routes := relayRoutesSpec(map[int]string{80: "localhost:5734"})
|
||||
assert.Equal(t, routes, "80=host.docker.internal:5734")
|
||||
}
|
||||
|
||||
// The relay joins the networks of the services depending on the provider —
|
||||
// its consumers — and falls back to the project default network.
|
||||
func TestRelayNetworks(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue