diff --git a/samples/SampleApp/Startup.cs b/samples/SampleApp/Startup.cs
index fb6c8e9a2..61bd9a3aa 100644
--- a/samples/SampleApp/Startup.cs
+++ b/samples/SampleApp/Startup.cs
@@ -52,6 +52,11 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IAp
context.Request.Path,
context.Request.QueryString);
+ var connectionFeature = context.Connection;
+ Console.WriteLine($"Peer: {connectionFeature.RemoteIpAddress?.ToString()} {connectionFeature.RemotePort}");
+ Console.WriteLine($"Sock: {connectionFeature.LocalIpAddress?.ToString()} {connectionFeature.LocalPort}");
+ Console.WriteLine($"IsLocal: {connectionFeature.IsLocal}");
+
context.Response.ContentLength = 11;
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync("Hello world");
diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Connection.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Connection.cs
index 906faa986..ac0540d18 100644
--- a/src/Microsoft.AspNet.Server.Kestrel/Http/Connection.cs
+++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Connection.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using System.Net;
using System.Threading;
using Microsoft.AspNet.Server.Kestrel.Filter;
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
@@ -28,6 +29,9 @@ public class Connection : ConnectionContext, IConnectionControl
private readonly object _stateLock = new object();
private ConnectionState _connectionState;
+ private IPEndPoint _remoteEndPoint;
+ private IPEndPoint _localEndPoint;
+
public Connection(ListenerContext context, UvStreamHandle socket) : base(context)
{
_socket = socket;
@@ -46,13 +50,20 @@ public void Start()
// Start socket prior to applying the ConnectionFilter
_socket.ReadStart(_allocCallback, _readCallback, this);
+ var tcpHandle = _socket as UvTcpHandle;
+ if (tcpHandle != null)
+ {
+ _remoteEndPoint = tcpHandle.GetPeerIPEndPoint();
+ _localEndPoint = tcpHandle.GetSockIPEndPoint();
+ }
+
// Don't initialize _frame until SocketInput and SocketOutput are set to their final values.
if (ConnectionFilter == null)
{
SocketInput = _rawSocketInput;
SocketOutput = _rawSocketOutput;
- _frame = new Frame(this);
+ _frame = CreateFrame();
_frame.Start();
}
else
@@ -94,7 +105,7 @@ private void ApplyConnectionFilter()
SocketInput = filteredStreamAdapter.SocketInput;
SocketOutput = filteredStreamAdapter.SocketOutput;
- _frame = new Frame(this);
+ _frame = CreateFrame();
_frame.Start();
}
@@ -142,6 +153,11 @@ private void OnRead(UvStreamHandle handle, int status)
_rawSocketInput.IncomingComplete(readCount, error);
}
+ private Frame CreateFrame()
+ {
+ return new Frame(this, _remoteEndPoint, _localEndPoint);
+ }
+
void IConnectionControl.Pause()
{
Log.ConnectionPause(_connectionId);
diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.FeatureCollection.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.FeatureCollection.cs
index 126dce789..1a4efb32f 100644
--- a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.FeatureCollection.cs
+++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.FeatureCollection.cs
@@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features;
@@ -13,7 +14,11 @@
namespace Microsoft.AspNet.Server.Kestrel.Http
{
- public partial class Frame : IFeatureCollection, IHttpRequestFeature, IHttpResponseFeature, IHttpUpgradeFeature
+ public partial class Frame : IFeatureCollection,
+ IHttpRequestFeature,
+ IHttpResponseFeature,
+ IHttpUpgradeFeature,
+ IHttpConnectionFeature
{
// NOTE: When feature interfaces are added to or removed from this Frame class implementation,
// then the list of `implementedFeatures` in the generated code project MUST also be updated.
@@ -246,6 +251,16 @@ bool IHttpUpgradeFeature.IsUpgradableRequest
int IFeatureCollection.Revision => _featureRevision;
+ IPAddress IHttpConnectionFeature.RemoteIpAddress { get; set; }
+
+ IPAddress IHttpConnectionFeature.LocalIpAddress { get; set; }
+
+ int IHttpConnectionFeature.RemotePort { get; set; }
+
+ int IHttpConnectionFeature.LocalPort { get; set; }
+
+ bool IHttpConnectionFeature.IsLocal { get; set; }
+
object IFeatureCollection.this[Type key]
{
get { return FastFeatureGet(key); }
diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.Generated.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.Generated.cs
index 4d336eb9e..985267a57 100644
--- a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.Generated.cs
+++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.Generated.cs
@@ -45,11 +45,11 @@ private void FastReset()
_currentIHttpRequestFeature = this;
_currentIHttpResponseFeature = this;
_currentIHttpUpgradeFeature = this;
+ _currentIHttpConnectionFeature = this;
_currentIHttpRequestIdentifierFeature = null;
_currentIServiceProvidersFeature = null;
_currentIHttpRequestLifetimeFeature = null;
- _currentIHttpConnectionFeature = null;
_currentIHttpAuthenticationFeature = null;
_currentIQueryFeature = null;
_currentIFormFeature = null;
diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs
index 13546271e..3c07bf2de 100644
--- a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs
+++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs
@@ -5,10 +5,12 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
+using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
@@ -44,8 +46,22 @@ public partial class Frame : FrameContext, IFrameControl
private bool _autoChunk;
private Exception _applicationException;
- public Frame(ConnectionContext context) : base(context)
+ private readonly IPEndPoint _localEndPoint;
+ private readonly IPEndPoint _remoteEndPoint;
+
+ public Frame(ConnectionContext context)
+ : this(context, remoteEndPoint: null, localEndPoint: null)
+ {
+ }
+
+ public Frame(ConnectionContext context,
+ IPEndPoint remoteEndPoint,
+ IPEndPoint localEndPoint)
+ : base(context)
{
+ _remoteEndPoint = remoteEndPoint;
+ _localEndPoint = localEndPoint;
+
FrameControl = this;
Reset();
}
@@ -99,6 +115,21 @@ public void Reset()
ResponseBody = null;
DuplexStream = null;
+ var httpConnectionFeature = this as IHttpConnectionFeature;
+ httpConnectionFeature.RemoteIpAddress = _remoteEndPoint?.Address;
+ httpConnectionFeature.RemotePort = _remoteEndPoint?.Port ?? 0;
+
+ httpConnectionFeature.LocalIpAddress = _localEndPoint?.Address;
+ httpConnectionFeature.LocalPort = _localEndPoint?.Port ?? 0;
+
+ if (_remoteEndPoint != null && _localEndPoint != null)
+ {
+ httpConnectionFeature.IsLocal = _remoteEndPoint.Address.Equals(_localEndPoint.Address);
+ }
+ else
+ {
+ httpConnectionFeature.IsLocal = false;
+ }
}
public void ResetResponseHeaders()
@@ -123,7 +154,7 @@ public void Start()
///
/// Should be called when the server wants to initiate a shutdown. The Task returned will
/// become complete when the RequestProcessingAsync function has exited. It is expected that
- /// Stop will be called on all active connections, and Task.WaitAll() will be called on every
+ /// Stop will be called on all active connections, and Task.WaitAll() will be called on every
/// return value.
///
public Task Stop()
@@ -136,7 +167,7 @@ public Task Stop()
}
///
- /// Primary loop which consumes socket input, parses it for protocol framing, and invokes the
+ /// Primary loop which consumes socket input, parses it for protocol framing, and invokes the
/// application delegate for as long as the socket is intended to remain open.
/// The resulting Task from this loop is preserved in a field which is used when the server needs
/// to drain and close all currently active connections.
@@ -731,7 +762,7 @@ public static bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders req
if (chSecond != '\n')
{
- // "\r" was all by itself, move just after it and try again
+ // "\r" was all by itself, move just after it and try again
scan = endValue;
scan.Take();
continue;
@@ -740,7 +771,7 @@ public static bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders req
var chThird = scan.Peek();
if (chThird == ' ' || chThird == '\t')
{
- // special case, "\r\n " or "\r\n\t".
+ // special case, "\r\n " or "\r\n\t".
// this is considered wrapping"linear whitespace" and is actually part of the header value
// continue past this for the next
wrapping = true;
diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/Libuv.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/Libuv.cs
index 2a1c9871c..ef6fb7443 100644
--- a/src/Microsoft.AspNet.Server.Kestrel/Networking/Libuv.cs
+++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/Libuv.cs
@@ -56,6 +56,8 @@ public Libuv()
_uv_req_size = NativeDarwinMonoMethods.uv_req_size;
_uv_ip4_addr = NativeDarwinMonoMethods.uv_ip4_addr;
_uv_ip6_addr = NativeDarwinMonoMethods.uv_ip6_addr;
+ _uv_tcp_getpeername = NativeDarwinMonoMethods.uv_tcp_getpeername;
+ _uv_tcp_getsockname = NativeDarwinMonoMethods.uv_tcp_getsockname;
_uv_walk = NativeDarwinMonoMethods.uv_walk;
}
else
@@ -95,6 +97,8 @@ public Libuv()
_uv_req_size = NativeMethods.uv_req_size;
_uv_ip4_addr = NativeMethods.uv_ip4_addr;
_uv_ip6_addr = NativeMethods.uv_ip6_addr;
+ _uv_tcp_getpeername = NativeMethods.uv_tcp_getpeername;
+ _uv_tcp_getsockname = NativeMethods.uv_tcp_getsockname;
_uv_walk = NativeMethods.uv_walk;
}
}
@@ -206,9 +210,9 @@ public void tcp_init(UvLoopHandle loop, UvTcpHandle handle)
Check(_uv_tcp_init(loop, handle));
}
- protected delegate int uv_tcp_bind_func(UvTcpHandle handle, ref sockaddr addr, int flags);
+ protected delegate int uv_tcp_bind_func(UvTcpHandle handle, ref SockAddr addr, int flags);
protected uv_tcp_bind_func _uv_tcp_bind;
- public void tcp_bind(UvTcpHandle handle, ref sockaddr addr, int flags)
+ public void tcp_bind(UvTcpHandle handle, ref SockAddr addr, int flags)
{
handle.Validate();
Check(_uv_tcp_bind(handle, ref addr, flags));
@@ -365,16 +369,16 @@ public int req_size(RequestType reqType)
return _uv_req_size(reqType);
}
- protected delegate int uv_ip4_addr_func(string ip, int port, out sockaddr addr);
+ protected delegate int uv_ip4_addr_func(string ip, int port, out SockAddr addr);
protected uv_ip4_addr_func _uv_ip4_addr;
- public int ip4_addr(string ip, int port, out sockaddr addr, out Exception error)
+ public int ip4_addr(string ip, int port, out SockAddr addr, out Exception error)
{
return Check(_uv_ip4_addr(ip, port, out addr), out error);
}
- protected delegate int uv_ip6_addr_func(string ip, int port, out sockaddr addr);
+ protected delegate int uv_ip6_addr_func(string ip, int port, out SockAddr addr);
protected uv_ip6_addr_func _uv_ip6_addr;
- public int ip6_addr(string ip, int port, out sockaddr addr, out Exception error)
+ public int ip6_addr(string ip, int port, out SockAddr addr, out Exception error)
{
return Check(_uv_ip6_addr(ip, port, out addr), out error);
}
@@ -388,24 +392,25 @@ unsafe public void walk(UvLoopHandle loop, uv_walk_cb walk_cb, IntPtr arg)
_uv_walk(loop, walk_cb, arg);
}
- public uv_buf_t buf_init(IntPtr memory, int len)
+ public delegate int uv_tcp_getsockname_func(UvTcpHandle handle, out SockAddr addr, ref int namelen);
+ protected uv_tcp_getsockname_func _uv_tcp_getsockname;
+ public void tcp_getsockname(UvTcpHandle handle, out SockAddr addr, ref int namelen)
{
- return new uv_buf_t(memory, len, IsWindows);
+ handle.Validate();
+ Check(_uv_tcp_getsockname(handle, out addr, ref namelen));
}
- public struct sockaddr
+ public delegate int uv_tcp_getpeername_func(UvTcpHandle handle, out SockAddr addr, ref int namelen);
+ protected uv_tcp_getpeername_func _uv_tcp_getpeername;
+ public void tcp_getpeername(UvTcpHandle handle, out SockAddr addr, ref int namelen)
{
- // this type represents native memory occupied by sockaddr struct
- // https://msdn.microsoft.com/en-us/library/windows/desktop/ms740496(v=vs.85).aspx
- // although the c/c++ header defines it as a 2-byte short followed by a 14-byte array,
- // the simplest way to reserve the same size in c# is with four nameless long values
-
- private long _field0;
- private long _field1;
- private long _field2;
- private long _field3;
+ handle.Validate();
+ Check(_uv_tcp_getpeername(handle, out addr, ref namelen));
+ }
- public sockaddr(long ignored) { _field3 = _field0 = _field1 = _field2 = _field3 = 0; }
+ public uv_buf_t buf_init(IntPtr memory, int len)
+ {
+ return new uv_buf_t(memory, len, IsWindows);
}
public struct uv_buf_t
@@ -504,7 +509,7 @@ private static class NativeMethods
public static extern int uv_tcp_init(UvLoopHandle loop, UvTcpHandle handle);
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
- public static extern int uv_tcp_bind(UvTcpHandle handle, ref sockaddr addr, int flags);
+ public static extern int uv_tcp_bind(UvTcpHandle handle, ref SockAddr addr, int flags);
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
public static extern int uv_tcp_open(UvTcpHandle handle, IntPtr hSocket);
@@ -564,10 +569,16 @@ private static class NativeMethods
public static extern int uv_req_size(RequestType reqType);
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
- public static extern int uv_ip4_addr(string ip, int port, out sockaddr addr);
+ public static extern int uv_ip4_addr(string ip, int port, out SockAddr addr);
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
- public static extern int uv_ip6_addr(string ip, int port, out sockaddr addr);
+ public static extern int uv_ip6_addr(string ip, int port, out SockAddr addr);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_tcp_getsockname(UvTcpHandle handle, out SockAddr name, ref int namelen);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_tcp_getpeername(UvTcpHandle handle, out SockAddr name, ref int namelen);
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
unsafe public static extern int uv_walk(UvLoopHandle loop, uv_walk_cb walk_cb, IntPtr arg);
@@ -606,7 +617,7 @@ private static class NativeDarwinMonoMethods
public static extern int uv_tcp_init(UvLoopHandle loop, UvTcpHandle handle);
[DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)]
- public static extern int uv_tcp_bind(UvTcpHandle handle, ref sockaddr addr, int flags);
+ public static extern int uv_tcp_bind(UvTcpHandle handle, ref SockAddr addr, int flags);
[DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)]
public static extern int uv_tcp_open(UvTcpHandle handle, IntPtr hSocket);
@@ -666,10 +677,16 @@ private static class NativeDarwinMonoMethods
public static extern int uv_req_size(RequestType reqType);
[DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)]
- public static extern int uv_ip4_addr(string ip, int port, out sockaddr addr);
+ public static extern int uv_ip4_addr(string ip, int port, out SockAddr addr);
+
+ [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_ip6_addr(string ip, int port, out SockAddr addr);
+
+ [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_tcp_getsockname(UvTcpHandle handle, out SockAddr name, ref int namelen);
[DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)]
- public static extern int uv_ip6_addr(string ip, int port, out sockaddr addr);
+ public static extern int uv_tcp_getpeername(UvTcpHandle handle, out SockAddr name, ref int namelen);
[DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)]
unsafe public static extern int uv_walk(UvLoopHandle loop, uv_walk_cb walk_cb, IntPtr arg);
diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/SockAddr.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/SockAddr.cs
new file mode 100644
index 000000000..c101de111
--- /dev/null
+++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/SockAddr.cs
@@ -0,0 +1,94 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Net;
+
+namespace Microsoft.AspNet.Server.Kestrel.Networking
+{
+ public struct SockAddr
+ {
+ // this type represents native memory occupied by sockaddr struct
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms740496(v=vs.85).aspx
+ // although the c/c++ header defines it as a 2-byte short followed by a 14-byte array,
+ // the simplest way to reserve the same size in c# is with four nameless long values
+ private long _field0;
+ private long _field1;
+ private long _field2;
+ private long _field3;
+
+ public SockAddr(long ignored)
+ {
+ _field3 = _field0 = _field1 = _field2 = _field3 = 0;
+ }
+
+ public IPEndPoint GetIPEndPoint()
+ {
+ // The bytes are represented in network byte order.
+ //
+ // Example 1: [2001:4898:e0:391:b9ef:1124:9d3e:a354]:39179
+ //
+ // 0000 0000 0b99 0017 => The third and fourth bytes 990B is the actual port
+ // 9103 e000 9848 0120 => IPv6 address is represented in the 128bit field1 and field2.
+ // 54a3 3e9d 2411 efb9 Read these two 64-bit long from right to left byte by byte.
+ // 0000 0000 0000 0000
+ //
+ // Example 2: 10.135.34.141:39178 when adopt dual-stack sockets, IPv4 is mapped to IPv6
+ //
+ // 0000 0000 0a99 0017 => The port representation are the same
+ // 0000 0000 0000 0000
+ // 8d22 870a ffff 0000 => IPv4 occupies the last 32 bit: 0A.87.22.8d is the actual address.
+ // 0000 0000 0000 0000
+ //
+ // Example 3: 10.135.34.141:12804, not dual-stack sockets
+ // 8d22 870a fd31 0002 => sa_family == AF_INET (02)
+ // 0000 0000 0000 0000
+ // 0000 0000 0000 0000
+ // 0000 0000 0000 0000
+
+
+ // Quick calculate the port by mask the field and locate the byte 3 and byte 4
+ // and then shift them to correct place to form a int.
+ var port = ((int)(_field0 & 0x00FF0000) >> 8) | (int)((_field0 & 0xFF000000) >> 24);
+
+ var family = (int)_field0 & 0x000000FF;
+ if (family == 0x00000002)
+ {
+ // AF_INET => IPv4
+ return new IPEndPoint(new IPAddress((_field0 >> 32) & 0xFFFFFFFF), port);
+ }
+ else if (IsIPv4MappedToIPv6())
+ {
+ var ipv4bits = (_field2 >> 32) & 0x00000000FFFFFFFF;
+ return new IPEndPoint(new IPAddress(ipv4bits), port);
+ }
+ else
+ {
+ // otherwise IPv6
+ var bytes1 = BitConverter.GetBytes(_field1);
+ var bytes2 = BitConverter.GetBytes(_field2);
+
+ var bytes = new byte[16];
+ for (int i = 0; i < 8; ++i)
+ {
+ bytes[i] = bytes1[i];
+ bytes[i + 8] = bytes2[i];
+ }
+
+ return new IPEndPoint(new IPAddress(bytes), port);
+ }
+ }
+
+ private bool IsIPv4MappedToIPv6()
+ {
+ // If the IPAddress is an IPv4 mapped to IPv6, return the IPv4 representation instead.
+ // For example [::FFFF:127.0.0.1] will be transform to IPAddress of 127.0.0.1
+ if (_field1 != 0)
+ {
+ return false;
+ }
+
+ return (_field2 & 0xFFFFFFFF) == 0xFFFF0000;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvTcpHandle.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvTcpHandle.cs
index b43e9c985..cc568cc50 100644
--- a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvTcpHandle.cs
+++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvTcpHandle.cs
@@ -3,6 +3,7 @@
using System;
using System.Net;
+using System.Runtime.InteropServices;
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
namespace Microsoft.AspNet.Server.Kestrel.Networking
@@ -17,7 +18,7 @@ public void Init(UvLoopHandle loop)
{
CreateMemory(
loop.Libuv,
- loop.ThreadId,
+ loop.ThreadId,
loop.Libuv.handle_size(Libuv.HandleType.TCP));
_uv.tcp_init(loop, this);
@@ -26,7 +27,7 @@ public void Init(UvLoopHandle loop)
public void Init(UvLoopHandle loop, Action, IntPtr> queueCloseHandle)
{
CreateHandle(
- loop.Libuv,
+ loop.Libuv,
loop.ThreadId,
loop.Libuv.handle_size(Libuv.HandleType.TCP), queueCloseHandle);
@@ -37,7 +38,7 @@ public void Bind(ServerAddress address)
{
var endpoint = CreateIPEndpoint(address);
- Libuv.sockaddr addr;
+ SockAddr addr;
var addressText = endpoint.Address.ToString();
Exception error1;
@@ -56,6 +57,24 @@ public void Bind(ServerAddress address)
_uv.tcp_bind(this, ref addr, 0);
}
+ public IPEndPoint GetPeerIPEndPoint()
+ {
+ SockAddr socketAddress;
+ int namelen = Marshal.SizeOf();
+ _uv.tcp_getpeername(this, out socketAddress, ref namelen);
+
+ return socketAddress.GetIPEndPoint();
+ }
+
+ public IPEndPoint GetSockIPEndPoint()
+ {
+ SockAddr socketAddress;
+ int namelen = Marshal.SizeOf();
+ _uv.tcp_getsockname(this, out socketAddress, ref namelen);
+
+ return socketAddress.GetIPEndPoint();
+ }
+
public void Open(IntPtr hSocket)
{
_uv.tcp_open(this, hSocket);
diff --git a/test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/RequestTests.cs b/test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/RequestTests.cs
index ab6159cff..f90429cf9 100644
--- a/test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/RequestTests.cs
+++ b/test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/RequestTests.cs
@@ -9,6 +9,8 @@
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http;
using Microsoft.Extensions.Configuration;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
using Xunit;
namespace Microsoft.AspNet.Server.Kestrel.FunctionalTests
@@ -47,7 +49,7 @@ public async Task LargeUpload()
await context.Response.WriteAsync(total.ToString(CultureInfo.InvariantCulture));
});
- });
+ });
using (var app = hostBuilder.Build().Start())
{
@@ -58,7 +60,7 @@ public async Task LargeUpload()
{
bytes[i] = (byte)i;
}
-
+
var response = await client.PostAsync("http://localhost:8791/", new ByteArrayContent(bytes));
response.EnsureSuccessStatusCode();
var sizeString = await response.Content.ReadAsStringAsync();
@@ -66,5 +68,60 @@ public async Task LargeUpload()
}
}
}
+
+ [Theory]
+ [InlineData("127.0.0.1", "127.0.0.1", "8792")]
+ [InlineData("localhost", "127.0.0.1", "8792")]
+ public Task RemoteIPv4Address(string requestAddress, string expectAddress, string port)
+ {
+ return TestRemoteIPAddress("localhost", requestAddress, expectAddress, port);
+ }
+
+ [Fact]
+ public Task RemoteIPv6Address()
+ {
+ return TestRemoteIPAddress("[::1]", "[::1]", "::1", "8792");
+ }
+
+ private async Task TestRemoteIPAddress(string registerAddress, string requestAddress, string expectAddress, string port)
+ {
+ var config = new ConfigurationBuilder().AddInMemoryCollection(
+ new Dictionary {
+ { "server.urls", $"http://{registerAddress}:{port}" }
+ }).Build();
+
+ var builder = new WebHostBuilder(config)
+ .UseServer("Microsoft.AspNet.Server.Kestrel")
+ .UseStartup(app =>
+ {
+ app.Run(async context =>
+ {
+ var connection = context.Connection;
+ await context.Response.WriteAsync(JsonConvert.SerializeObject(new
+ {
+ RemoteIPAddress = connection.RemoteIpAddress?.ToString(),
+ RemotePort = connection.RemotePort,
+ LocalIPAddress = connection.LocalIpAddress?.ToString(),
+ LocalPort = connection.LocalPort,
+ IsLocal = connection.IsLocal
+ }));
+ });
+ });
+
+ using (var app = builder.Build().Start())
+ using (var client = new HttpClient())
+ {
+ var response = await client.GetAsync($"http://{requestAddress}:{port}/");
+ response.EnsureSuccessStatusCode();
+
+ var connectionFacts = await response.Content.ReadAsStringAsync();
+ Assert.NotEmpty(connectionFacts);
+
+ var facts = JsonConvert.DeserializeObject(connectionFacts);
+ Assert.Equal(expectAddress, facts["RemoteIPAddress"].Value());
+ Assert.NotEmpty(facts["RemotePort"].Value());
+ Assert.True(facts["IsLocal"].Value());
+ }
+ }
}
-}
+}
\ No newline at end of file
diff --git a/tools/Microsoft.AspNet.Server.Kestrel.GeneratedCode/FrameFeatureCollection.cs b/tools/Microsoft.AspNet.Server.Kestrel.GeneratedCode/FrameFeatureCollection.cs
index f8dcf8d92..62b8e1924 100644
--- a/tools/Microsoft.AspNet.Server.Kestrel.GeneratedCode/FrameFeatureCollection.cs
+++ b/tools/Microsoft.AspNet.Server.Kestrel.GeneratedCode/FrameFeatureCollection.cs
@@ -66,6 +66,7 @@ public static string GeneratedFile()
typeof(IHttpRequestFeature),
typeof(IHttpResponseFeature),
typeof(IHttpUpgradeFeature),
+ typeof(IHttpConnectionFeature)
};
return $@"