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 $@"