icon

안동민 개발노트

8장 : DNS

DNS 레코드와 실습


DNS 서버가 도메인에 대한 정보를 저장한다는 것을 알았습니다. 그 정보가 구체적으로 어떤 형태인지, 그리고 직접 조회하는 방법을 살펴보겠습니다.


주요 DNS 레코드 유형

DNS 레코드는 도메인 이름에 연결된 데이터입니다.

DNS 레코드 구조
┌────────────────────────────────────────────┐
│         DNS 리소스 레코드 (RR)             │
├──────────┬──────┬──────┬──────┬────────────┤
│   NAME   │ TTL  │ CLASS│ TYPE │   RDATA    │
├──────────┼──────┼──────┼──────┼────────────┤
│ example  │ 86400│  IN  │  A   │ 93.184.216 │
│   .com   │      │      │      │    .34     │
├──────────┼──────┼──────┼──────┼────────────┤
│ example  │ 86400│  IN  │ AAAA │ 2606:2800: │
│   .com   │      │      │      │ 220:1::    │
├──────────┼──────┼──────┼──────┼────────────┤
│ example  │ 86400│  IN  │  MX  │ 10 mail.   │
│   .com   │      │      │      │ example.com│
└──────────┴──────┴──────┴──────┴────────────┘
NAME: 도메인 이름
TTL: 캐시 유효 기간 (초)
CLASS: 거의 항상 IN (Internet)
TYPE: 레코드 유형
RDATA: 실제 데이터

가장 자주 사용되는 레코드 유형을 정리합니다.

레코드용도예시비고
A도메인 → IPv4example.com → 93.184.216.34가장 기본
AAAA도메인 → IPv6example.com → 2606:2800:220:1::IPv4의 4배 = AAAA
CNAME도메인 → 도메인www → example.com별칭, 루트에 불가
MX메일 서버 지정10 mail.example.com우선순위 숫자 포함
NS권한 서버 지정ns1.example.com위임에 사용
TXT텍스트 정보v=spf1 include:...검증, 이메일 보안
SRV서비스 위치_sip._tcp 5060 sip.ex.com프로토콜+포트
SOA영역 시작ns1.ex.com admin.ex.com영역 메타데이터
PTRIP → 도메인 (역방향)34.216.184.93 → example.com역방향 DNS
CAA인증서 발급 허가0 issue letsencrypt.orgHTTPS 보안
CNAME의 제약과 대안
CNAME은 다른 레코드와 공존 불가
  example.com  CNAME  lb.amazonaws.com   ← 루트 도메인에 불가!
  example.com  MX     mail.example.com   ← MX가 이미 있으므로

이유: CNAME은 "이 이름의 모든 것은 저쪽을 따른다"는 의미
  → 같은 이름에 MX, NS 등이 존재하면 모순

대안 (비표준이지만 널리 사용)
  ALIAS / ANAME 레코드
  example.com  ALIAS  lb.amazonaws.com   ← DNS 서버가 해석 후 A 레코드로 응답
  Route 53의 Alias, Cloudflare의 CNAME Flattening 등

MX 레코드와 이메일 보안 레코드

MX 레코드에는 우선순위 값이 있어서, 메일 서버의 장애 대응이 가능합니다.

MX 레코드 동작
example.com  MX  10  mail1.example.com  (주 서버, 우선순위 10)
example.com  MX  20  mail2.example.com  (백업, 우선순위 20)
example.com  MX  30  mail3.example.com  (2차 백업, 우선순위 30)

메일 전송 시
  1. mail1 (우선순위 10)에 먼저 시도
  2. mail1 실패 → mail2 (우선순위 20)에 시도
  3. mail2도 실패 → mail3 (우선순위 30)에 시도

낮은 숫자 = 높은 우선순위

이메일 보안을 위한 TXT 레코드도 중요합니다.

이메일 보안 DNS 레코드
SPF (Sender Policy Framework)
  example.com  TXT  "v=spf1 include:_spf.google.com ~all"
  → "example.com의 메일은 Google 서버에서만 발송된다"
  → 스팸 발송자가 example.com을 사칭하는 것을 방지

DKIM (DomainKeys Identified Mail)
  selector._domainkey.example.com  TXT  "v=DKIM1; k=rsa; p=MIIBIj..."
  → 메일에 디지털 서명을 추가하여 변조 방지

DMARC (Domain-based Message Authentication)
  _dmarc.example.com  TXT  "v=DMARC1; p=reject; rua=mailto:..."
  → SPF/DKIM 실패 시 정책 (none/quarantine/reject)

nslookup과 dig 실습

DNS 레코드를 직접 조회해 보겠습니다.

nslookup은 Windows, macOS, Linux 모두에서 사용 가능한 기본 DNS 조회 도구입니다.

nslookup 기본 사용법
# 기본 A 레코드 조회
nslookup example.com

# 특정 레코드 유형 조회
nslookup -type=MX example.com
nslookup -type=TXT example.com
nslookup -type=NS example.com
nslookup -type=AAAA example.com

# 특정 DNS 서버를 지정하여 조회
nslookup example.com 8.8.8.8

# 역방향 DNS 조회 (IP → 도메인)
nslookup 93.184.216.34
nslookup 출력 해석
> nslookup -type=MX google.com

Server:    192.168.1.1          ← 사용된 DNS 리졸버
Address:   192.168.1.1#53       ← 리졸버 IP:포트

Non-authoritative answer:        ← 캐시된 응답 (권한 서버 직접 X)
google.com  mail exchanger = 10 smtp.google.com.
google.com  mail exchanger = 20 smtp2.google.com.
google.com  mail exchanger = 30 smtp3.google.com.

"Non-authoritative" = 리졸버 캐시에서 응답
"Authoritative" = 권한 서버에서 직접 응답

dig은 더 상세한 정보를 제공하는 도구입니다.

dig 기본 사용법
# A 레코드 조회
dig example.com

# 특정 레코드 유형 조회
dig example.com MX
dig example.com NS
dig example.com TXT
dig example.com AAAA

# 간결한 출력 (+short)
dig +short example.com
# 93.184.216.34

# 특정 DNS 서버 지정
dig @8.8.8.8 example.com

# 역방향 DNS
dig -x 93.184.216.34
dig 출력 상세 해석
> dig example.com

;; QUESTION SECTION:
;example.com.            IN    A         ← 질문: example.com의 A 레코드

;; ANSWER SECTION:
example.com.    86400   IN    A    93.184.216.34
│               │       │     │    └── 실제 IP 주소
│               │       │     └─────── 레코드 유형
│               │       └──────────── 클래스 (Internet)
│               └──────────────────── TTL (86400초 = 24시간)
└──────────────────────────────────── 도메인 이름

;; AUTHORITY SECTION:
example.com.    86400   IN   NS   ns1.example.com.  ← 권한 서버 정보

;; Query time: 23 msec       ← 질의 소요 시간
;; SERVER: 192.168.1.1#53    ← 사용된 리졸버
;; WHEN: Mon Jan 15 14:30:00 KST 2024
;; MSG SIZE rcvd: 56         ← 응답 크기
전체 해석 과정 추적
# +trace 옵션: 루트부터 최종까지 전체 경로 표시
dig +trace example.com

# 출력 예시 (간략):
# .                  518400  IN  NS  a.root-servers.net.  ← 루트
# com.               172800  IN  NS  a.gtld-servers.net.  ← TLD
# example.com.       172800  IN  NS  ns1.example.com.     ← 위임
# example.com.       86400   IN  A   93.184.216.34        ← 최종 답

TTL과 DNS 캐시 동작

DNS 응답에 포함된 TTL(Time To Live)은 해당 레코드를 캐시에 얼마나 오래 보관할 수 있는지를 초 단위로 지정합니다.

TTL의 트레이드오프
TTL이 긴 경우 (86400초 = 24시간)
  장점: DNS 질의 횟수 ↓, 응답 속도 ↑
  단점: 변경 사항 전파에 최대 24시간

TTL이 짧은 경우 (60초)
  장점: 변경 사항 1분 내 반영
  단점: DNS 서버 부하 ↑, 매번 질의 필요

실무 전략
  평소:       TTL = 86400 (24시간)
  이전 1주전: TTL = 300  (5분으로 낮춤)
  이전 당일:  IP 변경 → 5분 내 전파
  이전 완료:  TTL = 86400 (다시 높임)
dns_query.py
import socket
import struct

def build_dns_query(domain):
    """DNS 질의 패킷 직접 생성"""
    # 헤더: ID, Flags, Questions, Answers, Authority, Additional
    header = struct.pack("!HHHHHH",
        0x1234,  # Transaction ID
        0x0100,  # Flags: Standard query, Recursion Desired
        1,       # Questions: 1
        0, 0, 0  # Answers, Authority, Additional: 0
    )
    
    # 질문 섹션: 도메인을 DNS 형식으로 인코딩
    question = b""
    for part in domain.split("."):
        question += struct.pack("B", len(part)) + part.encode()
    question += b"\x00"  # 도메인 종료
    question += struct.pack("!HH", 1, 1)  # Type: A(1), Class: IN(1)
    
    return header + question

def send_dns_query(domain, dns_server="8.8.8.8"):
    """UDP로 DNS 질의 전송"""
    query = build_dns_query(domain)
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.settimeout(5)
    sock.sendto(query, (dns_server, 53))
    
    response, _ = sock.recvfrom(512)
    sock.close()
    
    # 응답 파싱 (간략)
    answer_count = struct.unpack("!H", response[6:8])[0]
    print(f"도메인: {domain}")
    print(f"DNS 서버: {dns_server}")
    print(f"응답 레코드 수: {answer_count}")
    print(f"응답 크기: {len(response)} bytes")
    
    # A 레코드 IP 추출 (단순 파싱)
    if answer_count > 0 and len(response) >= len(query) + 16:
        offset = len(query) + 12
        if offset + 4 <= len(response):
            ip = socket.inet_ntoa(response[offset:offset+4])
            print(f"IP 주소: {ip}")

send_dns_query("example.com")

캐시는 리졸버뿐 아니라 운영체제와 브라우저에도 존재합니다. DNS 변경 후 캐시가 남아서 안 바뀐다는 문제를 겪을 때, 브라우저 캐시 → OS 캐시 → 리졸버 캐시 순으로 확인해야 합니다.


DNS 실습 체크리스트

상황명령어확인 사항
기본 IP 조회nslookup domainA 레코드 IP
메일 서버 확인dig domain MXMX 레코드, 우선순위
네임서버 확인dig domain NS위임된 권한 서버
도메인 인증 확인dig domain TXTSPF, Google 인증
해석 경로 추적dig +trace domain루트 → TLD → 권한
특정 DNS 사용dig @1.1.1.1 domain리졸버 비교
캐시 초기화ipconfig /flushdns로컬 캐시 제거

다음 절에서는 DNS의 심화 활용과 실무에서 마주치는 보안 이슈를 살펴보겠습니다.

목차