logo

English

이곳의 프로그래밍관련 정보와 소스는 마음대로 활용하셔도 좋습니다. 다만 쓰시기 전에 통보 정도는 해주시는 것이 예의 일것 같습니다. 질문이나 오류 수정은 siseong@gmail.com 으로 주세요. 감사합니다.

윈도우 프로그래밍 시 인터넷 통신사(ISP) 정보를 가져오는 코드

by lizard2019 posted Sep 01, 2025
?

Shortcut

PrevPrev Article

NextNext Article

Larger Font Smaller Font Up Down Go comment Print
?

Shortcut

PrevPrev Article

NextNext Article

Larger Font Smaller Font Up Down Go comment Print
윈도우 프로그래밍 시 인터넷 통신사(ISP)를 알아야하는 상황이 있습니다.
이럴때는 아래와 같은 코드를 사용하시면됩니다.
프로젝트는 c++17  이상을 사용해야합니다.

실행 결과

인터넷 통신사(ASN/조직명) 탐지 예제
공인 IPv4: 220.70.82.231
[경고] Team Cymru 조회 실패. 보조 경로(ipapi.co) 시도.

=== 조회 결과 ===
IP     : 220.70.82.231
ASN    : AS4766
운영자 : Korea Telecom
국가   : KR
출처   : ipapi.co

=> 통신사 추정: KT


소스 코드

// DetectISP.cpp
// 현재 인터넷 통신사(ASN/조직명) 추정: 공인 IP → Team Cymru DNS → (실패 시) ipapi.co
// DetectISP.cpp
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX

// ★ winsock2 계열을 windows.h 보다 먼저!
#include <winsock2.h>
#include <ws2tcpip.h>

#include <windows.h>
#include <winhttp.h>
#include <windns.h>
#include <cctype> // isdigit

#include <iostream>
#include <string>
#include <vector>
#include <optional>
#include <algorithm>

#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib, "winhttp.lib")
#pragma comment(lib, "dnsapi.lib")

// --- 문자열 유틸 ---
static inline std::string Trim(const std::string& s) {
    size_t b = s.find_first_not_of(" \t\r\n");
    size_t e = s.find_last_not_of(" \t\r\n");
    if (b == std::string::npos) return "";
    return s.substr(b, e - b + 1);
}
static inline std::vector<std::string> Split(const std::string& s, char sep) {
    std::vector<std::string> out;
    size_t pos = 0, p = 0;
    while ((p = s.find(sep, pos)) != std::string::npos) {
        out.push_back(s.substr(pos, p - pos));
        pos = p + 1;
    }
    out.push_back(s.substr(pos));
    return out;
}

// --- WinHTTP GET (본문을 그대로 std::string으로) ---
static std::optional<std::string> HttpGet(const wchar_t* host, const wchar_t* path, bool https = true, DWORD timeout_ms = 4000) {
    HINTERNET hSession = WinHttpOpen(L"DetectISP/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
        WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
    if (!hSession) return std::nullopt;

    WinHttpSetTimeouts(hSession, timeout_ms, timeout_ms, timeout_ms, timeout_ms);

    HINTERNET hConnect = WinHttpConnect(hSession, host, https ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT, 0);
    if (!hConnect) { WinHttpCloseHandle(hSession); return std::nullopt; }

    DWORD flags = https ? (WINHTTP_FLAG_ESCAPE_DISABLE | WINHTTP_FLAG_SECURE) : (WINHTTP_FLAG_ESCAPE_DISABLE);
    HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", path, nullptr,
        WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, flags);
    if (!hRequest) { WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return std::nullopt; }

    BOOL ok = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
        WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
    if (!ok) { WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return std::nullopt; }

    ok = WinHttpReceiveResponse(hRequest, nullptr);
    if (!ok) { WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return std::nullopt; }

    std::string body;
    DWORD avail = 0;
    do {
        if (!WinHttpQueryDataAvailable(hRequest, &avail)) break;
        if (!avail) break;
        std::string chunk;
        chunk.resize(avail);
        DWORD read = 0;
        if (!WinHttpReadData(hRequest, chunk.data(), avail, &read)) break;
        chunk.resize(read);
        body += chunk;
    } while (avail > 0);

    WinHttpCloseHandle(hRequest);
    WinHttpCloseHandle(hConnect);
    WinHttpCloseHandle(hSession);
    return body;
}

// --- 공인 IP 얻기 (IPv4 우선, 필요 시 IPv6 사용) ---
static std::optional<std::string> GetPublicIP(bool v6 = false) {
    auto body = HttpGet(v6 ? L"api64.ipify.org" : L"api.ipify.org", L"/?format=text", true, 4000);
    if (!body) return std::nullopt;
    std::string ip = Trim(*body);

    // 형식 검증
    if (!v6) {
        in_addr a4{};
        if (InetPtonA(AF_INET, ip.c_str(), &a4) == 1) return ip;
    }
    else {
        in6_addr a6{};
        if (InetPtonA(AF_INET6, ip.c_str(), &a6) == 1) return ip;
    }
    return std::nullopt;
}

static std::string W2U8(PCWSTR ws) {
    if (!ws) return {};
    int n = WideCharToMultiByte(CP_UTF8, 0, ws, -1, nullptr, 0, nullptr, nullptr);
    if (n <= 1) return {};
    std::string out; out.resize(n - 1);
    WideCharToMultiByte(CP_UTF8, 0, ws, -1, out.data(), n, nullptr, nullptr);
    return out;
}

static std::optional<std::string> DnsTxtQuery(const std::string& name) {
    PDNS_RECORD pRec = nullptr; // _DnsRecordW*
    DNS_STATUS st = DnsQuery_UTF8(
        name.c_str(),           // UTF-8 도메인
        DNS_TYPE_TEXT,
        DNS_QUERY_STANDARD,
        nullptr,
        &pRec,                  // PDNS_RECORD*
        nullptr
    );
    if (st != 0 || !pRec) return std::nullopt;

    std::string result;
    for (auto rr = pRec; rr; rr = rr->pNext) {
        if (rr->wType == DNS_TYPE_TEXT && rr->Data.TXT.dwStringCount > 0) {
            for (DWORD i = 0; i < rr->Data.TXT.dwStringCount; ++i) {
                if (rr->Data.TXT.pStringArray[i]) {
                    result += W2U8(rr->Data.TXT.pStringArray[i]); // PWSTR → UTF-8
                }
            }
            break; // 첫 TXT만 사용
        }
    }
    if (pRec) DnsRecordListFree(pRec, DnsFreeRecordList);
    if (result.empty()) return std::nullopt;
    return result;
}

// IPv4: "1.2.3.4" -> "4.3.2.1.origin.asn.cymru.com"
static std::string ReverseIPv4(const std::string& ip) {
    auto parts = Split(ip, '.');
    if (parts.size() != 4) return "";
    std::reverse(parts.begin(), parts.end());
    return parts[0] + "." + parts[1] + "." + parts[2] + "." + parts[3] + ".origin.asn.cymru.com";
}

// Team Cymru 응답 예(대표형): "AS12345 | 1.2.3.0/24 | KR | apnic | 2001-01-01 | ISP NAME"
// 변형에 대비하여 파이프(|) 분리 후 첫 토큰에서 숫자만 뽑음
static std::optional<std::string> ExtractASN(const std::string& txt) {
    auto fields = Split(txt, '|');
    if (fields.empty()) return std::nullopt;
    std::string f0 = Trim(fields[0]);
    // "AS12345" 또는 "12345" 형태
    // 숫자만 추출
    std::string asn;
    for (char c : f0) if (isdigit((unsigned char)c)) asn.push_back(c);
    if (asn.empty()) return std::nullopt;
    return asn;
}

// AS정보 TXT: "AS12345 | KR | apnic | 2001-01-01 | ISP NAME"
// → 마지막 필드를 ISP/조직명으로 사용
static std::optional<std::string> ExtractASName(const std::string& txt) {
    auto fields = Split(txt, '|');
    if (fields.size() < 2) return std::nullopt;
    std::string last = Trim(fields.back());
    return last.empty() ? std::optional<std::string>{} : std::optional<std::string>{ last };
}

struct IspInfo {
    std::string ip;
    std::string asn;     // e.g., "12345"
    std::string as_name; // e.g., "KT Corp" / "SK Broadband" 등
    std::string cc;      // 국가코드(있으면)
    std::string source;  // "Team Cymru DNS" or "ipapi.co"
};

static std::optional<IspInfo> LookupByTeamCymruIPv4(const std::string& ip) {
    IspInfo info; info.ip = ip; info.source = "Team Cymru DNS";

    std::string q1 = ReverseIPv4(ip);
    if (q1.empty()) return std::nullopt;

    auto txt1 = DnsTxtQuery(q1);
    if (!txt1) return std::nullopt;

    auto asn = ExtractASN(*txt1);
    if (!asn) return std::nullopt;
    info.asn = *asn;

    // AS세부 (이름/국가)
    std::string q2 = "AS" + info.asn + ".asn.cymru.com";
    auto txt2 = DnsTxtQuery(q2);
    if (txt2) {
        // 국가코드는 보통 2번째 필드, 이름은 마지막 필드
        auto fields = Split(*txt2, '|');
        if (fields.size() >= 2) info.cc = Trim(fields[1]);
        auto name = ExtractASName(*txt2);
        if (name) info.as_name = *name;
    }

    return info;
}

// 보조: ipapi.co로 ASN/ORG 가져오기 (무료/제한적; 실패해도 프로그램 계속)
static std::optional<IspInfo> FallbackByIpapi(const std::string& ip) {
    IspInfo info; info.ip = ip; info.source = "ipapi.co";
    std::wstring host = L"ipapi.co";
    std::wstring path = L"/";
    path += std::wstring(ip.begin(), ip.end());
    path += L"/json";

    auto body = HttpGet(host.c_str(), path.c_str(), true, 4000);
    if (!body) return std::nullopt;

    // 매우 단순한 파싱: "asn": "ASXXXX", "org": "ORG NAME"
    std::string s = *body;
    auto find_json_value = [&](const char* key) -> std::optional<std::string> {
        std::string k = std::string("\"") + key + "\"";
        size_t p = s.find(k);
        if (p == std::string::npos) return std::nullopt;
        p = s.find(':', p);
        if (p == std::string::npos) return std::nullopt;
        p = s.find('"', p);
        if (p == std::string::npos) return std::nullopt;
        size_t q = s.find('"', p + 1);
        if (q == std::string::npos) return std::nullopt;
        return s.substr(p + 1, q - (p + 1));
        };

    auto asn = find_json_value("asn");
    auto org = find_json_value("org");
    auto cc = find_json_value("country");

    if (!asn && !org) return std::nullopt;
    if (asn) {
        // "AS12345"에서 숫자만 추출
        std::string onlynum; for (char c : *asn) if (isdigit((unsigned char)c)) onlynum.push_back(c);
        info.asn = onlynum;
    }
    if (org) info.as_name = *org;
    if (cc) info.cc = *cc;
    return info;
}

int main() {
    std::cout << "인터넷 통신사(ASN/조직명) 탐지 예제\n";

    // 1) 우선 IPv4 공인 IP 시도
    auto ip4 = GetPublicIP(false);
    bool usedV6 = false;
    if (!ip4) {
        // IPv4 불가하면 IPv6 시도 (Team Cymru IPv6 처리까지 구현하려면 추가 코드 필요)
        auto ip6 = GetPublicIP(true);
        if (!ip6) {
            std::cerr << "[오류] 공인 IP를 가져오지 못했습니다.\n";
            return 1;
        }
        usedV6 = true;
        std::cout << "공인 IPv6: " << std::string(ip6->begin(), ip6->end()) << "\n";
        std::cerr << "[알림] 본 샘플은 IPv4 기반 Team Cymru 조회를 중심으로 합니다. IPv6 ASN 조회는 추가 구현이 필요합니다.\n";
        return 0;
    }

    std::cout << "공인 IPv4: " << std::string(ip4->begin(), ip4->end()) << "\n";

    // 2) Team Cymru로 ASN/이름 조회 (권장 루트)
    auto info = LookupByTeamCymruIPv4(*ip4);
    if (!info) {
        std::cerr << "[경고] Team Cymru 조회 실패. 보조 경로(ipapi.co) 시도.\n";
        info = FallbackByIpapi(*ip4);
    }

    if (!info) {
        std::cerr << "[오류] 통신사 정보를 확인하지 못했습니다.\n";
        return 2;
    }

    std::cout << "\n=== 조회 결과 ===\n";
    std::cout << "IP     : " << std::string(info->ip.begin(), info->ip.end()) << "\n";
    if (!info->asn.empty())     std::cout << "ASN    : AS" << std::string(info->asn.begin(), info->asn.end()) << "\n";
    if (!info->as_name.empty()) std::cout << "운영자 : " << std::string(info->as_name.begin(), info->as_name.end()) << "\n";
    if (!info->cc.empty())      std::cout << "국가   : " << std::string(info->cc.begin(), info->cc.end()) << "\n";
    std::cout << "출처   : " << std::string(info->source.begin(), info->source.end()) << "\n";

    // 간단한 통신사 추정(한국 예시) - 문자열 포함 여부로 라벨링
    std::string nameUp = info->as_name;
    std::transform(nameUp.begin(), nameUp.end(), nameUp.begin(), ::toupper);
    if (nameUp.find("KOREA TELECOM") != std::string::npos || nameUp.find("KT") != std::string::npos) {
        std::cout << "\n=> 통신사 추정: KT\n";
    }
    else if (nameUp.find("SK BROADBAND") != std::string::npos || nameUp.find("SK") != std::string::npos) {
        std::cout << "\n=> 통신사 추정: SK브로드밴드\n";
    }
    else if (nameUp.find("LG") != std::string::npos || nameUp.find("LG U") != std::string::npos || nameUp.find("LGD") != std::string::npos) {
        std::cout << "\n=> 통신사 추정: LG U+\n";
    }

    return 0;
}
  • ?
    lizard2019 2025.09.01 15:15
    방화벽/보안 DNS가 TXT를 차단하는 환경이면 DNS 경로 자체가 막힐 수 있습니다. 이때는 코드의 ipapi.co 폴백이 작동합니다.

List of Articles
No. Subject Author Date Views
115 프로세스 능력 성숙도 모델(CMMI)의 적용 digipine 2017.10.28 4965
114 프로그래밍 언어 순위 2023년 file digipine 2023.10.30 9122
113 포렌식을 활용한 정보보호 digipine 2017.11.02 5154
112 컴퓨터 프로그래밍에서 마샬링(Marshalling) 이란 lizard2019 2025.12.10 6359
111 초고속망 통신사 DNS 서버 주소 모음 - DNS 설정 digipine 2017.11.03 7235
110 임베디드SW 개발자센터 이용안내(성남시 분당구, 개발공간 무료제공) digipine 2017.11.02 4937
109 이벤트 텍소노미(Event Taxonomy)란 무엇인가요? digipine 2023.08.11 9358
108 유닉스/리눅스 명령어 레퍼런스 digipine 2017.11.03 5687
107 윈도우즈 도스 커멘드(Command) 네트워크 관련 명령어 lizard2019 2019.02.07 7378
106 윈도우에서 패스워드 입력 실패로 잠금 상태인지 확인 하는 방법 lizard2019 2024.11.05 17115
105 윈도우 한영 전환 쉬프트 스페이스로 변경 digipine 2017.11.03 4934
» 윈도우 프로그래밍 시 인터넷 통신사(ISP) 정보를 가져오는 코드 1 lizard2019 2025.09.01 13269
103 우분투 Nabi 한글 입력기 Tray(트레이) 상단 메뉴바로 옮기기 digipine 2017.11.03 6708
102 우분투 18.04 MongoDB 설치 및 구성 lizard2019 2021.02.26 5566
101 언어 IDE 별로 git ignore 파일을 자동으로 만들어 주는 사이트 엉뚱도마뱀 2018.12.17 137322
100 악성코드 종류 구분 digipine 2017.11.13 5578
99 수학적 구조물 모델링 만들기 소개 비디오 엉뚱도마뱀 2018.09.24 5351
98 소프트웨어 테스팅 전문가들을 위한 사이트 digipine 2017.11.02 5218
97 비밀번호 해쉬에 Salt(소금) 첨가하기 file 엉뚱도마뱀 2017.11.23 9914
96 리눅스 커널의 Swap Memory에 대해서 digipine 2017.11.02 6274
Board Pagination Prev 1 2 3 4 5 6 Next
/ 6