diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md
index 538dc24cf98..73cbec1c345 100644
--- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md
+++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+* Added validation that insecure channel is configured correctly when using
+ .NET Core 3.x for gRPC-based exporting.
+ ([#2691](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2691))
+
## 1.2.0-rc1
Released 2021-Nov-29
diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/BaseOtlpGrpcExportClient.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/BaseOtlpGrpcExportClient.cs
index aee71c7fd91..cc5a5be13da 100644
--- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/BaseOtlpGrpcExportClient.cs
+++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/BaseOtlpGrpcExportClient.cs
@@ -17,10 +17,10 @@
using System.Threading;
using System.Threading.Tasks;
using Grpc.Core;
+using OpenTelemetry.Internal;
#if NETSTANDARD2_1 || NET5_0_OR_GREATER
using Grpc.Net.Client;
#endif
-using OpenTelemetry.Internal;
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient
{
@@ -33,6 +33,8 @@ protected BaseOtlpGrpcExportClient(OtlpExporterOptions options)
Guard.Null(options, nameof(options));
Guard.InvalidTimeout(options.TimeoutMilliseconds, nameof(options.TimeoutMilliseconds));
+ ExporterClientValidation.EnsureUnencryptedSupportIsEnabled(options);
+
this.Options = options;
this.Headers = options.GetMetadataFromHeaders();
}
diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/ExporterClientValidation.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/ExporterClientValidation.cs
new file mode 100644
index 00000000000..468fd0402a3
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/ExporterClientValidation.cs
@@ -0,0 +1,45 @@
+//
+// Copyright The OpenTelemetry 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.
+//
+
+using System;
+
+namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient
+{
+ internal static class ExporterClientValidation
+ {
+ internal static void EnsureUnencryptedSupportIsEnabled(OtlpExporterOptions options)
+ {
+ var version = System.Environment.Version;
+
+ // This verification is only required for .NET Core 3.x
+ if (version.Major != 3)
+ {
+ return;
+ }
+
+ if (options.Endpoint.Scheme.Equals("http", StringComparison.InvariantCultureIgnoreCase))
+ {
+ if (AppContext.TryGetSwitch(
+ "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", out var unencryptedIsSupported) == false
+ || unencryptedIsSupported == false)
+ {
+ throw new InvalidOperationException(
+ "Calling insecure gRPC services on .NET Core 3.x requires enabling the 'System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport' switch. See: https://docs.microsoft.com/aspnet/core/grpc/troubleshoot#call-insecure-grpc-services-with-net-core-client");
+ }
+ }
+ }
+ }
+}
diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/ExporterClientValidationTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/ExporterClientValidationTests.cs
new file mode 100644
index 00000000000..5667385271f
--- /dev/null
+++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/ExporterClientValidationTests.cs
@@ -0,0 +1,77 @@
+//
+// Copyright The OpenTelemetry 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.
+//
+
+using System;
+using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient;
+using Xunit;
+
+namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests
+{
+ public class ExporterClientValidationTests : Http2UnencryptedSupportTests
+ {
+ private const string HttpEndpoint = "http://localhost:4173";
+ private const string HttpsEndpoint = "https://localhost:4173";
+
+ [Fact]
+ public void ExporterClientValidation_FlagIsEnabledForHttpEndpoint()
+ {
+ var options = new OtlpExporterOptions
+ {
+ Endpoint = new Uri(HttpEndpoint),
+ };
+
+ AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
+
+ var exception = Record.Exception(() => ExporterClientValidation.EnsureUnencryptedSupportIsEnabled(options));
+ Assert.Null(exception);
+ }
+
+ [Fact]
+ public void ExporterClientValidation_FlagIsNotEnabledForHttpEndpoint()
+ {
+ var options = new OtlpExporterOptions
+ {
+ Endpoint = new Uri(HttpEndpoint),
+ };
+
+ AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", false);
+
+ var exception = Record.Exception(() => ExporterClientValidation.EnsureUnencryptedSupportIsEnabled(options));
+
+ if (Environment.Version.Major == 3)
+ {
+ Assert.NotNull(exception);
+ Assert.IsType(exception);
+ }
+ else
+ {
+ Assert.Null(exception);
+ }
+ }
+
+ [Fact]
+ public void ExporterClientValidation_FlagIsNotEnabledForHttpsEndpoint()
+ {
+ var options = new OtlpExporterOptions
+ {
+ Endpoint = new Uri(HttpsEndpoint),
+ };
+
+ var exception = Record.Exception(() => ExporterClientValidation.EnsureUnencryptedSupportIsEnabled(options));
+ Assert.Null(exception);
+ }
+ }
+}
diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Http2UnencryptedSupportTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Http2UnencryptedSupportTests.cs
new file mode 100644
index 00000000000..907905ff672
--- /dev/null
+++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Http2UnencryptedSupportTests.cs
@@ -0,0 +1,45 @@
+//
+// Copyright The OpenTelemetry 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.
+//
+
+using System;
+
+namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests
+{
+ public class Http2UnencryptedSupportTests : IDisposable
+ {
+ private readonly bool initialFlagStatus;
+
+ public Http2UnencryptedSupportTests()
+ {
+ this.initialFlagStatus = this.DetermineInitialFlagStatus();
+ }
+
+ public void Dispose()
+ {
+ AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", this.initialFlagStatus);
+ }
+
+ private bool DetermineInitialFlagStatus()
+ {
+ if (AppContext.TryGetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", out var flag))
+ {
+ return flag;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTests.cs
index ef13a3415b8..12403c22d0f 100644
--- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTests.cs
+++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTests.cs
@@ -68,5 +68,32 @@ public void ExportResultIsSuccess(OtlpExportProtocol protocol, string endpoint)
Assert.Single(delegatingExporter.ExportResults);
Assert.Equal(ExportResult.Success, delegatingExporter.ExportResults[0]);
}
+
+ [Trait("CategoryName", "CollectorIntegrationTests")]
+ [SkipUnlessEnvVarFoundFact(CollectorHostnameEnvVarName)]
+ public void ConstructingGrpcExporterFailsWhenHttp2UnencryptedSupportIsDisabledForNetcoreapp31()
+ {
+ // Adding the OtlpExporter creates a GrpcChannel.
+ // This switch must be set before creating a GrpcChannel/HttpClient when calling an insecure gRPC service.
+ // We want to fail fast so we are disabling it
+ // See: https://docs.microsoft.com/aspnet/core/grpc/troubleshoot#call-insecure-grpc-services-with-net-core-client
+ AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", false);
+
+ var exporterOptions = new OtlpExporterOptions
+ {
+ Endpoint = new Uri($"http://{CollectorHostname}:4317"),
+ };
+
+ var exception = Record.Exception(() => new OtlpTraceExporter(exporterOptions));
+
+ if (Environment.Version.Major == 3)
+ {
+ Assert.NotNull(exception);
+ }
+ else
+ {
+ Assert.Null(exception);
+ }
+ }
}
}
diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj
index 396ed50110a..1bce078c2cb 100644
--- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj
+++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj
@@ -29,6 +29,7 @@
+
diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsExtensionsTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsExtensionsTests.cs
index 5e7e6d701e4..970f96daaea 100644
--- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsExtensionsTests.cs
+++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsExtensionsTests.cs
@@ -22,7 +22,7 @@
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests
{
- public class OtlpExporterOptionsExtensionsTests
+ public class OtlpExporterOptionsExtensionsTests : Http2UnencryptedSupportTests
{
[Theory]
[InlineData("key=value", new string[] { "key" }, new string[] { "value" })]
@@ -86,6 +86,14 @@ public void GetHeaders_NoOptionHeaders_ReturnsEmptyHeadres(string optionHeaders)
[InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpTraceExportClient))]
public void GetTraceExportClient_SupportedProtocol_ReturnsCorrectExportClient(OtlpExportProtocol protocol, Type expectedExportClientType)
{
+ if (protocol == OtlpExportProtocol.Grpc && Environment.Version.Major == 3)
+ {
+ // Adding the OtlpExporter creates a GrpcChannel.
+ // This switch must be set before creating a GrpcChannel when calling an insecure HTTP/2 endpoint.
+ // See: https://docs.microsoft.com/aspnet/core/grpc/troubleshoot#call-insecure-grpc-services-with-net-core-client
+ AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
+ }
+
var options = new OtlpExporterOptions
{
Protocol = protocol,
@@ -96,6 +104,32 @@ public void GetTraceExportClient_SupportedProtocol_ReturnsCorrectExportClient(Ot
Assert.Equal(expectedExportClientType, exportClient.GetType());
}
+ [Fact]
+ public void GetTraceExportClient_GetClientForGrpcWithoutUnencryptedFlag_ThrowsException()
+ {
+ // Adding the OtlpExporter creates a GrpcChannel.
+ // This switch must be set before creating a GrpcChannel when calling an insecure HTTP/2 endpoint.
+ // See: https://docs.microsoft.com/aspnet/core/grpc/troubleshoot#call-insecure-grpc-services-with-net-core-client
+ AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", false);
+
+ var options = new OtlpExporterOptions
+ {
+ Protocol = OtlpExportProtocol.Grpc,
+ };
+
+ var exception = Record.Exception(() => options.GetTraceExportClient());
+
+ if (Environment.Version.Major == 3)
+ {
+ Assert.NotNull(exception);
+ Assert.IsType(exception);
+ }
+ else
+ {
+ Assert.Null(exception);
+ }
+ }
+
[Fact]
public void GetTraceExportClient_UnsupportedProtocol_Throws()
{
diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs
index b040143c0df..64070b84772 100644
--- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs
+++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs
@@ -35,7 +35,7 @@
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests
{
- public class OtlpTraceExporterTests
+ public class OtlpTraceExporterTests : Http2UnencryptedSupportTests
{
static OtlpTraceExporterTests()
{
@@ -372,6 +372,14 @@ public void ToOtlpSpanPeerServiceTest()
[Fact]
public void UseOpenTelemetryProtocolActivityExporterWithCustomActivityProcessor()
{
+ if (Environment.Version.Major == 3)
+ {
+ // Adding the OtlpExporter creates a GrpcChannel.
+ // This switch must be set before creating a GrpcChannel when calling an insecure HTTP/2 endpoint.
+ // See: https://docs.microsoft.com/aspnet/core/grpc/troubleshoot#call-insecure-grpc-services-with-net-core-client
+ AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
+ }
+
const string ActivitySourceName = "otlp.test";
TestActivityProcessor testActivityProcessor = new TestActivityProcessor();