diff --git a/src/Shared/LayoutRenderers/AspNetRequestIpLayoutRenderer.cs b/src/Shared/LayoutRenderers/AspNetRequestIpLayoutRenderer.cs
index df7a843d..9731fc6f 100644
--- a/src/Shared/LayoutRenderers/AspNetRequestIpLayoutRenderer.cs
+++ b/src/Shared/LayoutRenderers/AspNetRequestIpLayoutRenderer.cs
@@ -1,7 +1,5 @@
-using System;
-using System.ComponentModel;
+using System.ComponentModel;
using System.Text;
-using NLog.Config;
using NLog.LayoutRenderers;
using NLog.Layouts;
using NLog.Web.Internal;
@@ -17,7 +15,11 @@ namespace NLog.Web.LayoutRenderers
/// ASP.NET Request IP address of the remote client
///
///
- /// ${aspnet-request-ip}
+ /// ${aspnet-request-ip}
to return the Remote IP
+ /// ${aspnet-request-ip:CheckForwardedForHeader=true}
to return first element in the X-Forwarded-For header
+ /// ${aspnet-request-ip:CheckForwardedForHeaderOffset=1}
to return second element in the X-Forwarded-For header
+ /// ${aspnet-request-ip:CheckForwardedForHeaderOffset=-1}
to return last element in the X-Forwarded-For header
+ /// ${aspnet-request-ip:CheckForwardedForHeaderOffset=1:ForwardedForHeader=myHeader}
to return second element in the myHeader header
///
/// Documentation on NLog Wiki
[LayoutRenderer("aspnet-request-ip")]
@@ -35,6 +37,24 @@ public class AspNetRequestIpLayoutRenderer : AspNetLayoutRendererBase
///
public bool CheckForwardedForHeader { get; set; }
+ ///
+ /// Gets or sets the array index of the X-Forwarded-For header to use, if the desired client IP is not at
+ /// the zeroth index. Defaults to zero. If the index is too large the last array element is returned instead.
+ /// If a negative index is used, this is used as the position from the end of the array.
+ /// Minus one will indicate the last element in the array. If the negative index is too large the first index
+ /// of the array is returned instead.
+ ///
+ public int CheckForwardedForHeaderOffset
+ {
+ get => _checkForwardedForHeaderOffset;
+ set
+ {
+ _checkForwardedForHeaderOffset = value;
+ CheckForwardedForHeader = true;
+ }
+ }
+ private int _checkForwardedForHeaderOffset;
+
///
protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
{
@@ -60,6 +80,25 @@ protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
builder.Append(ip);
}
+ private int CalculatePosition(string[] headerContents)
+ {
+ var position = CheckForwardedForHeaderOffset;
+
+ if (position < 0)
+ {
+ position = headerContents.Length + position;
+ }
+ if (position < 0)
+ {
+ position = 0;
+ }
+ if (position >= headerContents.Length)
+ {
+ position = headerContents.Length - 1;
+ }
+ return position;
+ }
+
#if !ASP_NET_CORE
string TryLookupForwardHeader(HttpRequestBase httpRequest, LogEventInfo logEvent)
{
@@ -71,7 +110,8 @@ string TryLookupForwardHeader(HttpRequestBase httpRequest, LogEventInfo logEvent
var addresses = forwardedHeader.Split(',');
if (addresses.Length > 0)
{
- return addresses[0];
+ var position = CalculatePosition(addresses);
+ return addresses[position]?.Trim();
}
}
@@ -86,7 +126,8 @@ private string TryLookupForwardHeader(HttpRequest httpRequest, LogEventInfo logE
var forwardedHeaders = httpRequest.Headers.GetCommaSeparatedValues(headerName);
if (forwardedHeaders.Length > 0)
{
- return forwardedHeaders[0];
+ var position = CalculatePosition(forwardedHeaders);
+ return forwardedHeaders[position]?.Trim();
}
}
diff --git a/tests/Shared/LayoutRenderers/AspNetRequestIpLayoutRendererTests.cs b/tests/Shared/LayoutRenderers/AspNetRequestIpLayoutRendererTests.cs
index ea342000..9d33dc8c 100644
--- a/tests/Shared/LayoutRenderers/AspNetRequestIpLayoutRendererTests.cs
+++ b/tests/Shared/LayoutRenderers/AspNetRequestIpLayoutRendererTests.cs
@@ -116,5 +116,117 @@ public void ForwardedForHeaderPresentWithCustomRenderForwardedValue()
// Assert
Assert.Equal("127.0.0.1", result);
}
+
+ [Fact]
+ public void ForwardedForHeaderContainsMultipleEntriesRendersIndexValue()
+ {
+ // Arrange
+ var (renderer, httpContext) = CreateWithHttpContext();
+
+#if !ASP_NET_CORE
+ httpContext.Request.ServerVariables.Returns(new NameValueCollection {{"REMOTE_ADDR", "192.0.0.0"}});
+ httpContext.Request.Headers.Returns(
+ new NameValueCollection {{ForwardedForHeader, "192.168.1.1, 127.0.0.1"}});
+#else
+ var headers = new HeaderDict();
+ headers.Add(ForwardedForHeader, new StringValues("192.168.1.1, 127.0.0.1"));
+ httpContext.Request.Headers.Returns(callinfo => headers);
+#endif
+ renderer.CheckForwardedForHeaderOffset = 1;
+
+ // Act
+ string result = renderer.Render(new LogEventInfo());
+
+ // Assert
+ Assert.Equal("127.0.0.1", result);
+ }
+
+ [Fact]
+ public void ForwardedForHeaderContainsMultipleEntriesRendersLastValue()
+ {
+ // Arrange
+ var (renderer, httpContext) = CreateWithHttpContext();
+
+#if !ASP_NET_CORE
+ httpContext.Request.ServerVariables.Returns(new NameValueCollection {{"REMOTE_ADDR", "192.0.0.0"}});
+ httpContext.Request.Headers.Returns(
+ new NameValueCollection {{ForwardedForHeader, "192.168.1.1, 127.0.0.1"}});
+#else
+ var headers = new HeaderDict();
+ headers.Add(ForwardedForHeader, new StringValues("192.168.1.1, 127.0.0.1"));
+ httpContext.Request.Headers.Returns(callinfo => headers);
+#endif
+ renderer.CheckForwardedForHeaderOffset = -1;
+
+ // Act
+ string result = renderer.Render(new LogEventInfo());
+
+ // Assert
+ Assert.Equal("127.0.0.1", result);
+ }
+
+ [Fact]
+ public void ForwardedForHeaderContainsMultipleEntriesExcessiveIndexRendersLastValue()
+ {
+ // Arrange
+ var (renderer, httpContext) = CreateWithHttpContext();
+
+#if !ASP_NET_CORE
+ httpContext.Request.ServerVariables.Returns(new NameValueCollection {{"REMOTE_ADDR", "192.0.0.0"}});
+ httpContext.Request.Headers.Returns(
+ new NameValueCollection {{ForwardedForHeader, "192.168.1.1, 127.0.0.1"}});
+#else
+ var headers = new HeaderDict();
+ headers.Add(ForwardedForHeader, new StringValues("192.168.1.1, 127.0.0.1"));
+ httpContext.Request.Headers.Returns(callinfo => headers);
+#endif
+ renderer.CheckForwardedForHeaderOffset = 2;
+
+ // Act
+ string result = renderer.Render(new LogEventInfo());
+
+ // Assert
+ Assert.Equal("127.0.0.1", result);
+
+ renderer.CheckForwardedForHeaderOffset = 3;
+
+ // Act
+ result = renderer.Render(new LogEventInfo());
+
+ // Assert
+ Assert.Equal("127.0.0.1", result);
+ }
+
+ [Fact]
+ public void ForwardedForHeaderContainsMultipleEntriesExcessiveNegativeIndexRendersFirstValue()
+ {
+ // Arrange
+ var (renderer, httpContext) = CreateWithHttpContext();
+
+#if !ASP_NET_CORE
+ httpContext.Request.ServerVariables.Returns(new NameValueCollection {{"REMOTE_ADDR", "192.0.0.0"}});
+ httpContext.Request.Headers.Returns(
+ new NameValueCollection {{ForwardedForHeader, "127.0.0.1, 192.168.1.1"}});
+#else
+ var headers = new HeaderDict();
+ headers.Add(ForwardedForHeader, new StringValues("127.0.0.1, 192.168.1.1"));
+ httpContext.Request.Headers.Returns(callinfo => headers);
+#endif
+ renderer.CheckForwardedForHeaderOffset = -3;
+
+ // Act
+ string result = renderer.Render(new LogEventInfo());
+
+ // Assert
+ Assert.Equal("127.0.0.1", result);
+
+ renderer.CheckForwardedForHeaderOffset = -4;
+
+ // Act
+ result = renderer.Render(new LogEventInfo());
+
+ // Assert
+ Assert.Equal("127.0.0.1", result);
+ }
}
}
\ No newline at end of file