[Network] DNS 탐구

JeongYong Park·2023년 10월 30일
2
post-thumbnail
post-custom-banner

프로젝트를 진행하며 가비아에서 도메인을 구입하여 DNS 관리 서비스를 통해 호스트를 설정하고 IP주소를 설정한 경험이 있었습니다. 하지만 이것들이 어떤 것을 의미하는지 모르고 사용했기 때문에 오늘은 이를 이해하기 위해 DNS에 대해 알아보고자 합니다.

DNS

우리는 naver.com, google.com 과 같이 호스트 네임을 브라우저에 입력해 IP주소를 얻어 필요한 데이터를 얻습니다. 이 과정에서 호스트 네임을 IP 주소로 바꿔주는 과정을 DNS 서버가 담당하게 됩니다.

사실 DNS가 제공해주는 서비스는 이것 말고도 몇 가지가 더 존재합니다.

  • 호스트 에일리어싱
    • 복잡한 호스트네임을 가진 호스트는 하나 이상의 이름을 가질 수 있습니다. 예를 들어 apple.red.tasty.food.com이라는 호스트 이름은 food.com, www.food.com과 같은 별칭을 가질 수 있습니다. 이 경우에 apple.red.tasty.food.com는 정식 호스트 이름(canonical hostname)이라고 합니다. 이처럼 DNS는 제시한 호스트 이름에 대한 정식 호스트 이름을 얻기 위해 사용될 수도 있습니다.
  • 메일 서버 에일리어싱
    • 전자 메일을 송수신할 때 도메인 이름과 메일서버 사이의 중개 역할을 수행하기도 합니다. 우리는 보통 메일의 수신자가 user@naver.com 과 같이 간단한 것을 확인할 수 있었습니다. 메일 클라이언트는 수신자 도메인(naver.com)에 대해 DNS 서버에게 (MX 레코드를 통해) 질의합니다. DNS 서버는 해당 도메인의 메일서버의 호스트 이름을 반환합니다(MX 레코드를 반환). 그러면 메일 클라이언트는 얻은 메일서버의 호스트이름에 메일을 전송하게 됩니다.
  • 부하 분산
    • 중복된 서버 사이에서 부하를 분산하기 위해서도 DNS를 사용할 수 있습니다. 예를 들어 naver.com 같은 인기있는 사이트는 여러 서버에 중복되어 있기 때문에 각각이 다른 종단 시스템, 다른 IP주소를 가지고 있습니다. DNS 데이터베이스는 이 IP주소들의 집합을 가지고 있고 클라이언트가 해당 호스트 네임으로 질의를 하게 되면 DNS 서버는 IP주소 집합을 가지고 응답을 하게 됩니다.

그러면 DNS는 어떻게 동작할까요?

DNS의 동작

사용자의 어떤 애플리케이션이 호스트 네임을 IP 주소로 변환하려 한다고 가정해 봅시다. 그 애플리케이션은 DNS 클라이언트를 호출해 네트워크에 질의 메시지를 보낼 것입니다. 그러면 호스트의 DNS는 응답을 메시지를 받게 될 것입니다. 애플리케이션 관점에서 DNS는 변환서비스를 제공하는 블랙박스로 볼 수 있습니다.

DNS는 UDP 프로토콜을 통해 데이터를 주고 받습니다.

본격적으로 DNS의 동작과정에 대해 알아보기 전에 알아야 할 것들이 몇 가지 존재합니다.

분산 계층

DNS는 위 그림과 같이 계층 구조를 가지고 있습니다. 세 유형의 DNS 서버가 존재하는데 다음과 같습니다.

  • 루트 DNS 서버
  • 탑 레벨 도메인 서버 (Top-level domain, TLD)
  • 책임 서버 (authoritative)

또한 각각의 서버는 한 대만 존재하는 것이 아니라 분산되어 있습니다.

왜 분산되어 있을까?
만약 전세계에 하나의 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주소를 원한다고 해보겠습니다. 그러면 아래와 같이 동작하게 되는데, 그 과정은 다음과 같습니다.

  1. 호스트는 먼저 자신의 로컬 DNS 서버에게 요청을 보냅니다.
  2. 로컬 DNS 서버는 이를 받아 root DNS server에게 질의 메시지를 던집니다.
  3. 그러면 root DNS 서버는 com을 인식하고 com에 대한 책임을 가진 TLD DNS 서버의 IP 주소 목록을 로컬 DNS 서버에게 보냅니다.
  4. 로컬 DNS 서버는 TLD DNS 서버에게 질의 메시지를 던집니다.
  5. TLD 서버는 naver.com을 인식하고 네이버의 책임 DNS 서버의 IP주소를 응답합니다.
  6. 마지막으로 로컬 DNS 서버는 책임 DNS 서버에게 질의 메시지를 다시 보내고
  7. IP주소를 얻어
  8. 호스트에게 응답합니다.

이 과정에서 메시지가 전달되는 과정이 8번이나 반복됐습니다. 이를 해결하기 위해 DNS 캐싱을 수행합니다. 간단히 DNS 서버가 질의를 받았을 때 아는 정보라면 바로 응답해주는 것이죠.

DNS 레코드

DNS 서버들은 호스트네임을 IP주소로 매핑하기 위한 Resouce Record(RR)을 저장합니다. RR은 다음과 같은 필드를 포함하는 4개의 튜플로 구성되어 있습니다.

  • name
  • value
  • type
  • ttl (time to live)
    • RR이 DNS 서버에서 존재할 수 있는 생존기간을 의미합니다.(자원이 캐시되는 시간을 결정합니다.)

name, value는 type에 의해 결정되는데 type은 아래와 같은 것들이 존재합니다.

  • type = A
    • type이 A이면 name은 호스트네임이 되고 value는 호스트네임에 대한 IP주소를 의미합니다.
    • 예를 들어 name이 abc.com이면 value는 12.23.34.45 인것이죠.
  • type = NS
    • type이 NS이면 name은 호스트네임이 되고 value는 도메인 내부의 호스트에 대한 IP주소를 얻을 수 있는 방법을 아는 책임 DNS 서버의 호스트네임입니다.
    • 예를 들어 name이 abc.com이면 value는 dns.abc.com 인것이죠.
  • type = CNAME
    • name이 별칭 호스트네임일 때 value는 name(별칭 호스트네임)의 정식 호스트네임입니다.
    • 예를 들어 name이 abc.com이면 value는 ghi.def.abc.com 인 것이죠.
  • type = MX
    • name이 별칭 호스트네임일 때 value는 이를 갖는 메일 서버의 정식 이름입니다.
    • 예를 들어 name이 abc.com이면 value는 mail.abc.com 인 것이죠.

DNS 클라이언트 만들어보기

이제 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

profile
다음 단계를 고민하려고 노력하는 사람입니다
post-custom-banner

0개의 댓글