From b43276c6d3b14e552eb6075b8b75c90eea7305fa Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 19:49:47 +0000 Subject: [PATCH] gRPC client: Strip "grpc-go/version" suffix from User-Agent header (#5689) Fixes https://github.com/XTLS/Xray-core/pull/5658#issuecomment-3894269376 --------- Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- transport/internet/grpc/config_test.go | 12 ++++++++++++ transport/internet/grpc/dial.go | 27 ++++++++++++++++++-------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/transport/internet/grpc/config_test.go b/transport/internet/grpc/config_test.go index b159ffdf..04a995a4 100644 --- a/transport/internet/grpc/config_test.go +++ b/transport/internet/grpc/config_test.go @@ -1,9 +1,12 @@ package grpc import ( + "reflect" "testing" "github.com/stretchr/testify/assert" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" ) func TestConfig_GetServiceName(t *testing.T) { @@ -115,3 +118,12 @@ func TestConfig_GetTunMultiStreamName(t *testing.T) { }) } } + +func TestSetUserAgent(t *testing.T) { + ua := "Test/1.0" + conn, err := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithUserAgent(ua)) + assert.NoError(t, err) + defer conn.Close() + setUserAgent(conn, ua) + assert.Equal(t, ua, reflect.ValueOf(conn).Elem().FieldByName("dopts").FieldByName("copts").FieldByName("UserAgent").String()) +} diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index e0b0aa03..8b6d6925 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -2,6 +2,7 @@ package grpc import ( "context" + "reflect" "sync" "time" @@ -168,12 +169,6 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in dialOptions = append(dialOptions, grpc.WithInitialWindowSize(grpcSettings.InitialWindowsSize)) } - userAgent := grpcSettings.UserAgent - if userAgent == "" { - userAgent = utils.ChromeUA - } - dialOptions = append(dialOptions, grpc.WithUserAgent(userAgent)) - var grpcDestHost string if dest.Address.Family().IsDomain() { grpcDestHost = dest.Address.Domain() @@ -181,10 +176,26 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in grpcDestHost = dest.Address.IP().String() } - conn, err := grpc.Dial( - net.JoinHostPort(grpcDestHost, dest.Port.String()), + conn, err := grpc.NewClient( + "passthrough:///"+net.JoinHostPort(grpcDestHost, dest.Port.String()), dialOptions..., ) + if err == nil { + userAgent := grpcSettings.UserAgent + if userAgent == "" { + userAgent = utils.ChromeUA + } + setUserAgent(conn, userAgent) + conn.Connect() + } globalDialerMap[dialerConf{dest, streamSettings}] = conn return conn, err } + +// setUserAgent overrides the user-agent on a ClientConn to remove the +// "grpc-go/version" suffix that grpc.WithUserAgent unconditionally appends. +func setUserAgent(conn *grpc.ClientConn, ua string) { + if f := reflect.ValueOf(conn).Elem().FieldByName("dopts").FieldByName("copts").FieldByName("UserAgent"); f.IsValid() { + *(*string)(f.Addr().UnsafePointer()) = ua + } +}