解決在啟用 IPv6 協定的伺服器中擷取用戶端 IP 問題

解決在啟用 IPv6 協定的伺服器中擷取用戶端 IP 問題

這幾天忙得昏天暗地之下遇到的狀況。

一如往常地,我們在某些網頁被瀏覽時會紀錄用戶端的連線位址,一般藉由讀取 HttpRequest.UserHostAddress 即可達成,例如底下程式碼:

{
    LiteralControl info = new LiteralControl();

    #region 回傳 Client 端 IP
    info.Text = String.Format(
        "<div style='text-align: center; font-family: Arial; font-weight: bold; '>" +
        "<h1>" +
        "Your IP Address is..." +
        "</h1>" +
        "<h1>" +
        "<span style='color: deeppink; font-style: italic;'>" +
        "{0}" +
        "</span>" +
        "</h1>" +
        "</div>",
        HttpContext.Current.Request.UserHostAddress);
    #endregion

    #region 回傳 Server 資訊
    info.Text += String.Format(
        "<div style='text-align: center; font-family: Arial; font-weight: bold; '>" +
        "<h1>" +
        "Server Name is..." +
        "</h1>" +
        "<h1>" +
        "<span style='color: deeppink; font-style: italic;'>" +
        "{0}" +
        "</span>" +
        "</h1>" +
        "</div>",
        HttpContext.Current.Request.ServerVariables["SERVER_NAME"]);

    info.Text += String.Format(
        "<div style='text-align: center; font-family: Arial; font-weight: bold; '>" +
        "<h1>" +
        "IIS Version is..." +
        "</h1>" +
        "<h1>" +
        "<span style='color: deeppink; font-style: italic;'>" +
        "{0}" +
        "</span>" +
        "</h1>" +
        "</div>",
        HttpContext.Current.Request.ServerVariables["SERVER_SOFTWARE"]);
    #endregion

    Page.Controls.Add(info);
}


預期的回傳畫面會像這樣:

ip-address-in-iis-6.0 

在 Windows Server 2003/IIS 6.0 之前的環境是沒有問題的,但同樣的程式碼拿到 Windows Server 2008/IIS 7.0 (含之後) 的環境後,取回的 IP 可能不是你熟悉的樣子:

ip-address-in-iis-7.0

這段詭異的字串是甚麼?…較熟的人可能知道這是 IPv6 位址,假如你的環境有啟用 IPv6 的話,開啟『命令提示字元』並輸入 ipconfig /all 指令就可以看到:

ipconfig-all

大概的方向可能是 IPv6 協定造成,接著便下關鍵字找到一篇說明,請參考:
Returning an IPv4 Address in an IPv6-Enabled Environment (簡中版)
原來 Windows Vista 首開預設啟用 IPv6 的先例 (),而在 IPv4、IPv6 兩者共存的環境下,IPv6 具有較高的優先權,因此回傳的 IP 位址就會以 IPv6 格式為主。好在為了解決這問題,不必刻意把 IPv6 關掉,連結中有提供解法,只不過我試 run 了一下發覺部分邏輯跟我預期的不一樣,稍作修正如下:

using System.Net;
using System.Web;

public class IPNetworking
{
    /// <summary>
    /// 取得客戶端主機 IPv4 位址
    /// </summary>
    /// <returns></returns>
    public static string GetClientIPv4()
    {
        string ipv4 = String.Empty;

        foreach (IPAddress ip in Dns.GetHostAddresses(GetClientIP()))
        {
            if (ip.AddressFamily.ToString() == "InterNetwork")
            {
                ipv4 = ip.ToString();
                break;
            }
        }

        if (ipv4 != String.Empty)
        {
            return ipv4;
        }

        // 原作使用 Dns.GetHostName 方法取回的是 Server 端資訊,非 Client 端。
        // 改寫為利用 Dns.GetHostEntry 方法,由獲取的 IPv6 位址反查 DNS 紀錄,
        // 再逐一判斷何者屬 IPv4 協定,即可轉為 IPv4 位址。
        foreach (IPAddress ip in Dns.GetHostEntry(GetClientIP()).AddressList)
        //foreach (IPAddress ip in Dns.GetHostAddresses(Dns.GetHostName()))
        {
            if (ip.AddressFamily.ToString() == "InterNetwork")
            {
                ipv4 = ip.ToString();
                break;
            }
        }

        return ipv4;
    }

    /// <summary>
    /// 取得客戶端主機位址
    /// </summary>
    public static string GetClientIP()
    {
        if (null == HttpContext.Current.Request.ServerVariables["HTTP_VIA"])
        {
            return HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"];
        }
        else
        {
            return HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
        }
    }
}


如註解所說,原始程式碼實際上回傳的是 Server 端 IP,不知是故意的還是怎樣,個人覺得很奇怪,所以小幅修改成我要的,順便因應可能設定代理伺服器的 issue,如此即便在最新的 Windows Server 2008 R2/IIS 7.5 環境下,也能夠順利取回 IPv4 位址:

ip-address-in-iis-7.5

最後要請大家注意一下,畢竟是用 DNS 反查,效果有待驗證,我猜會有查不到任何東西的時候 (回傳空值),也許這是原作乾脆用 Server 端 IP 代替的原因吧,若再考慮到 IPv6 慢慢會取代 IPv4 的話,本文的內容其實只能算是暫時應急之用,請自行斟酌服用囉,有任何想法歡迎留下回應讓我知道!



  • 註:Windows XP SP2 即正式支援 IPv6 協定,但預設並未啟用,而後推出的 Windows Vista 則針對 IPv6 的支援做了強化且預設啟用。這些資訊可參閱這篇 IPv6 Support in Microsoft Windows,該文介紹 Windows 95 以後的各個版本對 IPv6 協定的支援程度以及演進過程。