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 컴퓨터 프로그래밍에서 마샬링(Marshalling) 이란 lizard2019 2025.12.10 109
» 윈도우 프로그래밍 시 인터넷 통신사(ISP) 정보를 가져오는 코드 1 lizard2019 2025.09.01 7081
113 윈도우에서 패스워드 입력 실패로 잠금 상태인지 확인 하는 방법 lizard2019 2024.11.05 10644
112 OpenSSL Build for Windows digipine 2024.08.30 8199
111 Rapid JSON 간단 사용법 digipine 2024.08.27 9093
110 Direct X 11에서 그래픽 카드의 정보 가져오는 예제 digipine 2024.08.27 8520
109 Python Slack 메시지 발송하는 예제 digipine 2024.08.27 3372
108 Python email 보내는 예제 코드 digipine 2024.08.27 2606
107 UDP 핀홀 트래버설 과정 요약, UDP pinhole traversal digipine 2024.08.08 2488
106 NAT 상태에서 P2P 통신하는 방법 digipine 2024.08.08 2448
105 Windows Visual Studio 2022 OpenSSL Build 방법 1 digipine 2024.05.02 5745
104 Visual Studio 단축키 정리 digipine 2024.03.28 2011
103 프로그래밍 언어 순위 2023년 file digipine 2023.10.30 2886
102 이벤트 텍소노미(Event Taxonomy)란 무엇인가요? digipine 2023.08.11 2962
101 Bitbucket에서 SSH 키 등록하고 사용하는 방법 (맥/리눅스) file lizard2019 2023.06.22 6760
100 FFServer RTSP Audio Server Config digipine 2023.05.12 3006
99 OBS Studio for Http Interface EXE lizard2019 2023.02.15 2985
98 xcode xib encountered an error communicating with ibagent-ios 해결 digipine 2022.10.06 3742
97 XCode 사용시 git ignore 로 xcuserstate 충돌 해결하기, .gitignore에 등록했는데도 동작안할때 해결방법 lizard2019 2022.09.25 3512
96 MAC Screen Sharing을 위한 VNC 접속을 위한 Port 변경 방법 digipine 2022.09.05 4020
Board Pagination Prev 1 2 3 4 5 6 Next
/ 6