From b93ba52b9cd76063ff18fa65ccdf5bfb45526fa9 Mon Sep 17 00:00:00 2001 From: Davanum Srinivas Date: Sun, 22 Mar 2026 09:07:57 +0100 Subject: [PATCH] cri-client: use passthrough resolver for socket endpoints gRPC defaults to the DNS resolver for bare targets passed to NewClient. For CRI socket endpoints, GetAddressAndDialer returns a socket path plus a custom dialer, but handing the bare path to grpc.NewClient still lets gRPC resolve the target first. That breaks unix socket clients with errors like "name resolver error: produced zero addresses" before the custom dialer ever sees the raw path. Use the passthrough resolver for socket-style addresses so the runtime and image clients hand the original endpoint directly to the custom dialer. Add a regression test for unix sockets, Windows named pipes, and TCP addresses. Precedent: https://github.com/etcd-io/etcd/blob/v3.3.27/clientv3/client.go#L266-L270 https://github.com/grpc/grpc-go/blob/v1.72.2/dialoptions.go#L448-L451 Signed-off-by: Davanum Srinivas --- .../cri-client/pkg/connection_target.go | 28 ++++++++++ .../cri-client/pkg/connection_target_test.go | 51 +++++++++++++++++++ .../src/k8s.io/cri-client/pkg/remote_image.go | 2 +- .../k8s.io/cri-client/pkg/remote_runtime.go | 2 +- 4 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 staging/src/k8s.io/cri-client/pkg/connection_target.go create mode 100644 staging/src/k8s.io/cri-client/pkg/connection_target_test.go diff --git a/staging/src/k8s.io/cri-client/pkg/connection_target.go b/staging/src/k8s.io/cri-client/pkg/connection_target.go new file mode 100644 index 00000000000..297d889fb8b --- /dev/null +++ b/staging/src/k8s.io/cri-client/pkg/connection_target.go @@ -0,0 +1,28 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cri + +import "strings" + +func clientTargetForAddress(addr string) string { + // grpc defaults to the DNS resolver for bare targets. Use the passthrough + // resolver for socket-style addresses so the custom dialer gets the raw path. + if strings.HasPrefix(addr, "/") { + return "passthrough:///" + addr + } + return addr +} diff --git a/staging/src/k8s.io/cri-client/pkg/connection_target_test.go b/staging/src/k8s.io/cri-client/pkg/connection_target_test.go new file mode 100644 index 00000000000..219878b2e8e --- /dev/null +++ b/staging/src/k8s.io/cri-client/pkg/connection_target_test.go @@ -0,0 +1,51 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cri + +import "testing" + +func TestClientTargetForAddress(t *testing.T) { + tests := []struct { + name string + addr string + want string + }{ + { + name: "unix socket path", + addr: "/run/containerd/containerd.sock", + want: "passthrough:////run/containerd/containerd.sock", + }, + { + name: "windows pipe path", + addr: "//./pipe/containerd-containerd", + want: "passthrough://///./pipe/containerd-containerd", + }, + { + name: "tcp address", + addr: "127.0.0.1:1234", + want: "127.0.0.1:1234", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if got := clientTargetForAddress(tc.addr); got != tc.want { + t.Fatalf("clientTargetForAddress(%q) = %q, want %q", tc.addr, got, tc.want) + } + }) + } +} diff --git a/staging/src/k8s.io/cri-client/pkg/remote_image.go b/staging/src/k8s.io/cri-client/pkg/remote_image.go index e6f975eece0..fbc76871e85 100644 --- a/staging/src/k8s.io/cri-client/pkg/remote_image.go +++ b/staging/src/k8s.io/cri-client/pkg/remote_image.go @@ -97,7 +97,7 @@ func NewRemoteImageService(ctx context.Context, endpoint string, connectionTimeo grpc.WithConnectParams(connParams), ) - conn, err := grpc.DialContext(ctx, addr, dialOpts...) + conn, err := grpc.NewClient(clientTargetForAddress(addr), dialOpts...) if err != nil { logger.Error(err, "Connect remote image service failed", "address", addr) return nil, err diff --git a/staging/src/k8s.io/cri-client/pkg/remote_runtime.go b/staging/src/k8s.io/cri-client/pkg/remote_runtime.go index ef12c265246..441714f069d 100644 --- a/staging/src/k8s.io/cri-client/pkg/remote_runtime.go +++ b/staging/src/k8s.io/cri-client/pkg/remote_runtime.go @@ -128,7 +128,7 @@ func NewRemoteRuntimeService(ctx context.Context, endpoint string, connectionTim grpc.WithConnectParams(connParams), ) - conn, err := grpc.DialContext(ctx, addr, dialOpts...) + conn, err := grpc.NewClient(clientTargetForAddress(addr), dialOpts...) if err != nil { logger.Error(err, "Connect remote runtime service failed", "address", addr) return nil, err