[소켓 #08] 도메인 이름과 인터넷 주소

이석환·2023년 4월 17일

Socket Programming

목록 보기
9/18

1. DNS Server

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

  • 도메인 이름
    인터넷에서 서비스를 제공하는 서버들 역시 IP주소로 구분이 된다.
    그러나 IP주소는 기억하기도 쉽지 않다. 때문에 기억하기도 좋고 표현하기에도 좋은 형태의 도메인 이름이라는 것을 IP주소에 부여해서, 이것을 IP주소 대신에 사용하고 있다.
    즉, IP를 대신하는 서버의 주소이며 실제 접속에 사용되는 주소는 아니다.
    이 정보는 IP로 변환이 되어야 접속이 가능하다.

  • DNS 서버
    인터넷 브라우저 주소 창에 IP주소 xxx.xx.xxx.x를 직접 입력하면 해당하는 홈페이지의 메인 페이지를 볼 수 있다. 그러나 일반적으로 IP주소로 들어가지 않고 www.XXXXX.com과 같은 입력을 통해서 접속한다.
    이 두 접속 방법에는 어떠한 차이점이 있을까 ?
    접속의 과정에 차이가 있다.
    도메인 이름은 해당 서버에 부여된 가상의 주소이지 실제 주소가 아니다.
    때문에 가상의 주소를 실제 주소로 변환하는 과정을 거쳐서 해당 홈페이지에 접속해야 한다.
    도메인 이름을 IP주소로 변환을 담당하는 것이 DNS 서버이다. 다음과 같은 과정을 거친다.
    "DNS 서버야, www.XXXXX.com의 IP주소가 어떻게 되니 ?"
    모든 컴퓨터에는 Default DNS 서버의 주소가 등록되어 있다.
    바로 이 DNS 서버를 통해서 도메인 이름에 대한 IP 주소 정보를 얻게 된다.
  • 즉, 인터넷 브라우저 주소 창에 도메인 이름을 입력하면 인터넷 브라우저는 해당 도메인 이름을 입력하면 인터넷 브라우저는 해당 도메인 이름의 IP주소를 디폴트 DNS 서버를 통해 얻게 되고, 그 다음에 서버로의 실제 접속이 된다.

  • 참고로 컴퓨터에 설정된 디폴트 DNS 서버가 모든 도메인의 IP주소를 알지는 못한다.
    그러나 디폴트 DNS 서버는 모르면 다른 DNS서버에 물어서라도 가르쳐 준다.

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

프로그램 상에서 도메인 이름을 쓸 필요가 있는가 ?
이 블로그를 읽는 독자들이 www.abcde.com이라는 도메인을 운영하는 회사의 시스템 엔지니어라고 가정해보자.
그리고 여러분은 회사의 서비스 사용을 위한 클라이언트 프로그램을 개발해야 한다.
사용자에게 서비스를 제공하기 위해서 계속해서 IP와 PORT번호를 입력하게 한다면 불편함을 느낄 것이다.
그렇다고 미리 프로그램 코드 상에 IP와 PORT번호를 넣는 것도 불편함을 불러일으킨다.
IP는 도메인 이름에 비해 상대적으로 변동이 심하다.
때문에 프로그램 코드상에서 서버의 IP를 직접 코드로 입력한다면, 서버의 IP가 변경될 때마다 컴파일을 다시 해야 하는 상황이 발생한다. 그러나 상대적으로 변동이 덜한 도메인 이름을 이용해서 서버가 실행될 때마다 IP를 얻어오게 구혀난다면, 서버의 코드를 다시 컴파일 할 필요가 없다.

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

#include <netdb.h>

struct hostent * gethostbyname(const char * hostname);

// 성공 시 hostent 구조체 변수의 주소 값, 실패 시 NULL 포인터 반환
// hostname은 도메인의 이름 (www.abcde.com)
/*
struct * h_name; // official name
struct ** h_aliases; // alias list
int h_addrtype; // host address type
int h_length; // address length
char ** h_addr_list; // char ** h_addr_list;
*/

gethostbyname 함수의 인자로 도메인의 이름(www.abcde.com)정보를 전달하면, 해당 도메인의 서버 정보가 hostent 구조체 변수에 채워지고, 그 변수의 주소 값이 반환된다.

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

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

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

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

  • h_addr_list
    가장 중요한 멤버이다.
    이 멤버를 통해서 도메인 이름에 대한 IP주소가 정수의 형태로 반환된다.
    참고로 접속자수가 많은 서버는 하나의 도메인 이름에 대응하는 IP를 여러 개 둬서, 둘 이상의 서버로 부하를 분산 시킬 수 있다.
    이러한 경우에도 이 멤버를 통해서 모든 IP의 주소 정보를 얻을 수 있다.
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>

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

int main(int argc, char* argv[]){
	int i;
	struct hostent *host;
	
	if(argc!=2){
		printf("Usage : %s <IP>\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;

}

코드를 보면 이상한 점을 느낄 수 있다.
구조체 멤버 h_addr_list가 가리키는 것은 문자열 포인터 배열(둘 이상의 문자열 주소 값으로 구성된 배열)이다. 그러나 문자열 포인터 배열이 실제 가리키는 것은(실제 저장하고 있는 것은) 문자열의 주소 값이 아닌 in_addr 구조체 변수의 주소 값이다.

위 그림은 구조체 멤버 h_addr_list의 참조 관계를 보인다.
때문에 형변환 및 inet_ntoa 함수의 호출을 동반하는 것이다.
추가로 정보의 끝은 NULL로 표시가 된다는 사실도 기억하자.

  • 실행 결과
  • 여기서 하나 더 !
    in_addr 이 아닌 char인 이유를 설명하겠다.
    h_addr_list가 가리키는 배열이 구조체 in_addr의 포인터 배열이 아닌 char형 포인터 배열인 이유는 구조체 hostent는 IPv4만을 위해 정의된 구조체가 아니다. h_addr_list가 가리키는 배열에는 IPv6 기반의 주소 정보가 저장될 수도 있다.
    때문에 일반화를 위해서 char형 포인터 배열로 선언한 것이다.
    소켓관련 함수들은 void형 포인터가 표준화되기 이전에 정의되었기 때문에 아직까지 char형 포인터 변수를 활용한다.

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

gethostbyname 함수의 반대 기능 제공

#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 전달
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>

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

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;

}

차이점은 매개 변수의 인자가 끝이다. 반대로 생각하면 굉장히 쉽다.
gethostbyname 함수를 이해했으면 굉장히 쉽기 때문에 추가 설명없이 넘어가겠다.

  • 실행 결과

참고: 윤성우의 열혈 TCP/IP 소켓 프로그래밍
GIT : https://github.com/im2sh/Socket_Programming/tree/main/lab06/ch8

profile
반갑습니다.

0개의 댓글