396 lines
14 KiB
C#
396 lines
14 KiB
C#
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;
|
|
}
|
|
|
|
|
|
}
|
|
} |