Ok I'm at a loss here for what I need to be doing I have a WPF client that I've written in .net 8 which is supposed to connect to a Go gRPC server, but every time I try and connect I'm getting the following exception
[ALERT] Connection Status: Connection error: Status(StatusCode="Internal", Detail="Error starting gRPC call. HttpRequestException: Requesting HTTP version 2.0 with version policy RequestVersionOrHigher while unable to establish HTTP/2 connection.", DebugException="System.Net.Http.HttpRequestException: Requesting HTTP version 2.0 with version policy RequestVersionOrHigher while unable to establish HTTP/2 connection.")
The issue is that when I wire shark the connection I can clearly see that the TLS 1.2 handshake completes AND it is using HTTP2
I do see that there is later an encrypted alert but that happens several seconds after the exception has been thrown so I'm not sure why.
either way I thought it could be that it's a self signed certificate so I added into the mainwindow()
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
and for good measures just incase
AppContext.SetSwitch("System.Net.Http.UseSocketsHttpHandler", false);
my init channel function looks like
internal async Task InitializeChannel(string ip)
{
Console.WriteLine("Initializing Channel with IP: " + ip);
Logger.Log("Initializing Channel with IP: " + ip, 1);
if (!string.IsNullOrEmpty(ip))
{
var parts = ip.Split(':');
if (parts.Length == 2 && int.TryParse(parts[1], out int port))
{
try
{
var httpClientHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
{
return true;
},
SslProtocols = System.Security.Authentication.SslProtocols.Tls12
};
var loggingHandler = new LoggingHandler(httpClientHandler);
var httpClient = new HttpClient(loggingHandler)
{
DefaultRequestVersion = HttpVersion.Version20,
DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher
};
Logger.Log("Creating GrpcChannel", 0);
// Create the GrpcChannel using the HttpClient
Channel = GrpcChannel.ForAddress($"https://{parts[0]}:{port}", new GrpcChannelOptions
{
HttpClient = httpClient
});
}
catch (Exception ex)
{
Logger.Log($"Exception in InitializeChannel: {ex.Message}", 3);
throw;
}
}
else
{
TeamServerStatus = "Invalid IP format";
Logger.Log("Invalid IP format", 2);
}
}
else
{
Logger.Log("IP is null or empty", 2);
}
}
// Define the custom HttpMessageHandler with logging
public class LoggingHandler : DelegatingHandler
{
public LoggingHandler(HttpMessageHandler innerHandler)
: base(innerHandler)
{
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Log the request details
Logger.Log($"Request: {request.Method} {request.RequestUri}", 0);
Logger.Log($"Request Headers: {request.Headers}", 0);
Logger.Log($"Request Version: {request.Version}", 0);
// Send the request
var response = await base.SendAsync(request, cancellationToken);
// Log the response details
Logger.Log($"Response: {response.StatusCode}", 0);
Logger.Log($"Response Headers: {response.Headers}", 0);
Logger.Log($"Response Version: {response.Version}", 0);
return response;
}
}
and here is the log it produces when I attempt to connect
[06/24 08:54] [Debug] TS Connect initatied
[06/24 08:54] [Debug] Attempted to connect to TS
[06/24 08:54] [Info] Initializing Channel with IP: 127.0.0.1:5001
[06/24 08:54] [Debug] Creating GrpcChannel
[06/24 08:54] [Info] Attempting to connect to Team Server at: 127.0.0.1:5001
[06/24 08:54] [Debug] Request: POST https://127.0.0.1:5001/App.Auth/ConnectToTeamServer
[06/24 08:54] [Debug] Request Headers: User-Agent: grpc-dotnet/2.59.0 (.NET 8.0.0; CLR 8.0.0; net8.0; windows; x64)
TE: trailers
grpc-accept-encoding: identity,gzip,deflate
[06/24 08:54] [Debug] Request Version: 2.0
[06/24 08:54] [Critical] Connection error: Status(StatusCode="Internal", Detail="Error starting gRPC call. HttpRequestException: Requesting HTTP version 2.0 with version policy RequestVersionOrHigher while unable to establish HTTP/2 connection.", DebugException="System.Net.Http.HttpRequestException: Requesting HTTP version 2.0 with version policy RequestVersionOrHigher while unable to establish HTTP/2 connection.")
[06/24 08:54] [Debug] Connection Status recived: Connection error: Status(StatusCode="Internal", Detail="Error starting gRPC call. HttpRequestException: Requesting HTTP version 2.0 with version policy RequestVersionOrHigher while unable to establish HTTP/2 connection.", DebugException="System.Net.Http.HttpRequestException: Requesting HTTP version 2.0 with version policy RequestVersionOrHigher while unable to establish HTTP/2 connection."
As best I can tell its not actually http2? but that wouldn't make sense since it has the ALPN. Any help would be greatly appreciated
So it turned out that it was the Go side after all. After a ton of research it turns out that by default the server was not advertising the ALPN so it was failing at the negotiation phase, despite being hard configured to use HTTP2. So what appears was happening was the .Net client was sending a ClienttHello with the h2 ALPN extension but the GO server's ServerHello did not include its ALPN in the response. Since the negotiation failed in this regards gRPC would kick back an error with it can't establish a connection over Http2. This is very similar to what would happen if the certificates were not accepted as well which is why I assumed it was cert based, but we can see that the cert was valid from Wireshark as it did pas the TLS check so something else was failing.
TL;DR the fix action is you must force the Go server's TLS configuration to include the ALPN of "h2" in the build something like this
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{
cert,
},
NextProtos: []string{"h2"},
}
return tlsConfig, nil
}
hope this helps anyone who has run into this stupid issue as well.