본문으로 바로가기
반응형

도메인 이름과 인터넷 주소

IP주소와 도메인 이름 사이에서의 변환을 수행하는 시스템을 가리켜 'DNS(Domain Name System)'라 하며, DNS의 중심에는 DNS 서버가 있다.

 

 

도메인 이름이란?

인터넷에서 서비스를 제공하는 서버들 역시 IP주소로 구분이 된다. 그러나 기억하기 쉽지 않은 IP주소의 형태로 서버의 주소 정보를 기억하는 것을 사실상 불가능한 일이다. 때문에 기억하기도 좋고 표현하기도 좋은 형태인 도메인 이름이라는 것을 IP주소에 부여해서, 이것이 IP주소를 대신하도록 하고 있다.

 

 

DNS 서버

인터넷 브라우저 주소 창에 222.192.195.5 를 입력하면 네이버의 메인 페이지를 볼 수 있다.

그러나 일반적으로는 네이버의 도메인 이름인 www.naver.com의  의 입력을 통해 네이버에 접속한다.

그렇다면 이 두 접속방법에는 어떠한 차이점이 있는 것일까?

 

네이버의 메인 페이지에 접속한다는 점에는 차이가 없지만, 접속의 과정에는 차이가 있다. 도메인 이름은 해당 서버에 부여된 가상의 주소이지 실제주소가 아니다. 때문에 가상의 주소를 실제주소로 변환하는 과정을 거쳐서 네이버에 접속해야한다. 그렇다면 어떻게 도메인 이름을 IP주소로 변환해야 할까? 

이러한 변환을 담당하는 것이 DNS 서버이니, DNS 서버에게 변환을 요청하면 된다.

모든 컴퓨터에는 디폴트 DNS 서버의 주소가 등록되어 있는데, 바로 이 디폴트 DNS 서버를 통해 도메인 이름에 대한 IP 주소 정보를 얻게된다. 즉, 우리가 인터넷 브라우저 주소 창에 도메인 이름을 입력하면 인터넷 브라우저는 해당 도메인 이름의 IP주소를 디폴트 DNS 서버를 통해 얻게되고, 그 다음에야 비로소 실제 접속에 들어가게 되는 것이다.

 

물론 우리 컴퓨터에 설정되있는 디폴트 DNS 서버가 모든 도메인의 IP주소를 알고 있지는 않다. 그러나 디폴트 DNS 서버는 모르면 물어서라도 가르쳐준다. 다른 DNS 서버에게 물어서라도 가르쳐준다.

위 그림은 호스트가 문의한 도메인 이름의 IP주소를 디폴트 DNS 서버가 모르는 사오항에 대한 응답과정을 보이고 있다.

디폴트 DNS 서버는 자신이 모르는 정보에 대한 요청이 들어오면 한단계 상위 계층에 있는 DNS 서버에게 물어본다.

이런 방식으로 계속 올라가다보면 최상위 DNS 서버인 Root DNS 서버에게까지 질의가 전달되는데, Root DNS 서버는 해당 질문을 누구에게 재전달해야할지 알고 있다. 그래서 자신보다 하위에 있는 DNS 서버에게 다시 질의를 던져서 결국은 IP 주소를 얻어내며, 그 결과는 질의가 진행된 반대 방향으로 전달되어 결국에는 질의를 시작한 호스트에게 IP주소가 전달된다. 이렇듯 DNS는 계층적으로 관리되는 일종의 분산 데이터베이스 시스템이다.

 

 

IP 주소와 도메인 이름 사이의 변환

IP 주소는 도메인 이름에 비해 상대적으로 변경될 확률이 높다. 

도메인 이름은 일단 등록하고나면 평생 유지가 가능하니, 이를 이용해서 코드를 작성하는 것이 좋을 것이다.

이렇게되면 프로그램이 실행될때마다 도메인 이름을 근거로 IP주소를 얻어온 다음에 서버에 접속하게 되니, 서버의 IP 주소로부터 클라이언트 프로그램은 자유로울 수 있다. 그래서 IP주소와 도메인 이름 사이의 변환함수가 필요하다.

 

 

도메인 이름을 이용해서 IP 주소 얻어오기

다음 함수를 이용하면 문자열 형태의 도메인 이름으로부터 IP 주소 정보를 얻을 수 있다.

#include <netdb.h>
struct hostent * gethostbyname(const char* hostname); // 성공 시 hostent 구조체 변수의 주소 값, 실패 시 NULL 포인터 변환

 

변환하고자 하는 도메인의 이름을 문자열 형태로 전달하면 해당 도메인의 IP 주소 정보가 반환된다.

단, hostent 라는 구조체의 변수에 담겨서 반환이 되는데, 이 구조체는 다음과 같이 정의되어 있다.

struct hostent
{
    char * h_name;        // official name
    char ** h_aliases;    // alias list
    int h_addrtype;       // host address type
    int h_length;         // address length
    char ** h_addr_list;  // address list
}

 

구조체 정의를 보면 IP 정보만 반환되는 것이 아니라 여러가지 다른 정보들도 덤으로 반환되는 것을 확인할 수 있다.

도메인 이름을 IP로 변환한 경우 h_addr_list만 신경쓰면 된다.

 

각각 멤버에 대해 알아보도록 하자.

 

- h_name

이 멤버는 '공식 도메인 이름(Official domain name)'이라는 것이 저장된다. 공식 도메인 이름은 해당 홈페이지를 대표하는 도메인 이름이라는 의미를 담고 있지만, 실제로는 우리에게 잘 알려진 유명 회사의 도메인 이름이 공식 도메인 이름으로 등록되지 않은 경우가 많다.

 

- h_aliases

같은 메인 페이지인데도 다른 도메인 이름으로 접속할 수 있는 경우를 본 적 있을 것이다. 하나의 IP에 둘 이상의 도메인 이름을 지정하는 것이 가능하기 때문에, 공식 도메인 이름 이외에 해당 메인 페이지에 접속할 수 있는 다른 도메인 이름의 지정이 가능하다. 그리고 이들 정보는 h_aliases를 통해서 얻을 수 있다.

 

- h_addrtype

gethostbyname 함수는 IPv4뿐만 아니라 IPv6까지 지원한다. 때문에 h_addr_list로 반환된 IP 주소의 주소체계에 대한 정보를 이 멤버를 통해 반환한다. IPv4의 경우 이 멤버에는 AF_INET이 저장된다.

 

- h_length

함수 호출의 결과로 반환된 IP 주소의 크기정보가 담긴다. IPv4의 경우 4바이트 이므로 4가 저장되고, IPv6의 경우에는 16바이트이므로 16이 저장된다.

 

- h_addr_list

가장 중요한 멤버이다.

이 멤버를 통해 도메인 이름에 대한 IP주소가 정수의 형태로 반환된다. 참고로 접속자 수가 많은 서버는 하나의 도메인 이름에 대응하는 IP를 여러개 둬서, 둘 이상의 서버로 부하를 분산시킬 수 있는데, 이러한 경우에도 이 멤버를 통해서 모든 IP의 주소정보를 얻을 수 있다.

 

다음 그림은 gethostbyname 함수 호출 후에 반환되는 hostent 구조체 변수의 구성을 나타낸 것이다.

실제 프로그램 구현에있어서 구조체멤버가 어떻게 돌아가는지 파악하는데 도움이 될 것이다.

예제를 하나 작성해보자. gethostbyname 함수의 활용을 보이기위해, 그리고 앞서 설명하지 못한 hostent 구조체 변수의 특성을 중점으로 보도록하자.

// gethostbyname.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
void error_handling(char *message);

int main(int argc, char *argv[])
{
   int i;
   struct hostent *host;
   if(argc != 2)
   {
      printf("Usage : %s <addr> \n", argv[0]);
      exit(1);
   }

   host = gethostbyname(argv[1]);
   if(!host)
      error_handling("gethost... error");

   printf("Official name : %s \n", host->h_name);
   for(i = 0; host->h_aliases[i]; i++)
      printf("Aliases %d : %s \n", i+1, host->h_aliases[i]);

   printf("Address type : %s \n",
         (host->h_addrtype == AF_INET) ? "AF_INET" : "AF_INET6");

   for(i = 0; host->h_addr_list[i]; i++)
      printf("IP addr %d : %s \n", i + 1,
            inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));
   return 0;
}

void error_handling(char *message)
{
   fputs(message, stderr);
   fputc('\n', stderr);
   exit(1);
}

 

원하는 도메인 이름으로 예제를 실행하면 된다. 앞서보인 예제의 행을 다시 보자.

for(i = 0; host->h_addr_list[i]; i++)
   printf("IP addr %d : %s \n", i + 1,
         inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));

구조체 hostent의 정의 부분만 높고보면, 구조체 멤버 h_addr_list가 가리키는 것은 문자열 포인터 배열(둘 이상의 문자열 주소 값으로 구성된 배열)이다. 그러나 문자열 포인터 배열이 실제 가리키는 것은(실제 저장하고 있는 것은) 문자열의 주소 값이 아닌 in_addr 구조체 변수의 주소 값이다.

8-3 h_addr_list 구조체 멤버

위 그림은 구조체 멤버 h_addr_list의 참조관계를 보이고 있다. 때문에 위 예제에서는 형변환 및 inet_ntoa 함수의 호출을 동반하는 것이다. 참고로 구조체 in_addr에 대해서는 3번 포스팅에서 확인할 수 있다.

 

 

in_addr* 이 아닌 char* 인 이유?

구조체 hostent의 멤버 h_addr_list가 가리키는 배열이 구조체 in_addr의 포인터 배열이 아닌, char형 포인터 배열인 이유가 궁금할 수도 있다. 구조체 hostent는 IPv4만을 위해 정의된 구조체가 아니다. 

h_addr_list가 가리키는 배열에는 IPv6 기반의 주소 정보가 저장될 수도 있다.

때문에 일반화를 위해 char형 포인터 배열로 선언한 것이다.

 

물론 참조할 대상이 일정하지 않을 경우 void형 포인터 변수가 훨씬 더 잘어울려보일 수 있다.

하지만 소켓관련 함수들은 void형 포인터가 표준화 되기 이전에 정의되었고, 옛날에는 참조의 대상이 일정하지 않은 경우 char형 포인터 변수를 활용하였다고한다.

 

 

IP 주소를 이용해서 도메인 정보 얻어오기

앞서 소개한 gethostbyname 함수는 도메인 이름을 이용해서 IP주소를 포함한 도메인 정보를 얻을 때 호출하는 함수이다. 반면 이번에 소개하는 gethostbyaddr 함수는 IP주소를 이용해서 도메인 정보를 얻을 때 호출하는 함수이다.

#include <netdb.h>
struct hostent * gethostbyaddr(const char * addr, socklen_t len, int family);
// 성공 시 hostent 구조체 변수의 주소 값, 실패 시 NULL 포인터 반환

- addr : IP 주소를 지니는 in_addr 구조체 변수의 포인터 전달, IPv4 이외의 다양한 정보를 전달받을 수 있도록 일반화하기위해 매개변수를 char형 포인터로 선언

- len : 첫번째 인자로 전달된 주소 정보의 길이, IPv4의 경우 4, IPv6의 경우 16 전달

- family : 주소체계 정보 전달, IPv4의 경우 AF_INET, IPv6의 경우 AF_INET6 전달

 

 

이제 간단한 예제를 통해 함수의 사용방법을 알아보자.

// gethostbyaddr.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
void error_handling(char *message);

int main(int argc, char *argv[])
{
   int i;
   struct hostent *host;
   struct sockaddr_in addr;
   if(argc != 2)
   {
      printf("Usage : %s <IP>\n", argv[0]);
      exit(1);
   }

   memset(&addr, 0, sizeof(addr));
   addr.sin_addr.s_addr = inet_addr(argv[1]);
   host = gethostbyaddr((char*)&addr.sin_addr, 4, AF_INET);
   if(!host)
      error_handling("gethost.... error");

   printf("Official name : %s \n", host->h_name);
   for(i = 0; host->h_aliases[i]; i++)
      printf("Aliases %d : %s\n", i+1, host->h_aliases[i]);

   printf("Address type : %s \n",
         (host->h_addrtype == AF_INET) ? "AF_INET" : "AF_INET6");

   for(i = 0; host->h_addr_list[i]; i++)
      printf("IP addr %d :%s \n", i + 1,
            inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));

   return 0;
}

void error_handling(char *message)
{
   fputs(message, stderr);
   fputc('\n', stderr);
   exit(1);
}

구글의 IP주소를 이용해서 예제를 실행해봤지만 DNS에 등록된 공식 홈페이지 주소가 다소 독특하다는 사실만 확인할 수 있었다...

반응형