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 { /// /// IP 주소 획득 유틸리티 클래스 /// public static class IPUtility { /// /// 클라이언트의 실제 IP 주소를 가져옵니다. /// 컨트롤러나 Razor 뷰 어디서든 호출 가능합니다. /// /// 클라이언트 IP 주소 public static string GetUserIP() { // 현재 HttpContext 가져오기 (컨트롤러나 Razor 뷰 모두에서 동작) HttpContextBase httpContext = GetCurrentContext(); if (httpContext == null) { return "0.0.0.0"; // HttpContext를 얻을 수 없는 경우 } return GetUserIPFromContext(httpContext); } /// /// 주어진 HttpContext에서 클라이언트 IP를 추출합니다. /// /// HttpContext 객체 /// 클라이언트 IP 주소 public static string GetUserIPFromContext(HttpContextBase httpContext) { if (httpContext == null) return "0.0.0.0"; // IP를 수집할 리스트 List potentialIPs = new List(); // 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"; } /// /// 현재 HttpContext를 가져옵니다. /// 컨트롤러와 Razor 뷰 모두에서 동작합니다. /// /// 현재 HttpContext private static HttpContextBase GetCurrentContext() { // 컨트롤러 또는 Razor 뷰에서 접근 가능한 현재 HttpContext 가져오기 if (HttpContext.Current != null) { return new HttpContextWrapper(HttpContext.Current); } return null; } /// /// IP 주소가 유효한지 확인합니다. /// /// 확인할 IP 주소 /// 유효한 경우 true, 그렇지 않으면 false 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; } /// /// 주어진 IP가 프라이빗 IP인지 확인합니다. /// /// 확인할 IP 주소 /// 프라이빗 IP인 경우 true, 그렇지 않으면 false 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; } } /// /// 외부 API를 사용하여 공인 IP 주소를 가져옵니다. /// /// 공인 IP 주소 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; } } /// /// 비동기 방식으로 공인 IP 주소를 가져옵니다. /// /// 공인 IP 주소 public static async Task 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; } } /// /// 문자열이 유효한 IP 형식인지 확인합니다. /// 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); } /// /// 클라이언트의 IP를 가져오되, 로컬 테스트 환경일 경우 공인 IP를 조회합니다. /// 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; } } }