From 8ee803d255650b341fb4c278480a54ea308e4bf5 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Wed, 18 Nov 2015 10:27:55 -0800 Subject: [PATCH] Handle Remote header with ip and port correctly --- .../IISPlatformHandlerMiddleware.cs | 17 ++++- .../IPAddressWithPortParser.cs | 73 +++++++++++++++++++ .../HttpPlatformHandlerMiddlewareTests.cs | 36 ++++++++- .../IPAddressWithPortParserTests.cs | 49 +++++++++++++ 4 files changed, 170 insertions(+), 5 deletions(-) create mode 100644 src/Microsoft.AspNet.IISPlatformHandler/IPAddressWithPortParser.cs create mode 100644 test/Microsoft.AspNet.IISPlatformHandler.Tests/IPAddressWithPortParserTests.cs diff --git a/src/Microsoft.AspNet.IISPlatformHandler/IISPlatformHandlerMiddleware.cs b/src/Microsoft.AspNet.IISPlatformHandler/IISPlatformHandlerMiddleware.cs index 4750749666..c8c7cb0a29 100644 --- a/src/Microsoft.AspNet.IISPlatformHandler/IISPlatformHandlerMiddleware.cs +++ b/src/Microsoft.AspNet.IISPlatformHandler/IISPlatformHandlerMiddleware.cs @@ -23,6 +23,7 @@ namespace Microsoft.AspNet.IISPlatformHandler private const string XForwardedForHeaderName = "X-Forwarded-For"; private const string XForwardedProtoHeaderName = "X-Forwarded-Proto"; private const string XIISWindowsAuthToken = "X-IIS-WindowsAuthToken"; + private const string XOriginalPortName = "X-Original-Port"; private const string XOriginalProtoName = "X-Original-Proto"; private const string XOriginalIPName = "X-Original-IP"; @@ -83,14 +84,24 @@ namespace Microsoft.AspNet.IISPlatformHandler if (xForwardedForHeaderValue != null && xForwardedForHeaderValue.Length > 0) { IPAddress ipFromHeader; - if (IPAddress.TryParse(xForwardedForHeaderValue[0], out ipFromHeader)) + int? port; + if (IPAddressWithPortParser.TryParse(xForwardedForHeaderValue[0], out ipFromHeader, out port)) { - var remoteIPString = httpContext.Connection.RemoteIpAddress?.ToString(); + var connection = httpContext.Connection; + var remoteIPString = connection.RemoteIpAddress?.ToString(); if (!string.IsNullOrEmpty(remoteIPString)) { httpContext.Request.Headers[XOriginalIPName] = remoteIPString; } - httpContext.Connection.RemoteIpAddress = ipFromHeader; + if (port.HasValue) + { + if (connection.RemotePort != 0) + { + httpContext.Request.Headers[XOriginalPortName] = connection.RemotePort.ToString(CultureInfo.InvariantCulture); + } + connection.RemotePort = port.Value; + } + connection.RemoteIpAddress = ipFromHeader; } } } diff --git a/src/Microsoft.AspNet.IISPlatformHandler/IPAddressWithPortParser.cs b/src/Microsoft.AspNet.IISPlatformHandler/IPAddressWithPortParser.cs new file mode 100644 index 0000000000..0c1ceeb400 --- /dev/null +++ b/src/Microsoft.AspNet.IISPlatformHandler/IPAddressWithPortParser.cs @@ -0,0 +1,73 @@ +// 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.Net; + +namespace Microsoft.AspNet.IISPlatformHandler +{ + public static class IPAddressWithPortParser + { + public static bool TryParse(string addressWithPort, out IPAddress address, out int? port) + { + port = null; + + string addressPart = null; + string portPart = null; + + var lastColonIndex = addressWithPort.LastIndexOf(':'); + if (lastColonIndex > 0) + { + // IPv4 with port or IPv6 + var closingIndex = addressWithPort.LastIndexOf(']'); + if (closingIndex > 0) + { + // IPv6 with brackets + addressPart = addressWithPort.Substring(1, closingIndex - 1); + if (closingIndex < lastColonIndex) + { + // IPv6 with port [::1]:80 + portPart = addressWithPort.Substring(lastColonIndex + 1); + } + } + else + { + // IPv6 without port or IPv4 + var firstColonIndex = addressWithPort.IndexOf(':'); + if (firstColonIndex != lastColonIndex) + { + // IPv6 ::1 + addressPart = addressWithPort; + } + else + { + // IPv4 with port 127.0.0.1:123 + addressPart = addressWithPort.Substring(0, firstColonIndex); + portPart = addressWithPort.Substring(firstColonIndex + 1); + } + } + } + else + { + // IPv4 without port + addressPart = addressWithPort; + } + + var success = IPAddress.TryParse(addressPart, out address); + if (success && portPart != null) + { + int portValue; + success &= int.TryParse(portPart, out portValue); + if (success) + { + port = portValue; + } + else + { + // we cannot parse port, reset address + address = null; + } + } + return success; + } + } +} diff --git a/test/Microsoft.AspNet.IISPlatformHandler.Tests/HttpPlatformHandlerMiddlewareTests.cs b/test/Microsoft.AspNet.IISPlatformHandler.Tests/HttpPlatformHandlerMiddlewareTests.cs index d69fdd2298..c08d7544b9 100644 --- a/test/Microsoft.AspNet.IISPlatformHandler.Tests/HttpPlatformHandlerMiddlewareTests.cs +++ b/test/Microsoft.AspNet.IISPlatformHandler.Tests/HttpPlatformHandlerMiddlewareTests.cs @@ -1,6 +1,7 @@ // 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.Net; using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNet.Builder; @@ -12,7 +13,7 @@ namespace Microsoft.AspNet.IISPlatformHandler public class HttpPlatformHandlerMiddlewareTests { [Fact] - public async Task XForwardedForOverrideChangesRemoteIp() + public async Task XForwardedForOverrideChangesRemoteIpAndPort() { var assertsExecuted = false; @@ -22,13 +23,43 @@ namespace Microsoft.AspNet.IISPlatformHandler app.Run(context => { Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString()); + Assert.Equal(123, context.Connection.RemotePort); assertsExecuted = true; return Task.FromResult(0); }); }); var req = new HttpRequestMessage(HttpMethod.Get, ""); - req.Headers.Add("X-Forwarded-For", "11.111.111.11"); + req.Headers.Add("X-Forwarded-For", "11.111.111.11:123"); + await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); + } + + [Fact] + public async Task XForwardedForStoresOriginalIpAndPort() + { + var assertsExecuted = false; + + var server = TestServer.Create(app => + { + app.Use((context, next) => + { + context.Connection.RemoteIpAddress = IPAddress.Loopback; + context.Connection.RemotePort = 1; + return next(); + }); + app.UseIISPlatformHandler(); + app.Run(context => + { + Assert.Equal("127.0.0.1", context.Request.Headers["X-Original-IP"]); + Assert.Equal("1", context.Request.Headers["X-Original-Port"]); + assertsExecuted = true; + return Task.FromResult(0); + }); + }); + + var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.Add("X-Forwarded-For", "11.111.111.11:123"); await server.CreateClient().SendAsync(req); Assert.True(assertsExecuted); } @@ -44,6 +75,7 @@ namespace Microsoft.AspNet.IISPlatformHandler app.Run(context => { Assert.Null(context.Connection.RemoteIpAddress); + Assert.Equal(0, context.Connection.RemotePort); assertsExecuted = true; return Task.FromResult(0); }); diff --git a/test/Microsoft.AspNet.IISPlatformHandler.Tests/IPAddressWithPortParserTests.cs b/test/Microsoft.AspNet.IISPlatformHandler.Tests/IPAddressWithPortParserTests.cs new file mode 100644 index 0000000000..83ca511911 --- /dev/null +++ b/test/Microsoft.AspNet.IISPlatformHandler.Tests/IPAddressWithPortParserTests.cs @@ -0,0 +1,49 @@ +// 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.Net; +using Microsoft.AspNet.IISPlatformHandler; +using Xunit; + +namespace Microsoft.AspNet.PipelineHandler.Tests +{ + public class IPAddressWithPortParserTests + { + [Theory] + [InlineData("127.0.0.1", "127.0.0.1", null)] + [InlineData("127.0.0.1:1", "127.0.0.1", 1)] + [InlineData("1", "0.0.0.1", null)] + [InlineData("1:1", "0.0.0.1", 1)] + [InlineData("::1", "::1", null)] + [InlineData("[::1]", "::1", null)] + [InlineData("[::1]:1", "::1", 1)] + public void ParsesCorrectly(string input, string expectedAddress, int? expectedPort) + { + IPAddress address; + int? port; + var success = IPAddressWithPortParser.TryParse(input, out address, out port); + Assert.True(success); + Assert.Equal(expectedAddress, address?.ToString()); + Assert.Equal(expectedPort, port); + } + + [InlineData("[::1]:")] + [InlineData("[::1:")] + [InlineData("::1:")] + [InlineData("127:")] + [InlineData("127.0.0.1:")] + [InlineData("")] + [InlineData("[]")] + [InlineData("]")] + [InlineData("]:1")] + public void ShouldNotParse(string input) + { + IPAddress address; + int? port; + var success = IPAddressWithPortParser.TryParse(input, out address, out port); + Assert.False(success); + Assert.Equal(null, address); + Assert.Equal(null, port); + } + } +}