도메인 이름(domain name)이란?
TCP/IP 프로토콜은 내부적으로 숫자 형태의 IP 주소를 기반으로 동작하기 때문에 사용자가 입력한 도메인 이름을 반드시 IP 주소로 변환해야 한다. 아래 그림은 'ping' 프로그램에 입력한 도메인 이름이 IPv4 또는 IPv6 주소로 변환되는 것을 보여준다.(윈도우 XP에서는 'ping' 프로그램이 '-6' 옵션을 제공하지 않는다.)
(3-10 그림)
도메인 이름과 IP 주소의 변환 정보는 인터넷에 존재하는 여러 도메인 이름 서버(DNS Server; Domain Name System Server)가 관리하며, 어느 한 도메인 이름 서버가 모든 것을 갖고 있지 않다는 점에서 일종의 분산 데이터베이스(distributed database)라고 할 수 있다.
응용 프로그램이 도메인 이름과 IP 주소를 상호 변환할 수 있도록 다음과 같은 윈속 함수가 제공된다.
/* 도메인 이름 → IP 주소(네트워크 바이트 정렬) */
struct hostent *gethostbyname (
const char *name // 도메인 이름
);
/* IP 주소(네트워크 바이트 정렬) → 도메인 이름 */
struct hostent *gethostbyaddr (
const char *addr, // IP 주소(네트워크 바이트 정렬)
int len, // IP 주소의 길이
int type // 주소 체계(AF_INET 또는 AF_INET6)
);
두 함수 모두 hostent
구조체형 포인터를 리턴하기 때문에 이 구조체를 이해하기 위해 정의를 살펴보면 아래와 같다.
typedef struct hostent
{
char *h_name; // official name of host
char **h_aliases; // alias list
short h_addrtype; // host address type
short h_length; // length of address
char **h_addr_list; // list of address
#define h_addr h_addr_list[0] // address, for backward compatibility
} HOSTENT;
AF_INET
또는 AF_INET6
)h_addr_list[0]
에 접근하는데, 매크로를 통해 재정의된 h_addr
을 사용하면 편하다.(3-11 그림)
아래 함수들은 도메인 이름과 IPv4 상호 변환 사용자 정의 함수들로 성공하면 TRUE
를, 실패하면 FALSE
를 리턴한다.
// 도메인 이름 → IPv4 주소
BOOL GetIPAddr(char *name, IN_ADDR *addr)
{
HOSTENT *ptr = gethostbyname(name);
if (prt == NULL)
{
err_display("gethostbyname()");
return FALSE;
}
if (ptr->h_addrtype != AF_INET)
return FALSE;
memcpy(addr, ptr->h_addr, ptr->h_length); // IP 주소 복사
return TRUE;
}
// IPv4 주소 → 도메인 이름
BOOL GetDomainName(IN_ADDR *addr, char *name, int namelen)
{
HOSTENT *ptr = gethostbyaddr((char *) &addr, sizeof(addr), AF_INET);
if (ptr == NULL)
{
err_display("gethostbyaddr()");
return FALSE;
}
if (ptr->h_addrtype != AF_INET)
return FALSE;
strncpy(name, ptr->h_name, namelen); // 도메인 이름 복사
return TRUE;
}
이름 변환 함수 사용 시 유의사항
1. 호스트가 IPv4와 IPv6 주소를 모두 갖고 있는 경우,gethostbyname()
함수는 항상 IPv4 주소 정보를 리턴*한다. 호스트의 IPv6 주소 또는 IPv4/IPv6 주소 정보 모두 필요할 경우getaddrinfo()
함수를 사용하면 된다.
2. 유효한 도메인 이름을 IP 주소로 변환하는 일은 항상 가능하지만, 유효한 IP 주소를 도메인 이름으로 변환하는 일은 항상 가능하지는 않다. 따라서 IP 주소를 도메인 이름으로 변환한 결과는 참고로만 사용하는 것이 좋다.
3.HOSTENT
구조체는 스레드당 하나씩 할당된다. 따라서 변환된 IP 주소나 도메인 이름을 계속 사용하려면 다른 소켓 함수를 호출하기 전에 미리 복사해둬야 한다. 위에서 정의한 사용자 정의 함수GetIPAddr()
,GetDomainName()
함수 모두 이 규칙을 따르고 있다.
위에서 정의한 사용자 저의 함수 GetIPAddr()
, GetDomainName()
함수를 사용해 이름 변환을 수행하는 예제이다.
#pragma comment(lib, "ws2_32")
#include <winsock2.h>
#include <stdio.h>
#define TESTNAME "www.example.com"
// 소켓 함수 오류 출력
void err_display(char *msg)
{
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
NULL, WSAGetLastError(),
MAKELANGID(LAGN_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf, 0, NULL);
printf("[%s] %s\n", msg, lpMsgBuf);
LocalFree(lpMsgBuf);
}
// 도메인 이름 → IPv4 주소
BOOL GetIPAddr(char *name, IN_ADDR *addr)
{
HOSTENT ptr = gethostbyname(name);
if (ptr == NULL)
{
err_display("gethostbyname()");
return FALSE;
}
if (ptr->h_addrtype != AF_INET)
return FALSE;
memcpy(addr, ptr->h_addr, ptr->h_length);
return TRUE;
}
// IPv4 주소 → 도메인 이름
BOOL GetDomainName(IN_ADDR addr, char *name, int namelen)
{
HOSTENT ptr = gethostbyaddr((char *) &addr, sizeof(addr), AF_INET);
if (ptr == NULL)
{
err_display("gethostbyaddr()");
return FALSE;
}
if (ptr->h_addrtype != AF_INET)
return FALSE;
strncpy(name, ptr->h_name, namelen);
return TRUE;
}
int main(int argc, char *argv[])
{
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
return 1;
printf("도메인 이름(변환 전) = %s\n", TESTNAME);
// 도메인 이름 → IP 주소
IN_ADDR addr;
if (GetIPAddr(TESTNAME, &addr))
{
// 성공이면 결과 출력
printf("IP 주소(변환 후) = %s\n", inet_ntoa(addr));
// IP 주소 → 도메인 이름
char name[256];
if (GetDomainName(addr, name, sizeof(name)))
{
// 성공이면 결과 출력
printf("도메인 이름(다시 변환후) = %s\n", name);
}
}
WSACleanup();
return 0;
}
실행 결과 도메인 이름이 존재하는 경우 아래와 같다.
(실형 결과 성공)
실행 결과 도메인 이름이 존재하지 않은 경우 아래와 같다.
(실행 결과 실패)
참고 자료
김성우 저, "TCP/IP 윈도우 소켓 프로그래밍", 한빛아카데미, 2018