解決在啟用 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);
}
預期的回傳畫面會像這樣:
在 Windows Server 2003/IIS 6.0 之前的環境是沒有問題的,但同樣的程式碼拿到 Windows Server 2008/IIS 7.0 (含之後) 的環境後,取回的 IP 可能不是你熟悉的樣子:
這段詭異的字串是甚麼?…較熟的人可能知道這是 IPv6 位址,假如你的環境有啟用 IPv6 的話,開啟『命令提示字元』並輸入 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 位址:
最後要請大家注意一下,畢竟是用 DNS 反查,效果有待驗證,我猜會有查不到任何東西的時候 (回傳空值),也許這是原作乾脆用 Server 端 IP 代替的原因吧,若再考慮到 IPv6 慢慢會取代 IPv4 的話,本文的內容其實只能算是暫時應急之用,請自行斟酌服用囉,有任何想法歡迎留下回應讓我知道!
- 註:Windows XP SP2 即正式支援 IPv6 協定,但預設並未啟用,而後推出的 Windows Vista 則針對 IPv6 的支援做了強化且預設啟用。這些資訊可參閱這篇 IPv6 Support in Microsoft Windows,該文介紹 Windows 95 以後的各個版本對 IPv6 協定的支援程度以及演進過程。