Skip to content

Instantly share code, notes, and snippets.

@winzig
Last active June 26, 2021 17:54
Show Gist options
  • Save winzig/ee57e559341a2a92d8ee7bc0daa1304a to your computer and use it in GitHub Desktop.
Save winzig/ee57e559341a2a92d8ee7bc0daa1304a to your computer and use it in GitHub Desktop.
If you're using a load balancer that obscures the remote client's true IP address, you can run this IHttpModule to take the first IP address from the X-Forwarded-For header, and overwrite the REMOTE_ADDR and REMOTE_HOST server variables. (We tried to use the URL Rewrite module that everyone normally recommends to do this, but it's buggy.)
using System;
using System.Web;
using System.Text.RegularExpressions;
namespace HttpModules
{
/// <summary>
/// This module handles complications from our load balancer configuration not properly passing the client's true IP
/// address to our code via the REMOTE_ADDR and REMOTE_HOST variables. We tried to use URL Rewrite to compensate for
/// this, but it does not run when default documents are being accessed (a longstanding bug).
///
/// Add a reference to this class to your <modules> section in either applicationHost.config (for your entire server),
/// or the web.config of a specific web server.
/// </summary>
public class ClientIP : IHttpModule
{
/// <summary>
/// The IP we want will be in $1. X-Forwarded-For can carry multiple IPs in a comma-separated
/// list, but the first IP should belong to the original client.
/// </summary>
private static Regex REGEX_FIRST_IP = new Regex(@"^\s*(\d+\.\d+\.\d+\.\d+)", RegexOptions.Compiled);
/// <summary>
/// This overwrites the REMOTE_ADDR and REMOTE_HOST server variables with the first IP taken from the
/// X-Forwarded-For header, if it exists. Otherwise it does nothing.
/// </summary>
void OnBeginRequest(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
string headervalue = app.Context.Request.Headers["X-Forwarded-For"];
if (headervalue != null)
{
Match m = REGEX_FIRST_IP.Match(headervalue);
if (m.Success)
{
app.Context.Request.ServerVariables["REMOTE_ADDR"] = m.Groups[1].Value;
app.Context.Request.ServerVariables["REMOTE_HOST"] = m.Groups[1].Value;
}
}
}
/// <summary>
/// Put anything here that needs to happen when the module is disposed of.
/// </summary>
void IHttpModule.Dispose()
{
}
/// <summary>
/// This wires up our OnBeginRequest method to the BeginRequest event.
/// </summary>
/// <param name="app"></param>
void IHttpModule.Init(HttpApplication app)
{
app.BeginRequest += OnBeginRequest;
}
}
}
@winzig
Copy link
Author

winzig commented Feb 19, 2021

@opiums9 @itdap77 Sorry, I haven’t used IIS in over 3 years, so can’t remember the details, but the comments in the above code say:

Add a reference to this class to your <modules> section in either applicationHost.config (for your entire server), or the web.config of a specific web server.

@winzig
Copy link
Author

winzig commented Feb 19, 2021

Also this code likely should be updated to support IPv6 (the regex that matches the IP should be updated).

@itdap77
Copy link

itdap77 commented Feb 23, 2021

Do I need to create a DLL? as in the GlobalModules section there are dll file references.

@winzig
Copy link
Author

winzig commented Feb 23, 2021

@itdap77 I don't totally recall, but yeah I believe you create a DLL using the code above, and then you can register it with IIS. Check out this article as it seems to be kind of what I remember doing: https://docs.microsoft.com/en-us/iis/develop/runtime-extensibility/developing-iis-modules-and-handlers-with-the-net-framework

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment