Skip to content

Support unix:// URLs with Windows absolute paths #8675

@mxschmitt

Description

@mxschmitt

Use case(s) - what problem will this feature solve?

gRPC fails to connect to Unix domain sockets on Windows when using unix:// URLs with
absolute paths. This seems expected as of today, since its only supported on Unix. Windows 10+ natively supports Unix domain sockets (AF_UNIX), but the URL
parser misinterprets the colon in drive letters (e.g., C:) as a port delimiter, causing
connection attempts to fail with "too many colons in address".

  // This fails on Windows
  conn, err := grpc.NewClient(
      "unix://C:\\Users\\user\\socket.sock",
      grpc.WithTransportCredentials(insecure.NewCredentials()),
  )
  // Error: invalid target address unix://C:\Users\...:443: too many colons in address

Proposed Solution

Accept the standard file URL format for Windows paths: unix:///C:/path/to/socket (with
three slashes and forward slashes, similar to file:// URLs).

The unix resolver should strip the leading slash from paths like /C:/... to produce the
correct Windows path C:/....

Alternatives Considered

  1. Custom dialer workaround (current): Use grpc.WithContextDialer() with passthrough:///
    scheme. This works but is verbose and non-obvious.
  2. Opaque URI form: Convert unix://C:\path to unix:C:\path before parsing. Less standard
    than the file URL approach.

Additional Context

  • Windows Unix socket support: Windows 10 build 17063+ (April 2018)
  • Standard Go net.Dial("unix", path) works correctly on Windows
  • Only gRPC's URL parsing is affected
  • Fix implemented in internal/resolver/unix/unix.go

Repro

// Minimal reproduction for unix:// URL parsing issue on Windows
//
// Problem: unix:// URLs with Windows absolute paths fail because the colon
// in drive letters (e.g., "C:") is misinterpreted as a port delimiter.
//
// Run: go run minimal_repro.go

package main

import (
	"context"
	"fmt"
	"log"
	"net"
	"os"
	"runtime"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

type greeterServer struct {
	pb.UnimplementedGreeterServer
}

func (s *greeterServer) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}

func main() {
	if runtime.GOOS != "windows" {
		log.Fatal("This reproduction is specific to Windows")
	}

	// Create Unix socket with Windows absolute path
	socketPath := os.TempDir() + "\\grpc-test.sock"
	defer os.Remove(socketPath)

	// Start server
	lis, err := net.Listen("unix", socketPath)
	if err != nil {
		log.Fatalf("Failed to listen: %v", err)
	}
	defer lis.Close()

	srv := grpc.NewServer()
	pb.RegisterGreeterServer(srv, &greeterServer{})
	go srv.Serve(lis)
	defer srv.Stop()

	time.Sleep(100 * time.Millisecond)

	fmt.Printf("Socket path: %s\n\n", socketPath)

	// Try to connect using unix:// URL with Windows path
	target := "unix://" + socketPath
	fmt.Printf("Connecting to: %s\n", target)

	conn, err := grpc.NewClient(target, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		fmt.Printf("❌ NewClient error: %v\n", err)
		return
	}
	defer conn.Close()

	client := pb.NewGreeterClient(conn)
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()

	_, err = client.SayHello(ctx, &pb.HelloRequest{Name: "Test"})
	if err != nil {
		fmt.Printf("❌ RPC error: %v\n", err)
	} else {
		fmt.Println("✅ Success!")
	}
}

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions