Skip to content

Implement IConnectionEndPointFeature in Kestrel's TransportConnection #62162

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -4,8 +4,6 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Metrics;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Security.Authentication;
using Microsoft.AspNetCore.Connections;
@@ -321,43 +319,7 @@ private void TlsHandshakeStopCore(ConnectionMetricsContext metricsContext, long

private static void InitializeConnectionTags(ref TagList tags, in ConnectionMetricsContext metricsContext)
{
var localEndpoint = metricsContext.ConnectionContext.LocalEndPoint;
if (localEndpoint is IPEndPoint localIPEndPoint)
{
tags.Add("server.address", localIPEndPoint.Address.ToString());
tags.Add("server.port", localIPEndPoint.Port);

switch (localIPEndPoint.Address.AddressFamily)
{
case AddressFamily.InterNetwork:
tags.Add("network.type", "ipv4");
break;
case AddressFamily.InterNetworkV6:
tags.Add("network.type", "ipv6");
break;
}

// There isn't an easy way to detect whether QUIC is the underlying transport.
// This code assumes that a multiplexed connection is QUIC.
// Improve in the future if there are additional multiplexed connection types.
var transport = metricsContext.ConnectionContext is not MultiplexedConnectionContext ? "tcp" : "udp";
tags.Add("network.transport", transport);
}
else if (localEndpoint is UnixDomainSocketEndPoint udsEndPoint)
{
tags.Add("server.address", udsEndPoint.ToString());
tags.Add("network.transport", "unix");
}
else if (localEndpoint is NamedPipeEndPoint namedPipeEndPoint)
{
tags.Add("server.address", namedPipeEndPoint.ToString());
tags.Add("network.transport", "pipe");
}
else if (localEndpoint != null)
{
tags.Add("server.address", localEndpoint.ToString());
tags.Add("network.transport", localEndpoint.AddressFamily.ToString());
}
ConnectionEndpointTags.AddConnectionEndpointTags(ref tags, metricsContext.ConnectionContext);
}

public ConnectionMetricsContext CreateContext(BaseConnectionContext connection)
Original file line number Diff line number Diff line change
@@ -37,6 +37,7 @@
<Compile Include="$(SharedSourceRoot)Obsoletions.cs" LinkBase="Shared" />
<Compile Include="$(RepoRoot)src\Shared\TaskToApm.cs" Link="Internal\TaskToApm.cs" />
<Compile Include="$(SharedSourceRoot)Metrics\MetricsExtensions.cs" />
<Compile Include="$(SharedSourceRoot)ConnectionEndpointTags.cs" />
</ItemGroup>

<ItemGroup>
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.InternalTesting;
using Moq;
using Xunit;

namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests;

public class TransportConnectionFeatureCollectionTests
{
[Fact]
public void IConnectionEndPointFeature_IsAvailableInFeatureCollection()
{
var serviceContext = new TestServiceContext();
var connection = new Mock<DefaultConnectionContext> { CallBase = true }.Object;
var transportConnectionManager = new TransportConnectionManager(serviceContext.ConnectionManager);
var kestrelConnection = CreateKestrelConnection(serviceContext, connection, transportConnectionManager);

var endpointFeature = kestrelConnection.TransportConnection.Features.Get<IConnectionEndPointFeature>();

Assert.NotNull(endpointFeature);
}

[Fact]
public void IConnectionEndPointFeature_ReturnsCorrectLocalEndPoint()
{
var serviceContext = new TestServiceContext();
var connection = new Mock<DefaultConnectionContext> { CallBase = true }.Object;
var expectedLocalEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);
connection.LocalEndPoint = expectedLocalEndPoint;
var transportConnectionManager = new TransportConnectionManager(serviceContext.ConnectionManager);
var kestrelConnection = CreateKestrelConnection(serviceContext, connection, transportConnectionManager);

var endpointFeature = kestrelConnection.TransportConnection.Features.Get<IConnectionEndPointFeature>();

Assert.NotNull(endpointFeature);
Assert.Equal(expectedLocalEndPoint, endpointFeature.LocalEndPoint);
}

[Fact]
public void IConnectionEndPointFeature_ReturnsCorrectRemoteEndPoint()
{
var serviceContext = new TestServiceContext();
var connection = new Mock<DefaultConnectionContext> { CallBase = true }.Object;
var expectedRemoteEndPoint = new IPEndPoint(IPAddress.Parse("192.168.1.100"), 54321);
connection.RemoteEndPoint = expectedRemoteEndPoint;
var transportConnectionManager = new TransportConnectionManager(serviceContext.ConnectionManager);
var kestrelConnection = CreateKestrelConnection(serviceContext, connection, transportConnectionManager);

var endpointFeature = kestrelConnection.TransportConnection.Features.Get<IConnectionEndPointFeature>();

Assert.NotNull(endpointFeature);
Assert.Equal(expectedRemoteEndPoint, endpointFeature.RemoteEndPoint);
}

[Fact]
public void IConnectionEndPointFeature_AllowsSettingLocalEndPoint()
{
var serviceContext = new TestServiceContext();
var connection = new Mock<DefaultConnectionContext> { CallBase = true }.Object;
var transportConnectionManager = new TransportConnectionManager(serviceContext.ConnectionManager);
var kestrelConnection = CreateKestrelConnection(serviceContext, connection, transportConnectionManager);
var newLocalEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9090);

var endpointFeature = kestrelConnection.TransportConnection.Features.Get<IConnectionEndPointFeature>();
endpointFeature.LocalEndPoint = newLocalEndPoint;

Assert.Equal(newLocalEndPoint, kestrelConnection.TransportConnection.LocalEndPoint);
Assert.Equal(newLocalEndPoint, endpointFeature.LocalEndPoint);
}

[Fact]
public void IConnectionEndPointFeature_AllowsSettingRemoteEndPoint()
{
var serviceContext = new TestServiceContext();
var connection = new Mock<DefaultConnectionContext> { CallBase = true }.Object;
var transportConnectionManager = new TransportConnectionManager(serviceContext.ConnectionManager);
var kestrelConnection = CreateKestrelConnection(serviceContext, connection, transportConnectionManager);
var newRemoteEndPoint = new IPEndPoint(IPAddress.Parse("10.0.0.1"), 12345);

var endpointFeature = kestrelConnection.TransportConnection.Features.Get<IConnectionEndPointFeature>();
endpointFeature.RemoteEndPoint = newRemoteEndPoint;

Assert.Equal(newRemoteEndPoint, kestrelConnection.TransportConnection.RemoteEndPoint);
Assert.Equal(newRemoteEndPoint, endpointFeature.RemoteEndPoint);
}

private static KestrelConnection<ConnectionContext> CreateKestrelConnection(TestServiceContext serviceContext, DefaultConnectionContext connection, TransportConnectionManager transportConnectionManager, Func<ConnectionContext, Task> connectionDelegate = null)
{
connectionDelegate ??= _ => Task.CompletedTask;

return new KestrelConnection<ConnectionContext>(
id: 0, serviceContext, transportConnectionManager, connectionDelegate, connection, serviceContext.Log, TestContextFactory.CreateMetricsContext(connection));
}
}
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@

using System.Buffers;
using System.IO.Pipelines;
using System.Net;
using Microsoft.AspNetCore.Connections.Features;

#nullable enable
@@ -37,4 +38,16 @@ CancellationToken IConnectionLifetimeFeature.ConnectionClosed
}

void IConnectionLifetimeFeature.Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via IConnectionLifetimeFeature.Abort()."));

EndPoint? IConnectionEndPointFeature.LocalEndPoint
{
get => LocalEndPoint;
set => LocalEndPoint = value;
}

EndPoint? IConnectionEndPointFeature.RemoteEndPoint
{
get => RemoteEndPoint;
set => RemoteEndPoint = value;
}
}
25 changes: 24 additions & 1 deletion src/Servers/Kestrel/shared/TransportConnection.Generated.cs
Original file line number Diff line number Diff line change
@@ -18,14 +18,16 @@ internal partial class TransportConnection : IFeatureCollection,
IConnectionTransportFeature,
IConnectionItemsFeature,
IMemoryPoolFeature,
IConnectionLifetimeFeature
IConnectionLifetimeFeature,
IConnectionEndPointFeature
{
// Implemented features
internal protected IConnectionIdFeature? _currentIConnectionIdFeature;
internal protected IConnectionTransportFeature? _currentIConnectionTransportFeature;
internal protected IConnectionItemsFeature? _currentIConnectionItemsFeature;
internal protected IMemoryPoolFeature? _currentIMemoryPoolFeature;
internal protected IConnectionLifetimeFeature? _currentIConnectionLifetimeFeature;
internal protected IConnectionEndPointFeature? _currentIConnectionEndPointFeature;

// Other reserved feature slots
internal protected IPersistentStateFeature? _currentIPersistentStateFeature;
@@ -48,6 +50,7 @@ private void FastReset()
_currentIConnectionItemsFeature = this;
_currentIMemoryPoolFeature = this;
_currentIConnectionLifetimeFeature = this;
_currentIConnectionEndPointFeature = this;

_currentIPersistentStateFeature = null;
_currentIConnectionSocketFeature = null;
@@ -180,6 +183,10 @@ private void ExtraFeatureSet(Type key, object? value)
{
feature = _currentIConnectionMetricsTagsFeature;
}
else if (key == typeof(IConnectionEndPointFeature))
{
feature = _currentIConnectionEndPointFeature;
}
else if (MaybeExtra != null)
{
feature = ExtraFeatureGet(key);
@@ -244,6 +251,10 @@ private void ExtraFeatureSet(Type key, object? value)
{
_currentIConnectionMetricsTagsFeature = (IConnectionMetricsTagsFeature?)value;
}
else if (key == typeof(IConnectionEndPointFeature))
{
_currentIConnectionEndPointFeature = (IConnectionEndPointFeature?)value;
}
else
{
ExtraFeatureSet(key, value);
@@ -310,6 +321,10 @@ private void ExtraFeatureSet(Type key, object? value)
{
feature = Unsafe.As<IConnectionMetricsTagsFeature?, TFeature?>(ref _currentIConnectionMetricsTagsFeature);
}
else if (typeof(TFeature) == typeof(IConnectionEndPointFeature))
{
feature = Unsafe.As<IConnectionEndPointFeature?, TFeature?>(ref _currentIConnectionEndPointFeature);
}
else if (MaybeExtra != null)
{
feature = (TFeature?)(ExtraFeatureGet(typeof(TFeature)));
@@ -382,6 +397,10 @@ private void ExtraFeatureSet(Type key, object? value)
{
_currentIConnectionMetricsTagsFeature = Unsafe.As<TFeature?, IConnectionMetricsTagsFeature?>(ref feature);
}
else if (typeof(TFeature) == typeof(IConnectionEndPointFeature))
{
_currentIConnectionEndPointFeature = Unsafe.As<TFeature?, IConnectionEndPointFeature?>(ref feature);
}
else
{
ExtraFeatureSet(typeof(TFeature), feature);
@@ -442,6 +461,10 @@ private IEnumerable<KeyValuePair<Type, object>> FastEnumerable()
{
yield return new KeyValuePair<Type, object>(typeof(IConnectionMetricsTagsFeature), _currentIConnectionMetricsTagsFeature);
}
if (_currentIConnectionEndPointFeature != null)
{
yield return new KeyValuePair<Type, object>(typeof(IConnectionEndPointFeature), _currentIConnectionEndPointFeature);
}

if (MaybeExtra != null)
{
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Net;
using Microsoft.AspNetCore.Connections.Features;

namespace Microsoft.AspNetCore.Connections;
@@ -28,4 +29,16 @@ CancellationToken IConnectionLifetimeFeature.ConnectionClosed
}

void IConnectionLifetimeFeature.Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via IConnectionLifetimeFeature.Abort()."));

EndPoint? IConnectionEndPointFeature.LocalEndPoint
{
get => LocalEndPoint;
set => LocalEndPoint = value;
}

EndPoint? IConnectionEndPointFeature.RemoteEndPoint
{
get => RemoteEndPoint;
set => RemoteEndPoint = value;
}
}
Original file line number Diff line number Diff line change
@@ -17,13 +17,15 @@ internal partial class TransportMultiplexedConnection : IFeatureCollection,
IConnectionIdFeature,
IConnectionItemsFeature,
IMemoryPoolFeature,
IConnectionLifetimeFeature
IConnectionLifetimeFeature,
IConnectionEndPointFeature
{
// Implemented features
internal protected IConnectionIdFeature? _currentIConnectionIdFeature;
internal protected IConnectionItemsFeature? _currentIConnectionItemsFeature;
internal protected IMemoryPoolFeature? _currentIMemoryPoolFeature;
internal protected IConnectionLifetimeFeature? _currentIConnectionLifetimeFeature;
internal protected IConnectionEndPointFeature? _currentIConnectionEndPointFeature;

// Other reserved feature slots
internal protected IConnectionTransportFeature? _currentIConnectionTransportFeature;
@@ -40,6 +42,7 @@ private void FastReset()
_currentIConnectionItemsFeature = this;
_currentIMemoryPoolFeature = this;
_currentIConnectionLifetimeFeature = this;
_currentIConnectionEndPointFeature = this;

_currentIConnectionTransportFeature = null;
_currentIProtocolErrorCodeFeature = null;
@@ -135,6 +138,10 @@ private void ExtraFeatureSet(Type key, object? value)
{
feature = _currentIConnectionLifetimeFeature;
}
else if (key == typeof(IConnectionEndPointFeature))
{
feature = _currentIConnectionEndPointFeature;
}
else if (key == typeof(IProtocolErrorCodeFeature))
{
feature = _currentIProtocolErrorCodeFeature;
@@ -175,6 +182,10 @@ private void ExtraFeatureSet(Type key, object? value)
{
_currentIConnectionLifetimeFeature = (IConnectionLifetimeFeature?)value;
}
else if (key == typeof(IConnectionEndPointFeature))
{
_currentIConnectionEndPointFeature = (IConnectionEndPointFeature?)value;
}
else if (key == typeof(IProtocolErrorCodeFeature))
{
_currentIProtocolErrorCodeFeature = (IProtocolErrorCodeFeature?)value;
@@ -217,6 +228,10 @@ private void ExtraFeatureSet(Type key, object? value)
{
feature = Unsafe.As<IConnectionLifetimeFeature?, TFeature?>(ref _currentIConnectionLifetimeFeature);
}
else if (typeof(TFeature) == typeof(IConnectionEndPointFeature))
{
feature = Unsafe.As<IConnectionEndPointFeature?, TFeature?>(ref _currentIConnectionEndPointFeature);
}
else if (typeof(TFeature) == typeof(IProtocolErrorCodeFeature))
{
feature = Unsafe.As<IProtocolErrorCodeFeature?, TFeature?>(ref _currentIProtocolErrorCodeFeature);
@@ -260,6 +275,10 @@ private void ExtraFeatureSet(Type key, object? value)
{
_currentIConnectionLifetimeFeature = Unsafe.As<TFeature?, IConnectionLifetimeFeature?>(ref feature);
}
else if (typeof(TFeature) == typeof(IConnectionEndPointFeature))
{
_currentIConnectionEndPointFeature = Unsafe.As<TFeature?, IConnectionEndPointFeature?>(ref feature);
}
else if (typeof(TFeature) == typeof(IProtocolErrorCodeFeature))
{
_currentIProtocolErrorCodeFeature = Unsafe.As<TFeature?, IProtocolErrorCodeFeature?>(ref feature);
@@ -296,6 +315,10 @@ private IEnumerable<KeyValuePair<Type, object>> FastEnumerable()
{
yield return new KeyValuePair<Type, object>(typeof(IConnectionLifetimeFeature), _currentIConnectionLifetimeFeature);
}
if (_currentIConnectionEndPointFeature != null)
{
yield return new KeyValuePair<Type, object>(typeof(IConnectionEndPointFeature), _currentIConnectionEndPointFeature);
}
if (_currentIProtocolErrorCodeFeature != null)
{
yield return new KeyValuePair<Type, object>(typeof(IProtocolErrorCodeFeature), _currentIProtocolErrorCodeFeature);
Original file line number Diff line number Diff line change
@@ -24,7 +24,8 @@ public static string GenerateFile()
"IStreamIdFeature",
"IStreamAbortFeature",
"IStreamClosedFeature",
"IConnectionMetricsTagsFeature"
"IConnectionMetricsTagsFeature",
"IConnectionEndPointFeature"
};

var implementedFeatures = new[]
@@ -33,7 +34,8 @@ public static string GenerateFile()
"IConnectionTransportFeature",
"IConnectionItemsFeature",
"IMemoryPoolFeature",
"IConnectionLifetimeFeature"
"IConnectionLifetimeFeature",
"IConnectionEndPointFeature"
};

var usings = $@"
Loading