프로젝트를 진행하며 가비아에서 도메인을 구입하여 DNS 관리 서비스를 통해 호스트를 설정하고 IP주소를 설정한 경험이 있었습니다. 하지만 이것들이 어떤 것을 의미하는지 모르고 사용했기 때문에 오늘은 이를 이해하기 위해 DNS에 대해 알아보고자 합니다.
우리는 naver.com, google.com 과 같이 호스트 네임을 브라우저에 입력해 IP주소를 얻어 필요한 데이터를 얻습니다. 이 과정에서 호스트 네임을 IP 주소로 바꿔주는 과정을 DNS 서버가 담당하게 됩니다.
사실 DNS가 제공해주는 서비스는 이것 말고도 몇 가지가 더 존재합니다.
정식 호스트 이름(canonical hostname)
이라고 합니다. 이처럼 DNS는 제시한 호스트 이름에 대한 정식 호스트 이름을 얻기 위해 사용될 수도 있습니다.그러면 DNS는 어떻게 동작할까요?
사용자의 어떤 애플리케이션이 호스트 네임을 IP 주소로 변환하려 한다고 가정해 봅시다. 그 애플리케이션은 DNS 클라이언트를 호출해 네트워크에 질의 메시지를 보낼 것입니다. 그러면 호스트의 DNS는 응답을 메시지를 받게 될 것입니다. 애플리케이션 관점에서 DNS는 변환서비스를 제공하는 블랙박스로 볼 수 있습니다.
DNS는 UDP 프로토콜을 통해 데이터를 주고 받습니다.
본격적으로 DNS의 동작과정에 대해 알아보기 전에 알아야 할 것들이 몇 가지 존재합니다.
DNS는 위 그림과 같이 계층 구조를 가지고 있습니다. 세 유형의 DNS 서버가 존재하는데 다음과 같습니다.
또한 각각의 서버는 한 대만 존재하는 것이 아니라 분산되어 있습니다.
왜 분산되어 있을까?
만약 전세계에 하나의 DNS 서버만이 존재한다고 가정해봅시다.
- 그렇다면 모든 요청이 한 대의 DNS 서버에게 갈 것이고 단일 DNS 서버가 모든 DNS 질의를 처리해야 할 것입니다.
- DNS 서버가 미국에 한 대 있다고 했을 때 대한민국에 있는 저는 미국으로부터의
물리적 거리
가 있기 때문에 지연이 발생할 것입니다. 하지만 미국에 있는 사람들은 지연이 덜 하겠죠.- SPOF: 만약 이 서버가 고장이 나면 어떻게 될까요? 인터넷이 멈춰버리는 대참사가 발생하게 됩니다.
상위 계층의 DNS 서버는 하위 계층의 DNS 서버의 IP 주소들을 알고 있습니다. 만약 www.naver.com 이라는 호스트네임의 IP 주소를 얻고 싶다면 먼저 루트 DNS 서버에게 질의합니다. 그러면 루트 DNS 서버는 com이라는 TLD 서버의 IP 주소를 제공합니다. TLD에서는 책임 DNS 서버(naver.com)에 대한 IP 주소를 제공합니다.
다시 돌아와서, 우리가 naver.com의 IP주소를 원한다고 해보겠습니다. 그러면 아래와 같이 동작하게 되는데, 그 과정은 다음과 같습니다.
com
을 인식하고 com
에 대한 책임을 가진 TLD DNS 서버의 IP 주소 목록을 로컬 DNS 서버에게 보냅니다.naver.com
을 인식하고 네이버의 책임 DNS 서버의 IP주소를 응답합니다.이 과정에서 메시지가 전달되는 과정이 8번이나 반복됐습니다. 이를 해결하기 위해 DNS 캐싱을 수행합니다. 간단히 DNS 서버가 질의를 받았을 때 아는 정보라면 바로 응답해주는 것이죠.
DNS 서버들은 호스트네임을 IP주소로 매핑하기 위한 Resouce Record(RR)을 저장합니다. RR은 다음과 같은 필드를 포함하는 4개의 튜플로 구성되어 있습니다.
name, value는 type에 의해 결정되는데 type은 아래와 같은 것들이 존재합니다.
이제 DNS 의 질의 방식과 응답형태를 알았으니 간단하게 DNS 클라이언트를 만들어보겠습니다.
RFC 문서를 보며 클라이언트를 만들어보죠.
먼저 DNS 서버에게 요청을 보낼 레코드를 만들어야겠죠? 저는 Java를 이용해 진행해보겠습니다.
RFC 문서에 따르면 Header Section은 아래의 그림과 같이 구성되어 있다고 나옵니다.
그리고 Question Section은 다음과 같습니다.
return new byte[]{
// Header Section
// 트랜잭션 아이디 (transaction id)
0x00, 0x01,
// |QR| Opcode |AA|TC|RD|RA|Z|RCODE|
// 0 0 0 0 0 0 0 0
0x00, 0x00, // QR, Opcode, AA, TC, RD, RA, Z, RCODE
// QDCOUNT
0x00, 0x01,
// ANCOUNT
0x00, 0x00,
// NSCOUNT
0x00, 0x00,
// ARCOUNT
0x00, 0x00,
// Question Section
// 질의 이름 QNAME(query name) 5naver3com0
0x05, 0x6e, 0x61, 0x76, 0x65, 0x72,
0x03, 0x63, 0x6f, 0x6d,
0x00,
// 질의 타입 QTYPE(query type) A
0x00, 0x01, // A
// 질의 클래스 QCLASS(query class) IN(1)
0x00, 0x01
};
이와 같은 질의 쿼리를 보내고 응답을 받았습니다. 바이트 형태로 출력해보면 아래와 같습니다.
Header Section
00 01 -> 트랜잭션 아이디 (transaction id)
80 80 -> |QR| Opcode |AA|TC|RD|RA|Z|RCODE|
00 01 -> QDCOUNT
00 04 -> ANCOUNT
00 00 -> NSCOUNT
00 00 -> ARCOUNT
*
Question Section
// QNAME (5naver3com0)
05 6e 61 76 65 72 -> naver
03 63 6f 6d -> com
00
00 01 -> 질의 타입 (query type) A type(1)
00 01 -> 질의 클래스 (query class) IN(1)
*
Answer Section
c0 0c -> offset 12
00 01 -> TYPE : A type(1)
00 01 -> CLASS : IN(1)
00 00 00 b0 -> TTL : 0xb0 -> 176
00 04 -> RDLENGTH : 4
df 82 c8 6b -> RDATA : 223 130 200 107
*
c0 0c -> offset 12
00 01 -> TYPE: A type(1)
00 01 -> CLASS : IN(1)
00 00 00 b0 -> TTL : 0xb0 -> 176
00 04 -> RDLENGTH : 4
df 82 c3 5f -> RDATA : 223 130 195 95
c0 0c -> offset 12
00 01 -> TYPE: A type(1)
00 01 -> CLASS: IN(1)
00 00 00 b0 -> TTL : 176
00 04 -> RDLENGTH : 4
df 82 c3 c8 -> RDATA : 223 130 195 200
*
c0 0c -> offset 12
00 01 -> TYPE: A type(1)
00 01 -> CLASS: IN(1)
00 00 00 b0 -> TTL : 176
00 04 -> RDLENGTH : 4
df 82 c8 68 -> RDATA : 223 130 200 104
Answer Section을 살펴보면 우리가 A type의 레코드를 요청했기 때문에 응답도 A type으로 온 것을 볼 수 있습니다. 또한 TTL과 IP주소도 정상적으로 온 것을 확인할 수 있었습니다.
처음에는 DNS 가 단순히 호스트네임을 IP주소로 변환해준다 정도로 알고 있었는데 별칭을 부여해주는 에일리어싱 서비스, 부하분산등을 제공해준다는 것을 배울 수 있었습니다.
또한 DNS의 응답 type에 따라 오는 value들이 달라진다는 것을 보며
정말 간단하게 만들어본 DNS Client는 아래 링크에서 확인할 수 있습니다.
https://gist.github.com/23Yong/c7aa6030b73e519bd1e5b2fc7b3e6aa1