From 755af4adf4e34fdb15c6117efab2c6306dc2b961 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sun, 7 Jun 2026 10:20:58 -0700 Subject: [PATCH] s3: actually bind outbound connections when -ip.bind is set (#9849) * s3: set outbound bind IP before the first filer dial Standalone weed s3 dialed the filer for GetFilerConfiguration before SetOutboundLocalIP ran, so that gRPC conn was created with the stock dialer and no source address. gRPC caches conns by address and reuses the original dialer on reconnect, so the s3->filer connection kept leaving from the OS-chosen source for the life of the process even after the bind IP was set a moment later. * grpc: install the outbound-bind dialer unconditionally The dialer was installed only when OutboundLocalAddr was already set at GrpcDial time, baking the source-address decision into the cached conn, so a conn dialed before the bind IP was configured never bound. Install the context dialer always and decide per dial: bind through OutboundDialContext once a source is set, otherwise fall back to the stock net.Dialer so default deployments keep gRPC's dial timeout and keepalive behavior. The bind now applies on the next reconnect regardless of ordering, matching the HTTP transport's unconditional DialContext. --- weed/command/s3.go | 4 +++- weed/pb/grpc_client_server.go | 11 +++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/weed/command/s3.go b/weed/command/s3.go index 5037da9cb..faedfe520 100644 --- a/weed/command/s3.go +++ b/weed/command/s3.go @@ -261,6 +261,9 @@ func (s3opt *S3Options) resolvePaths() { func (s3opt *S3Options) startS3Server() bool { + // Before the first filer dial below; gRPC caches conns and binds at dial time. + util.SetOutboundLocalIP(*s3opt.bindIp) + filerAddresses := pb.ServerAddresses(*s3opt.filer).ToAddresses() filerBucketsPath := "/buckets" @@ -324,7 +327,6 @@ func (s3opt *S3Options) startS3Server() bool { if *s3opt.bindIp == "" { *s3opt.bindIp = "0.0.0.0" } - util.SetOutboundLocalIP(*s3opt.bindIp) defaultFileMode, fileModeErr := s3opt.parseDefaultFileMode() if fileModeErr != nil { diff --git a/weed/pb/grpc_client_server.go b/weed/pb/grpc_client_server.go index c053e37f5..d7960255a 100644 --- a/weed/pb/grpc_client_server.go +++ b/weed/pb/grpc_client_server.go @@ -212,11 +212,14 @@ func GrpcDial(ctx context.Context, address string, waitForReady bool, opts ...gr var d net.Dialer return d.DialContext(ctx, "unix", socketPath) })) - } else if util.OutboundLocalAddr() != nil { - // Bind outbound gRPC connections to the configured -ip.bind source - // address. Only installed when a source address is set, so default - // deployments keep gRPC's stock dialer behavior. + } else { + // Always install so a conn cached before SetOutboundLocalIP binds once set; + // the stock net.Dialer until then preserves gRPC's default dial behavior. options = append(options, grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) { + if util.OutboundLocalAddr() == nil { + var d net.Dialer + return d.DialContext(ctx, "tcp", addr) + } return util.OutboundDialContext(ctx, "tcp", addr) })) }