From d21a1eda54b4a091162b89e7a575c3bd6751f67c Mon Sep 17 00:00:00 2001 From: multivac61 Date: Thu, 29 Oct 2026 09:35:00 +0100 Subject: [PATCH] util: do not bind outbound dials to a loopback -ip.bind (#12198) `weed server -ip.bind=127.0.0.1` keeps every listener local, but the same address became the source of outbound connections, so the filer dialled its remote mount from 127.0.0.1 and failed with "connect: invalid argument". A loopback source cannot reach off the host; leave the source to the OS, as for a wildcard bind. Co-authored-by: Claude Opus 5.5 --- weed/util/outbound_dial.go | 8 +++++--- weed/util/outbound_dial_test.go | 4 ++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/weed/util/outbound_dial.go b/weed/util/outbound_dial.go index e2159edfb8..d8ebc75b57 100644 --- a/weed/util/outbound_dial.go +++ b/weed/util/outbound_dial.go @@ -20,8 +20,10 @@ var ( ) // SetOutboundLocalIP records ip as the source address for outbound TCP -// connections. An empty, wildcard (0.0.0.0 / ::), or unparseable value leaves -// the source address to the OS. +// connections. An empty, wildcard (0.0.0.0 / ::), loopback, or unparseable +// value leaves the source address to the OS: a loopback source cannot reach +// anything off the host, so `-ip.bind=127.0.0.1` (listen locally only) would +// otherwise break every dial to a remote S3 endpoint or peer. // // The first call wins. `weed server` applies the top-level -ip.bind before its // embedded master/volume/filer/s3/... start, so a component's own (possibly @@ -32,7 +34,7 @@ func SetOutboundLocalIP(ip string) { return } parsed := net.ParseIP(ip) - if parsed == nil || parsed.IsUnspecified() { + if parsed == nil || parsed.IsUnspecified() || parsed.IsLoopback() { return } outboundLocalAddr.Store(&net.TCPAddr{IP: parsed}) diff --git a/weed/util/outbound_dial_test.go b/weed/util/outbound_dial_test.go index 98d7d85905..2fb2202e8c 100644 --- a/weed/util/outbound_dial_test.go +++ b/weed/util/outbound_dial_test.go @@ -24,6 +24,10 @@ func TestSetOutboundLocalIP(t *testing.T) { {"empty", "", ""}, {"wildcard v4", "0.0.0.0", ""}, {"wildcard v6", "::", ""}, + // A loopback source cannot reach anything off the host, so binding to + // one would break every dial to a remote endpoint (S3, peers). + {"loopback v4", "127.0.0.1", ""}, + {"loopback v6", "::1", ""}, {"garbage", "not-an-ip", ""}, } for _, tc := range cases {