본문 바로가기

Programming Language/C&C++

윈도우에서 IP 주소 얻는 방법

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

윈도우에서 IP 주소 얻는 방법

- by Tapito


 인터넷으로 패킷을 주고 받기 위해서는 상대방과 자기 자신의 IP 주소를 알아야 합니다. 이 IP 주소는 세계 어디서나 식별 가능한 공인 IP일 수도 있고 같은 라우터에 물려있어야 식별 가능한 사설 IP일 수도 있습니다. 상대방의 IP 주소는 비교적 쉽게 얻을 수 있거나 이미 알고 있는 상태이기 때문에 문제가 되지 않지만 자기 자신의 IP를 얻는 방법은 약간 복잡합니다. 흔히 10.xxx.xxx.xxx, 172.16.xxx.xxx ~ 172.31.xxx.xxx, 192.168.xxx.xxx로 시작하는 주소는 사설 IP로서 같은 공유기에 물려있는 장치들 사이에서나 유효하며 이 범위가 아닌 IP가 아니더라도 같은 네트워크에 접속된 장치들에서만 유효한 사설 IP일 수 있습니다. 자기 자신의 IP를 얻기 위해서는 아래와 같이 시도해 볼 수 있습니다. 

1. ioctl 소켓 함수를 사용하기

 유닉스에서는 ioctl, 윈도우에서는 ioctlsocket 또는 WSAIoctl 함수입니다. 이 컴퓨터와 연결된 통신 장비들의 목록을 모두 가져와 그것들의 속성을 읽을 수 있는데 이 중 IP 주소도 포함되어 있습니다.

/* 예제 */
#pragma comment(lib, "ws2_32.lib")

#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <conio.h>

INTERFACE_INFO aInterfaceInfo[64];

int main()
{
	WSADATA wsaData; // WinSock 실행에 필요한 구조체
	SOCKET ipSocket; // 이 컴퓨터가 네트워크에서 식별되는 이름이 저장될 문자열 버퍼입니다.
	INTERFACE_INFO aInterfaceInfo[512]; // 이 컴퓨터에 장착된 네트워크 장치들의 목록을 저장할 버퍼입니다.
	DWORD dwInterfaceInfo; // 위 배열에서 실제 검색된 장치들의 개수입니다.
	DWORD dwIndex; // 배열 인덱스
	char szBuffer[128];

	WSAStartup(MAKEWORD(2, 2), &wsaData);

	/* 인터넷에 연결하는 소켓을 만듭니다. */
	printf("소켓을 만듭니다...\n");
	ipSocket = socket(AF_INET, SOCK_RAW, 0);

	/* ioctl 함수로 네트워크 장치들을 검색합니다. */
	/* WSAIoctl(소켓, SIO_GET_INTERFACE_LIST, 입력버퍼, 입력버퍼의 최대 바이트, 출력버퍼, 출력버퍼의 최대 바이트, 출력버퍼로 배출된 바이트 수, NULL, NULL) */
	/* 그 외 다른 옵션은 MSDN 참고 */
	printf("네트워크 장치를 검색합니다...\n");
	WSAIoctl(ipSocket, SIO_GET_INTERFACE_LIST, NULL, 0, &aInterfaceInfo, sizeof(aInterfaceInfo), &dwInterfaceInfo, NULL, NULL);
	printf("총 %d개 검색됨.\n", dwInterfaceInfo / sizeof(INTERFACE_INFO));

	for (dwIndex = 0; dwIndex < dwInterfaceInfo / sizeof(INTERFACE_INFO); dwIndex++)
	{
		printf(" -- 장치 %d -- \n", dwIndex);

		/* -- iiFlags 속성을 출력합니다. -- */
		printf("INTERFACE_INFO.iiFlags = %x (", aInterfaceInfo[dwIndex].iiFlags);
		/* IFF_UP 인터페이스가 작동중임을 나타냅니다. */
		if (aInterfaceInfo[dwIndex].iiFlags & IFF_UP)
		{
			printf("IFF_UP, ");
		}

		/* IFF_BROADCAST: 이 장치가 브로드캐스트를 지원함을 나타냅니다. */
		if (aInterfaceInfo[dwIndex].iiFlags & IFF_BROADCAST)
		{
			printf("IFF_BROADCAST, ");
		}

		/* IFF_LOOPBACK: 루프백(자기 자신에게 통신하는 것)이 가능함을 나타냅니다. */
		if (aInterfaceInfo[dwIndex].iiFlags & IFF_LOOPBACK)
		{
			printf("IFF_LOOPBACK, ");
		}

		/* IFF_POINTTOPOINT: 장치가 Point to Poing 링크를 지원함을 나타냅니다. */
		if (aInterfaceInfo[dwIndex].iiFlags & IFF_POINTTOPOINT)
		{
			printf("IFF_POINTTOPOINT, ");
		}

		/* IFF_MULTICAST: 장치가 멀티캐스트 가능함을 나타냅니다. */
		if (aInterfaceInfo[dwIndex].iiFlags & IFF_MULTICAST)
		{
			printf("IFF_MULTICAST");
		}
		printf(")\n");

		/* -- iiAddress 속성을 출력합니다. -- */
		switch (aInterfaceInfo[dwIndex].iiAddress.Address.sa_family) // IPv6인지 IPv4인지를 검사하여 출력을 다르게 합니다.
		{
		case AF_INET:
			inet_ntop(AF_INET, &aInterfaceInfo[dwIndex].iiAddress.AddressIn.sin_addr, szBuffer, sizeof(szBuffer));
			printf("INTERFACE_INFO.iiAddress.AddressIn.sin_family = %d (AF_INET)\n", (int)aInterfaceInfo[dwIndex].iiAddress.AddressIn.sin_family);
			printf("INTERFACE_INFO.iiAddress.AddressIn.sin_addr = %s\n", szBuffer);
			printf("INTERFACE_INFO.iiAddress.AddressIn.sin_port = %d\n", (int)aInterfaceInfo[dwIndex].iiAddress.AddressIn.sin_port);
			break;
		case AF_INET6:
			inet_ntop(AF_INET6, &aInterfaceInfo[dwIndex].iiAddress.AddressIn6.sin6_addr, szBuffer, sizeof(szBuffer));
			printf("INTERFACE_INFO.iiAddress.AddressIn.sin_family = %d (AF_INET6)\n", (int)aInterfaceInfo[dwIndex].iiAddress.AddressIn6.sin6_family);
			printf("INTERFACE_INFO.iiAddress.AddressIn.sin_addr = %s\n", szBuffer);
			printf("INTERFACE_INFO.iiAddress.AddressIn.sin_port = %d\n", (int)aInterfaceInfo[dwIndex].iiAddress.AddressIn6.sin6_port);
			break;
		}

		/* -- iiBroadcastAddress 속성을 출력합니다. -- */
		switch (aInterfaceInfo[dwIndex].iiBroadcastAddress.Address.sa_family)
		{
		case AF_INET:
			inet_ntop(AF_INET, &aInterfaceInfo[dwIndex].iiBroadcastAddress.AddressIn.sin_addr, szBuffer, sizeof(szBuffer));
			printf("INTERFACE_INFO.iiBroadcastAddress.AddressIn.sin_family = %d (AF_INET)\n", (int)aInterfaceInfo[dwIndex].iiBroadcastAddress.AddressIn.sin_family);
			printf("INTERFACE_INFO.iiBroadcastAddress.AddressIn.sin_addr = %s\n", szBuffer);
			printf("INTERFACE_INFO.iiBroadcastAddress.AddressIn.sin_port = %d\n", (int)aInterfaceInfo[dwIndex].iiBroadcastAddress.AddressIn.sin_port);
			break;
		case AF_INET6:
			inet_ntop(AF_INET6, &aInterfaceInfo[dwIndex].iiBroadcastAddress.AddressIn6.sin6_addr, szBuffer, sizeof(szBuffer));
			printf("INTERFACE_INFO.iiBroadcastAddress.AddressIn.sin_family = %d (AF_INET6)\n", (int)aInterfaceInfo[dwIndex].iiBroadcastAddress.AddressIn6.sin6_family);
			printf("INTERFACE_INFO.iiBroadcastAddress.AddressIn.sin_addr = %s\n", szBuffer);
			printf("INTERFACE_INFO.iiBroadcastAddress.AddressIn.sin_port = %d\n", (int)aInterfaceInfo[dwIndex].iiBroadcastAddress.AddressIn6.sin6_port);
			break;
		}

		/* -- iiNetmask 속성을 출력합니다. -- */
		switch (aInterfaceInfo[dwIndex].iiNetmask.Address.sa_family)
		{
		case AF_INET:
			inet_ntop(AF_INET, &aInterfaceInfo[dwIndex].iiNetmask.AddressIn.sin_addr, szBuffer, sizeof(szBuffer));
			printf("INTERFACE_INFO.iiNetmask.AddressIn.sin_family = %d (AF_INET)\n", (int)aInterfaceInfo[dwIndex].iiNetmask.AddressIn.sin_family);
			printf("INTERFACE_INFO.iiNetmask.AddressIn.sin_addr = %s\n", szBuffer);
			printf("INTERFACE_INFO.iiNetmask.AddressIn.sin_port = %d\n", (int)aInterfaceInfo[dwIndex].iiNetmask.AddressIn.sin_port);
			break;
		case AF_INET6:
			inet_ntop(AF_INET6, &aInterfaceInfo[dwIndex].iiNetmask.AddressIn6.sin6_addr, szBuffer, sizeof(szBuffer));
			printf("INTERFACE_INFO.iiNetmask.AddressIn.sin_family = %d (AF_INET6)\n", (int)aInterfaceInfo[dwIndex].iiNetmask.AddressIn6.sin6_family);
			printf("INTERFACE_INFO.iiNetmask.AddressIn.sin_addr = %s\n", szBuffer);
			printf("INTERFACE_INFO.iiNetmask.AddressIn.sin_port = %d\n", (int)aInterfaceInfo[dwIndex].iiNetmask.AddressIn6.sin6_port);
			break;
		}
	}

	closesocket(ipSocket);
	WSACleanup();

	printf("END");
	_getch();

	return 0;
}

 출력 결과는 아래와 같습니다. 이 컴퓨터에서 2개의 네트워크 장치를 발견하였고 공유기에 의한 사설 IP가 출력됨을 알 수 있습니다. 여기서 얻은 주소를 IP 패킷의 Source Address로 지정하면 네트워크를 거치며 공인 IP로 변환되기 때문에 상대방 컴퓨터에는 이 주소 대신 다른 주소가 보일 것입니다. 네트워크의 어딘가에서는 외부로 노출되는 IP와 자신이 다른 컴퓨터에게 할당한 10.100.xxx.xxx와 같은 사설 IP 사이의 대응 관계표를 보유하며 관리하는데 이러한 기능을 NAT라고 합니다.



2. gethostXXX 소켓 함수를 사용하기

 gethostname과 gethostbyname을 사용하여 IP 주소와 컴퓨터 이름을 얻을 수 있습니다. 이 때 얻어진 IP 주소 역시 공인 IP가 아닐 수 있습니다.

/* 예제 */
#pragma comment(lib, "ws2_32.lib") // WinSock 2 라이브러리를 사용 
#include <winsock2.h> // winsock 헤더
#include <windows.h> // 이하 잡다한 헤더
#include <stdio.h>
#include <string.h>
#include <conio.h>

/* IP 주소와 네트워크 식별 이름 등을 얻습니다. 오류 처리는 생략합니다. 실행 결과 공인 IP가 나오지 않을 수 있습니다. */
int main()
{
	WSADATA wsaData; // WinSock 실행에 필요한 구조체
	char szLocalHostName[512]; // 이 컴퓨터가 네트워크에서 식별되는 이름이 저장될 문자열 버퍼입니다.
	struct hostent * pLocalHostInformation; // 로컬 호스트의 정보가 담길 구조체의 포인터
	struct in_addr internetAddress; // IP 주소를 문자열로 변환하기 위한 구조체
	int i; // 배열 인덱스 변수

	/* WinSock을 시작합니다. */
	WSAStartup(MAKEWORD(1, 1), &wsaData);

	printf("로컬 호스트의 이름을 얻습니다...\n");
	gethostname(szLocalHostName, sizeof(szLocalHostName));
	printf("로컬 호스트의 이름은 \"%s\"입니다.\n\n", szLocalHostName);

	printf("로컬 호스트의 속성을 얻습니다...\n");
	pLocalHostInformation = gethostbyname(szLocalHostName);
	printf("로컬 호스트의 속성을 얻었습니다. 내용은 아래와 같습니다.\n\n");

	/* 이 컴퓨터의 식별 이름입니다. gethostname에서 얻은 문자열과 동일할 것입니다. */
	printf("hostent.h_name = %s\n", pLocalHostInformation->h_name);

	/* 이 컴퓨터의 식별 이름에 대한 별칭입니다. 없을 수도 있습니다. */
	for (i = 0; pLocalHostInformation->h_aliases[i] != NULL; i++)
		printf("hostent.h_aliases[%d] = %s\n", i, pLocalHostInformation->h_aliases[i]);

	/* 이 컴퓨터가 연결된 네트워크 주소의 유형입니다. */
	/* 통상 IPv4를 사용하는 인터넷 망이라면 (AF_INET)이 지정되어 있고 IPv6를 사용하는 인터넷 망이라면 (AF_INET6)가 지정될 것입니다. */
	/* IPv6에 관한 기능은 WinSock 2부터 지원합니다. */
	switch (pLocalHostInformation->h_addrtype)
	{
	case AF_INET:
		printf("hostent.h_addrtype = %d (AF_INET)\n", pLocalHostInformation->h_addrtype);
		break;
	case AF_INET6:
		printf("hostent.h_addrtype = %d (AF_INET6)\n", pLocalHostInformation->h_addrtype);
		break;
	default:
		printf("hostent.h_addrtype = %d (unknown)\n", pLocalHostInformation->h_addrtype);
		break;
	}

	/* IP 주소의 길이입니다. IPv4라면 4바이트, IPv6라면 16바이트입니다. */
	printf("hostent.h_length = %d\n", pLocalHostInformation->h_length);

	/* 한 컴퓨터에서 여러 IP를 할당 받을 수 있습니다. 이를 모두 출력합니다. */
	for (i = 0; pLocalHostInformation->h_addr_list[i] != NULL; i++)
	{
		printf("hostent.h_addr_list[%d] = %s\n", i, inet_ntoa(*(struct in_addr*)pLocalHostInformation->h_addr_list[i]));
	}

	/* WinSock 사용을 종료합니다. */
	WSACleanup();

	printf("END");
	_getch();

	return 0;
}

 실행 결과는 아래와 같습니다.

 ioctl로 얻은 IP와 동일함을 알 수 있습니다.

 

getsockname, getpeername을 이용하는 방법

 IP 주소를 알고 있는 다른 컴퓨터에 접속한 다음 그 소켓 정보를 이용해 주소를 구할 수 있습니다. getsockname의 자기 자신에 대한 정보를, getpeername은 상대방에 대한 정보를 얻습니다.

/* 예제 */
#pragma comment(lib, "ws2_32.lib")

#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <conio.h>

int main()
{
	WSADATA wsaData; // WinSock 실행에 필요한 구조체
	SOCKET ipSocket; // 이 컴퓨터가 네트워크에서 식별되는 이름이 저장될 문자열 버퍼입니다.
	DWORD dwInterfaceInfo; // 위 배열에서 실제 검색된 장치들의 개수입니다.
	DWORD dwIndex; // 배열 인덱스
	struct sockaddr_in sockaddr_info1, sockaddr_info2;
	int sockaddr_info_size = sizeof(struct sockaddr_in);
	char szBuffer[128];

	WSAStartup(MAKEWORD(2, 2), &wsaData);
	sockaddr_info1.sin_family = AF_INET;
	sockaddr_info1.sin_port = htons(4567);
	sockaddr_info1.sin_addr.s_addr = inet_addr("8.8.8.8");

	ipSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	connect(ipSocket, (struct sockaddr*)&sockaddr_info1, sizeof sockaddr_info1);
	getpeername(ipSocket, (struct sockaddr*)&sockaddr_info2, &sockaddr_info_size);
	printf("상대방 IP %s:%u\n", inet_ntoa(sockaddr_info2.sin_addr), ntohs(sockaddr_info2.sin_port));
	getsockname(ipSocket, (struct sockaddr*)&sockaddr_info2, &sockaddr_info_size);
	printf("내 IP %s:%u\n", inet_ntoa(sockaddr_info2.sin_addr), ntohs(sockaddr_info2.sin_port));
	closesocket(ipSocket);
	WSACleanup();

	printf("END");
	_getch();

	return 0;
}

 결론은 소켓에는 자기 자신의 IP 주소를 얻을 다양한 방법들이 존재하지만 이들 IP는 공인(public)일 수도 있고 사설(private)일 수도 있습니다. 패킷을 전송하더라도 바깥으로 노출되는 IP를 알고 있는 장치는 이 컴퓨터가 아니라 사설 IP들을 할당해주는 장치이기 때문입니다. 공인 IP를 알 수 있는 유일한 방법은 whatismyip.com과 같이 IP 주소를 알려주는 사이트에 직접 접속하여 보는 수밖에 없습니다. 소켓으로 굳이 구현한다면 이들 사이트로 HTTP 요청 패킷을 보낸다음 수신되는 HTML 소스 중에서 IP 주소만 읽는 식으로 구현하면 됩니다.

위의 주소가 이 컴퓨터의 공인 IP입니다. 일반적으로 통신사를 통해 특정 공인 IP를 직접 구매하지 않는 이상 이 주소는 접속할 때마다 달라질 수 있습니다.