YNICTE/Base/Lib/IPUtility.cs

396 lines
14 KiB
C#
Raw Permalink Normal View History

using System;
using System.Net;
using System.Linq;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Web;
using System.Net.Http;
using System.Threading.Tasks;
namespace NP.Base.Lib
{
/// <summary>
/// IP 주소 획득 유틸리티 클래스
/// </summary>
public static class IPUtility
{
/// <summary>
/// 클라이언트의 실제 IP 주소를 가져옵니다.
/// 컨트롤러나 Razor 뷰 어디서든 호출 가능합니다.
/// </summary>
/// <returns>클라이언트 IP 주소</returns>
public static string GetUserIP()
{
// 현재 HttpContext 가져오기 (컨트롤러나 Razor 뷰 모두에서 동작)
HttpContextBase httpContext = GetCurrentContext();
if (httpContext == null)
{
return "0.0.0.0"; // HttpContext를 얻을 수 없는 경우
}
return GetUserIPFromContext(httpContext);
}
/// <summary>
/// 주어진 HttpContext에서 클라이언트 IP를 추출합니다.
/// </summary>
/// <param name="httpContext">HttpContext 객체</param>
/// <returns>클라이언트 IP 주소</returns>
public static string GetUserIPFromContext(HttpContextBase httpContext)
{
if (httpContext == null)
return "0.0.0.0";
// IP를 수집할 리스트
List<string> potentialIPs = new List<string>();
// 1. 모든 서버 변수 확인
string[] headerKeys = new[]
{
"HTTP_X_FORWARDED_FOR",
"X-Forwarded-For",
"HTTP_X_REAL_IP",
"X-Real-IP",
"HTTP_CLIENT_IP",
"HTTP_X_FORWARDED",
"HTTP_X_CLUSTER_CLIENT_IP",
"HTTP_FORWARDED_FOR",
"HTTP_FORWARDED",
"HTTP_VIA",
"Proxy-Client-IP",
"WL-Proxy-Client-IP",
"CF-Connecting-IP", // Cloudflare
"True-Client-IP", // Akamai, Cloudflare
"X-Client-IP",
"REMOTE_ADDR"
};
try
{
// 서버 변수에서 IP 확인
foreach (string key in headerKeys)
{
try
{
string headerValue = httpContext.Request.ServerVariables[key];
if (!string.IsNullOrEmpty(headerValue))
{
// 쉼표로 구분된 여러 IP 처리
string[] addresses = headerValue.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string address in addresses)
{
string trimmedIP = address.Trim();
if (!string.IsNullOrEmpty(trimmedIP))
{
potentialIPs.Add(trimmedIP);
}
}
}
}
catch
{
// 특정 서버 변수에 접근할 수 없는 경우 계속 진행
continue;
}
}
// 2. Headers 컬렉션에서 확인
foreach (string key in headerKeys)
{
try
{
var headerValue = httpContext.Request.Headers[key];
if (!string.IsNullOrEmpty(headerValue))
{
string[] addresses = headerValue.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string address in addresses)
{
string trimmedIP = address.Trim();
if (!string.IsNullOrEmpty(trimmedIP))
{
potentialIPs.Add(trimmedIP);
}
}
}
}
catch
{
// 특정 헤더에 접근할 수 없는 경우 계속 진행
continue;
}
}
// 3. UserHostAddress 확인
try
{
string userHostAddress = httpContext.Request.UserHostAddress;
if (!string.IsNullOrEmpty(userHostAddress))
{
potentialIPs.Add(userHostAddress);
}
}
catch
{
// UserHostAddress에 접근할 수 없는 경우 무시
}
}
catch (Exception)
{
// 전체 처리 중 예외 발생 시 기본값 반환
return "0.0.0.0";
}
// 수집된 IP 검증 및 필터링
foreach (string ip in potentialIPs)
{
// 유효한 IP 확인
if (IsValidClientIP(ip))
{
return ip;
}
}
// 기본 IP (아무것도 찾지 못한 경우)
return "0.0.0.0";
}
/// <summary>
/// 현재 HttpContext를 가져옵니다.
/// 컨트롤러와 Razor 뷰 모두에서 동작합니다.
/// </summary>
/// <returns>현재 HttpContext</returns>
private static HttpContextBase GetCurrentContext()
{
// 컨트롤러 또는 Razor 뷰에서 접근 가능한 현재 HttpContext 가져오기
if (HttpContext.Current != null)
{
return new HttpContextWrapper(HttpContext.Current);
}
return null;
}
/// <summary>
/// IP 주소가 유효한지 확인합니다.
/// </summary>
/// <param name="ip">확인할 IP 주소</param>
/// <returns>유효한 경우 true, 그렇지 않으면 false</returns>
private static bool IsValidClientIP(string ip)
{
if (string.IsNullOrWhiteSpace(ip))
return false;
// 알려진 특수 IP 처리
if (ip.ToLower() == "unknown")
return false;
// 로컬 IP 허용 여부 (필요에 따라 수정)
if (ip == "::1" || ip == "127.0.0.1")
return true;
// 프라이빗 IP 필터링 여부 (필요에 따라 수정)
if (IsPrivateIP(ip))
return true; // 프라이빗 IP 허용
// 표준 IP 형식 검증
IPAddress address;
if (!IPAddress.TryParse(ip, out address))
return false;
// IP 기본 검증 - 비어있는 주소 거부
byte[] bytes = address.GetAddressBytes();
if (bytes.All(b => b == 0))
return false;
return true;
}
/// <summary>
/// 주어진 IP가 프라이빗 IP인지 확인합니다.
/// </summary>
/// <param name="ipAddress">확인할 IP 주소</param>
/// <returns>프라이빗 IP인 경우 true, 그렇지 않으면 false</returns>
private static bool IsPrivateIP(string ipAddress)
{
try
{
// IP 파싱
IPAddress address;
if (!IPAddress.TryParse(ipAddress, out address))
return false;
// IPv4 프라이빗 주소 범위 확인
if (address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{
byte[] bytes = address.GetAddressBytes();
// 10.0.0.0/8
if (bytes[0] == 10)
return true;
// 172.16.0.0/12
if (bytes[0] == 172 && bytes[1] >= 16 && bytes[1] <= 31)
return true;
// 192.168.0.0/16
if (bytes[0] == 192 && bytes[1] == 168)
return true;
// 169.254.0.0/16 (링크-로컬)
if (bytes[0] == 169 && bytes[1] == 254)
return true;
}
// IPv6 프라이빗 주소 체크
else if (address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
{
// IPv6 로컬 주소 확인 (fc00::/7)
byte[] bytes = address.GetAddressBytes();
return (bytes[0] & 0xFE) == 0xFC;
}
return false;
}
catch
{
return false;
}
}
/// <summary>
/// 외부 API를 사용하여 공인 IP 주소를 가져옵니다.
/// </summary>
/// <returns>공인 IP 주소</returns>
public static string GetPublicIP()
{
try
{
// 여러 IP 확인 서비스 목록
string[] ipServices = new[]
{
"https://api.ipify.org",
"https://icanhazip.com",
"https://ipinfo.io/ip",
"https://checkip.amazonaws.com",
"https://wtfismyip.com/text"
};
foreach (string service in ipServices)
{
try
{
using (var client = new WebClient())
{
string ip = client.DownloadString(service).Trim();
// IP 형식 검증
if (IsValidIPFormat(ip))
{
return ip;
}
}
}
catch
{
// 한 서비스가 실패하면 다음 서비스 시도
continue;
}
}
return "IP 확인 실패";
}
catch (Exception ex)
{
return "오류: " + ex.Message;
}
}
/// <summary>
/// 비동기 방식으로 공인 IP 주소를 가져옵니다.
/// </summary>
/// <returns>공인 IP 주소</returns>
public static async Task<string> GetPublicIPAsync()
{
try
{
// 여러 IP 확인 서비스 목록
string[] ipServices = new[]
{
"https://api.ipify.org",
"https://icanhazip.com",
"https://ipinfo.io/ip",
"https://checkip.amazonaws.com",
"https://wtfismyip.com/text"
};
foreach (string service in ipServices)
{
try
{
using (var client = new HttpClient())
{
client.Timeout = TimeSpan.FromSeconds(3); // 3초 타임아웃
string ip = (await client.GetStringAsync(service)).Trim();
// IP 형식 검증
if (IsValidIPFormat(ip))
{
return ip;
}
}
}
catch
{
// 한 서비스가 실패하면 다음 서비스 시도
continue;
}
}
return "IP 확인 실패";
}
catch (Exception ex)
{
return "오류: " + ex.Message;
}
}
/// <summary>
/// 문자열이 유효한 IP 형식인지 확인합니다.
/// </summary>
private static bool IsValidIPFormat(string ip)
{
if (string.IsNullOrWhiteSpace(ip))
return false;
// IPv4 형식 확인 (간단한 정규식)
string pattern = @"^([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3}$";
return Regex.IsMatch(ip, pattern);
}
/// <summary>
/// 클라이언트의 IP를 가져오되, 로컬 테스트 환경일 경우 공인 IP를 조회합니다.
/// </summary>
public static string GetClientIPWithFallback()
{
string ip = GetUserIP();
// 로컬 IP인 경우 공인 IP 조회 시도
if (ip == "127.0.0.1" || ip == "::1" || ip.StartsWith("192.168.") || ip.StartsWith("10.") || (ip.StartsWith("172.") && ip.Split('.').Length > 1 && int.Parse(ip.Split('.')[1]) >= 16 && int.Parse(ip.Split('.')[1]) <= 31))
{
string publicIP = GetPublicIP();
if (IsValidIPFormat(publicIP))
{
return publicIP;
}
}
return ip;
}
}
}