작성일 :

네트워크 문제를 어떻게 진단하는가

“인터넷이 안 돼요.”

가장 흔하지만 가장 모호한 문의입니다. 이 한 문장 뒤에는 수십 가지 원인이 숨어 있을 수 있습니다. 케이블이 빠져 있을 수도 있고, DNS 서버가 응답하지 않을 수도 있고, 방화벽이 특정 포트를 차단하고 있을 수도 있습니다.

이전 글들에서 다루어 온 네트워크 통신의 원리, TCP/IP 스택, 라우팅, DNS 등의 지식은 문제가 발생했을 때 원인을 빠르게 찾아내는 데 직접 쓰입니다.


네트워크는 물리 계층부터 애플리케이션 계층까지 여러 계층이 쌓여 동작합니다. 각 계층은 아래 계층이 정상적으로 동작한다는 전제 위에서 작동하므로, 아래 계층에 문제가 있으면 위 계층도 함께 실패합니다.

예를 들어, DNS가 동작하려면 먼저 IP 통신(L3)이 되어야 하고, IP 통신이 되려면 L2(데이터 링크 계층, 즉 MAC 주소 기반의 물리적 연결) 수준의 연결이 되어야 합니다. 문제 해결의 첫 단계는 이 계층 중 어디서 문제가 발생하는지 찾는 것입니다.


문제 해결 방법론

계층별 접근

앞서 살펴보았듯이 아래 계층이 정상이어야 위 계층도 동작하므로, 아래에서 위로 올라가며 확인하는 것이 기본 원칙입니다.

다만 OSI 7계층 전부를 개별 진단 단위로 삼지는 않습니다.

L1(물리 계층)은 케이블 연결이나 링크 감지처럼 L2 진단 과정에서 함께 확인하고, L5(세션)·L6(표현)은 애플리케이션 프로토콜 내부에서 처리되므로 독립된 진단 단계로 다루지 않습니다.

L7(애플리케이션)에는 HTTP, SMTP, DNS 등 다양한 프로토콜이 속하는데, 그중 DNS는 거의 모든 네트워크 서비스가 의존하는 기반 프로토콜입니다.

이 글에서는 L2, L3, L4, 그리고 DNS(L7) 순서로 진단 도구를 살펴봅니다.

1
2
3
4
5
6
7
8
9
10
확인 순서 (아래 → 위):
┌─────────────────────────────────────────┐
│ L7 DNS          ← dig, nslookup        │
├─────────────────────────────────────────┤
│ L4 전송 계층    ← ss, nc, 방화벽 확인  │
├─────────────────────────────────────────┤
│ L3 네트워크 계층 ← ping, traceroute    │
├─────────────────────────────────────────┤
│ L2 데이터 링크  ← ARP, MAC, 링크 상태  │  ← 여기서 시작
└─────────────────────────────────────────┘

분할 정복

계층별 접근이 “어느 계층에서 막히는가”를 찾는 전략이라면, 분할 정복은 “같은 계층 안에서 어느 구간이 문제인가”를 좁히는 전략입니다. 특히 경로가 길어서 하나씩 확인하기 어려울 때 유용합니다.

네트워크 경로의 중간 지점을 먼저 확인하여 문제 범위를 절반으로 좁힙니다.

1
2
3
4
5
6
7
클라이언트 ─── 스위치 ─── 라우터 ─── 인터넷 ─── 서버
                            │
                     여기까지 정상 응답
                       → 문제는 오른쪽(서버 방향)에 존재

                     여기서 응답 실패
                       → 문제는 왼쪽(클라이언트 방향)에 존재

가설과 검증

증상을 관찰한 뒤 하나의 가설을 세우고, 해당 가설을 검증할 수 있는 최소한의 테스트를 수행합니다.

예를 들어 “DNS 문제”라는 가설이 있다면, 도메인 대신 IP 주소로 직접 접속해 봅니다. IP로 접속이 되면 DNS 문제가 확정되고, IP로도 안 되면 가설을 수정하여 네트워크 경로 문제로 범위를 옮깁니다.


L2 진단: 데이터 링크 계층

명령줄 기반 진단의 첫 단계입니다. L2가 정상이어야 그 위의 IP 통신(L3)이나 포트 연결(L4)도 동작하기 때문에, L2 문제를 먼저 배제하는 편이 효율적입니다.

ARP 테이블 확인

L2 통신 여부를 확인할 때 가장 먼저 살펴볼 곳은 ARP(Address Resolution Protocol) 테이블 입니다. ARP는 통신하려는 상대의 IP 주소로부터 실제 프레임을 전달할 MAC 주소를 알아내는 프로토콜입니다. ARP 테이블에는 이렇게 알아낸 IP-MAC 매핑이 캐시되어 있습니다.

ARP는 같은 서브넷 안에서만 동작합니다. 여기서 “같은 서브넷”이란 스위치(MAC 주소를 보고 프레임을 해당 포트로만 전달하는 L2 장비)로 연결되어 ARP 요청이 직접 도달할 수 있는 L2 브로드캐스트 도메인을 가리킵니다. 다른 서브넷의 호스트와 통신할 때는 패킷을 대신 전달해 줄 게이트웨이의 MAC 주소가 ARP 테이블에 등록됩니다.

1
2
3
4
5
6
7
8
# Linux
ip neigh show
# 또는
arp -a

# 결과 예시:
# 192.168.1.1 dev eth0 lladdr 00:11:22:33:44:55 REACHABLE
# 192.168.1.100 dev eth0 lladdr aa:bb:cc:dd:ee:ff STALE

ARP 테이블의 각 항목은 상태(state)를 가집니다. REACHABLE은 최근 통신이 확인된 정상 상태이며, STALE은 일정 시간이 지나 재확인이 필요한 상태를 뜻합니다. 이 둘은 정상 범위에 해당합니다.

반면 INCOMPLETE은 ARP 요청을 보냈지만 응답을 받지 못한 것이고, FAILED는 ARP 해석이 완전히 실패한 것을 나타냅니다. 이 두 상태가 보인다면 대상 호스트가 네트워크에 연결되어 있지 않거나, ARP 요청에 응답하지 않는 상황입니다. 케이블 연결, VLAN(같은 스위치에서 네트워크를 논리적으로 분리하는 설정) 설정, 대상 호스트의 전원 상태를 먼저 점검합니다.

MAC 주소 문제

MAC 주소는 48비트(6바이트)로 구성되며, L2에서 네트워크 인터페이스를 식별하는 고유 주소입니다. 스위치는 각 포트에 연결된 장비의 MAC 주소를 학습하고, 프레임을 해당 포트로만 전달합니다.

ARP 테이블에 올바른 MAC 주소가 등록되어 있더라도, 같은 네트워크 안에 동일한 MAC 주소가 중복 존재하면 L2 통신은 여전히 실패합니다. 현재 인터페이스의 MAC 주소는 다음과 같이 확인합니다.

1
2
3
# 인터페이스 MAC 확인
ip link show eth0
# link/ether 00:11:22:33:44:55 brd ff:ff:ff:ff:ff:ff

동일한 네트워크 세그먼트(같은 스위치나 허브에 연결된 장비들의 L2 영역)에 같은 MAC 주소를 가진 장비가 두 대 이상 존재하면, 스위치의 학습 테이블에 같은 MAC 주소가 두 포트에 번갈아 나타납니다. 스위치는 가장 최근에 본 포트로 테이블을 갱신하므로, 테이블이 계속 변경됩니다. 이 현상을 MAC flapping이라고 하며, 그 결과 프레임이 올바른 포트로 전달되지 못합니다.


증상으로는 연결이 랜덤하게 끊기거나, 트래픽이 엉뚱한 장비로 향하는 현상이 나타납니다. 가상 머신의 MAC 주소를 수동 설정하거나, 저가 장비에서 고정 MAC을 사용할 때 이런 충돌이 발생하기 쉽습니다.

스위치 포트 상태

ARP 테이블과 MAC 주소에 이상이 없다면, 호스트 인터페이스와 스위치 포트 사이의 물리적 링크 상태를 확인할 차례입니다. 앞에서 ip link show로 MAC 주소를 확인했는데, 같은 명령의 출력에서 링크 상태 플래그도 함께 볼 수 있습니다.

1
2
3
# Linux: 인터페이스 상태
ip link show
# eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> ...

출력의 꺾쇠괄호(< >) 안에 표시되는 핵심 플래그는 두 가지입니다.

  • UP – 운영체제에서 인터페이스를 활성화한 상태(관리적 상태)입니다. 이 플래그가 없으면 ip link set eth0 up으로 활성화해야 합니다.
  • LOWER_UP – 물리적 링크(케이블 연결)가 감지된 상태입니다. 이 플래그가 없으면 케이블이 빠져 있거나 상대 스위치 포트가 비활성 상태입니다.


더 자세한 링크 정보가 필요하면 ethtool을 사용합니다.

1
2
3
4
ethtool eth0
# Link detected: yes
# Speed: 1000Mb/s
# Duplex: Full

Speed는 협상된 링크 속도이고, Duplex는 데이터 전송 방식입니다. Full이면 송신과 수신을 동시에 처리하고, Half이면 한 번에 한 방향씩 교대로 처리합니다.


L3 진단: 네트워크 계층

L2에서 인터페이스 상태와 ARP 테이블이 정상임을 확인했다면, 다음으로 살펴볼 것은 IP 수준의 통신입니다. 패킷이 대상 호스트까지 실제로 도달하는지, 경로에 이상이 없는지, 라우팅 설정이 올바른지를 차례로 검증합니다.

ping의 원리

세 가지 검증 항목 중 첫 번째, 패킷이 대상까지 실제로 도달하는지를 확인하는 도구가 ping입니다. 패킷이 상대에게 닿기는 하는지, 그 왕복에 얼마나 걸리는지를 한 번의 명령으로 확인할 수 있습니다.

내부적으로 ping은 ICMP(Internet Control Message Protocol) – IP 계층에서 오류 보고와 진단 메시지를 전달하기 위한 프로토콜 – 의 Echo Request를 대상 호스트에 보내고, 돌아오는 Echo Reply 유무로 IP 수준의 도달 가능성을 판단합니다.

1
2
3
4
5
6
클라이언트                        대상
    │                              │
    │ ── ICMP Echo Request ──────► │
    │                              │
    │ ◄── ICMP Echo Reply ──────── │
    │                              │

Google의 공개 DNS 서버(8.8.8.8)에 ping을 실행하면 다음과 같은 출력을 볼 수 있습니다.

1
2
3
4
5
6
7
8
9
10
ping 8.8.8.8

# PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
# 64 bytes from 8.8.8.8: icmp_seq=1 ttl=116 time=32.5 ms
# 64 bytes from 8.8.8.8: icmp_seq=2 ttl=116 time=31.8 ms
# ...
#
# --- 8.8.8.8 ping statistics ---
# 5 packets transmitted, 5 received, 0% packet loss, time 4005ms
# rtt min/avg/max/mdev = 31.8/32.1/32.5/0.3 ms

첫째 줄의 “56(84) bytes”는 ICMP 페이로드가 56바이트이고, IP 헤더(20바이트)와 ICMP 헤더(8바이트)를 포함한 전체 패킷 크기가 84바이트라는 뜻입니다.

이 출력에서 눈여겨볼 부분은 네 가지입니다. 응답 여부, RTT, 패킷 손실률, TTL이며, 각각이 알려주는 정보가 다릅니다.

먼저 응답 여부 입니다. 64 bytes from 8.8.8.8 줄이 나타나면 대상에 IP 수준에서 도달 가능하다는 뜻이고, 이 줄 없이 타임아웃된다면 경로상 어딘가에서 패킷이 차단되거나 유실되고 있는 것입니다.


RTT(Round Trip Time) 는 각 줄 끝의 time=32.5 ms 부분으로, 패킷이 대상까지 왕복하는 데 걸린 시간입니다. 같은 대상에 대한 RTT가 측정마다 크게 달라진다면(예: 30 ms에서 갑자기 200 ms로 튀는 경우) 네트워크 혼잡을 의심할 수 있습니다.


패킷 손실률 은 통계 요약의 0% packet loss 부분입니다. 전송한 패킷 중 응답이 돌아오지 않은 비율이며, 0%가 정상입니다. 1% 이상이면 경로상 간헐적 장애가 존재할 가능성이 있고, 100%는 대상이 아예 응답하지 않거나 ICMP가 차단된 상황을 가리킵니다.


마지막으로 TTL(Time To Live) 입니다. 각 응답 줄의 ttl=116 부분으로, 이 값을 통해 패킷이 경유한 라우터 수를 역산할 수 있습니다. IP 패킷이 라우터를 하나 지날 때마다 TTL이 1씩 줄어드는데, 이 동작은 패킷이 경로를 무한히 순환하는 것을 방지하기 위한 장치입니다. 응답에 담긴 TTL은 대상 운영체제가 설정한 초기 TTL에서 경유한 라우터 수만큼 감소한 값입니다. 운영체제마다 초기 TTL이 다른데, Linux는 64, Windows는 128로 설정합니다. 위 출력에서 TTL이 116이므로, 64보다 크고 128에 가깝습니다. 따라서 대상의 초기 TTL이 128이라고 추정하면 128 - 116 = 12개의 라우터를 경유한 것으로 볼 수 있습니다.

ping이 실패하는 이유

ping 출력이 정상이라면 다음 단계로 넘어가면 되지만, 응답이 돌아오지 않을 때가 문제입니다. ping 실패가 곧 연결 불가능을 뜻하지는 않습니다. 에러 메시지 유형에 따라 원인을 좁힐 수 있습니다.

“Destination Host Unreachable”은 패킷이 목적지 네트워크에는 도달했지만, 최종 호스트의 MAC 주소를 ARP로 알아내지 못한 상황입니다. 같은 서브넷 안에서 대상 호스트가 꺼져 있거나 네트워크에 연결되어 있지 않을 때 이 메시지가 나타납니다. 반면, 라우팅 테이블에 목적지로 향하는 경로 자체가 없을 때는 “Network is unreachable”이 나타납니다.


응답 없이 타임아웃되면 방화벽이 ICMP를 차단하거나 대상 서버가 꺼져 있을 가능성이 높습니다.


“Time to live exceeded”는 패킷이 TTL이 0이 될 때까지 목적지에 도달하지 못한 것입니다. 대표적인 원인은 라우팅 루프, 즉 두 대 이상의 라우터가 서로에게 패킷을 되돌려 보내며 무한히 순환하는 상태입니다.


한편, 많은 서버가 보안상 ICMP를 차단합니다. 이 경우 ping이 실패하더라도 실제 서비스(HTTP 등)는 정상 동작하고 있을 수 있습니다. 따라서 ping 실패만으로 “서비스에 접근할 수 없다”고 단정할 수 없으며, 이후 L4 진단에서 다루는 포트 수준의 도달 가능성 확인이 별도로 필요합니다.

traceroute / mtr

ping은 목적지까지의 도달 여부를 알려주지만, 어디에서 막히는지는 알려주지 않습니다. traceroute는 경로상의 각 홉(hop, 패킷이 거치는 각 라우터 지점)을 하나씩 드러내어 병목이나 장애 지점을 특정할 수 있게 합니다.

1
2
3
4
5
6
7
8
traceroute google.com

# traceroute to google.com (142.250.185.46), 30 hops max
#  1  192.168.1.1 (192.168.1.1)  1.234 ms  1.156 ms  1.089 ms
#  2  10.0.0.1 (10.0.0.1)  5.678 ms  5.432 ms  5.321 ms
#  3  * * *  (응답 없음)
#  4  72.14.198.18 (72.14.198.18)  15.432 ms  15.321 ms  15.210 ms
#  5  142.250.185.46 (142.250.185.46)  20.123 ms  20.012 ms  19.987 ms

원리는 TTL을 이용한 역추적입니다. 앞서 ping 섹션에서 살펴보았듯이 IP 패킷은 라우터를 하나 지날 때마다 TTL이 1씩 줄어드는데, traceroute는 이 성질을 이용합니다. TTL 값을 1부터 하나씩 늘려 가며 패킷을 보내면, TTL=1일 때 첫 번째 라우터에서 TTL이 0이 되어 ICMP Time Exceeded 응답이 돌아오고, TTL=2이면 두 번째 라우터에서 같은 일이 일어납니다. 목적지에 도달할 때까지 이 과정을 반복하면 경로상의 모든 라우터가 순서대로 드러납니다. 각 홉에서 기본적으로 3번 측정하기 때문에 출력에 응답 시간이 3개씩 나타납니다.

결과에서 * * *가 나오면 해당 홉에서 ICMP Time Exceeded 응답을 받지 못한 것입니다. 가장 흔한 원인은 해당 라우터가 보안 정책으로 ICMP 응답을 차단하는 경우이고, 응답 시간이 타임아웃(기본 5초)을 초과해도 *로 표시됩니다. 드물게는 비대칭 라우팅 – 요청은 도달하지만 응답이 다른 경로로 돌아오면서 유실되는 경우 – 이 원인이 되기도 합니다.

다만, 중간에 * * *가 있더라도 최종 목적지에 도달한다면 해당 홉만 응답을 차단한 것이므로 정상입니다.


traceroute는 한 번의 스냅샷입니다. 그 순간의 경로는 보여주지만, 간헐적으로 패킷이 유실되는 홉은 잡아내기 어렵습니다.

mtr은 이 한계를 보완합니다. traceroute와 ping을 결합하여 동일 경로에 대해 반복 측정을 수행하고, 각 홉의 패킷 손실률과 지연 시간 통계를 실시간으로 누적합니다. 단발성 traceroute로는 놓치기 쉬운 간헐적 장애를 잡아내는 데 적합한 도구입니다.

1
2
3
4
5
6
7
8
mtr google.com

# 실시간으로 업데이트되는 통계
# Host                   Loss%   Snt   Last   Avg  Best  Wrst
# 1. 192.168.1.1          0.0%    50    1.2   1.3   1.0   2.1
# 2. 10.0.0.1             0.0%    50    5.4   5.5   5.2   6.1
# 3. (no response)       100.0%   50    0.0   0.0   0.0   0.0
# 4. 72.14.198.18         0.0%    50   15.3  15.4  15.1  16.2

Loss%는 해당 홉의 패킷 손실률, Snt는 보낸 패킷 수, Last는 가장 최근 측정값, Avg/Best/Wrst는 각각 평균/최소/최대 응답 시간(ms)입니다.


Loss% 수치를 읽을 때 중요한 것은 해당 홉만 볼 것이 아니라, 그 이후 홉들의 Loss%와 함께 비교하는 것입니다. 특정 홉의 Loss%가 높은데 그 이후 홉들은 정상이라면, traceroute에서 * * *가 나타나는 것과 같은 원리로, 해당 라우터가 ICMP 응답만 제한하는 것이므로 실제 장애는 아닙니다. 반대로 특정 홉부터 이후 모든 홉의 Loss%가 함께 올라간다면, 그 홉이 실제 병목 지점일 가능성이 높습니다.

라우팅 테이블 확인

traceroute로 경로를 추적한 뒤에는, 그 경로가 왜 그렇게 결정되었는지도 살펴볼 차례입니다. 운영체제는 라우팅 테이블 – 목적지별로 어떤 인터페이스와 게이트웨이를 사용할지 정리한 경로 목록 – 을 참조하여 패킷의 다음 행선지를 결정하기 때문입니다.

1
2
3
4
5
6
7
8
# Linux
ip route show
# 또는
route -n

# default via 192.168.1.1 dev eth0 proto dhcp metric 100
# 192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.10
# 10.0.0.0/8 via 192.168.1.254 dev eth0

출력을 한 줄씩 살펴봅니다.

  • default via 192.168.1.1 dev eth0 – 명시적으로 일치하는 경로가 없는 모든 패킷을 192.168.1.1(기본 게이트웨이)로 보내라는 규칙입니다.
  • 192.168.1.0/24 dev eth0 – 192.168.1.0~255 범위가 eth0에 직접 연결된 로컬 서브넷이므로 게이트웨이 없이 통신한다는 뜻입니다.
  • 10.0.0.0/8 via 192.168.1.254 – 10.x.x.x 대역 트래픽을 192.168.1.254 라우터로 넘기라는 정적 경로입니다.


진단 시 가장 먼저 볼 것은 기본 게이트웨이(default via ...)의 존재 여부입니다. 이 항목이 없고 별도의 정적 경로도 설정되어 있지 않다면, 위 예시의 192.168.1.0/24처럼 직접 연결된 서브넷 밖으로 나가는 패킷은 전달되지 않습니다. 그 다음으로는 특정 목적지에 대한 경로가 의도한 인터페이스와 게이트웨이를 가리키는지 확인합니다. 예를 들어, 위 출력에서 10.x.x.x 대역이 192.168.1.254를 거쳐야 하는데 이전 설정의 잔재로 다른 게이트웨이를 가리키고 있다면, 해당 대역의 트래픽은 엉뚱한 방향으로 흘러가게 됩니다.


L4 진단: 전송 계층

ping이 잘 나가고 traceroute 경로도 깨끗하다면, IP 수준의 도달 가능성은 확보된 셈입니다. 하지만 호스트에 패킷이 도착한다고 해서 원하는 서비스에 접근할 수 있는 것은 아닙니다.

IP 도달 가능성과 서비스 도달 가능성은 별개입니다.

특정 포트에서 연결을 받아들이며 대기하는(리슨하는) 프로세스가 없거나, 방화벽이 해당 포트를 차단하고 있으면, IP가 살아 있어도 연결은 실패합니다.

포트 상태 확인

서비스가 실제로 포트를 열고 대기하고 있는지 확인하려면, 서버 측 소켓 상태를 직접 조회해야 합니다. ss(socket statistics)가 그 역할을 합니다. 아래 예시처럼 -l 옵션을 사용하면 현재 리슨(listen) 중인 포트 목록을 확인할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
# ss (socket statistics) - Linux 권장
ss -tuln
# -t: TCP
# -u: UDP
# -l: listening
# -n: 숫자로 표시

# State    Recv-Q Send-Q Local Address:Port   Peer Address:Port
# LISTEN   0      128    0.0.0.0:22          0.0.0.0:*
# LISTEN   0      128    0.0.0.0:80          0.0.0.0:*
# LISTEN   0      128    [::]:443            [::]:*

출력에서 Local Address:Port 컬럼이 리슨 중인 주소와 포트입니다. 0.0.0.0은 모든 IPv4 인터페이스에서 수신한다는 뜻이고, [::]은 모든 IPv6 인터페이스에서 수신한다는 뜻입니다. 위 예시를 읽으면, 22번 포트(SSH)와 80번 포트(HTTP)는 모든 IPv4 주소에서, 443번 포트(HTTPS)는 모든 IPv6 주소에서 연결을 대기하고 있는 상태입니다.

netstat -tuln도 같은 정보를 제공하지만, 최신 Linux에서는 ss가 더 빠르고 출력도 상세합니다.

TCP 연결 상태

리슨 포트뿐 아니라 현재 활성 중인 모든 TCP 연결까지 확인하려면 ss -tan을 사용합니다.

1
2
3
4
5
ss -tan
# State      Recv-Q Send-Q Local Address:Port  Peer Address:Port
# LISTEN     0      128    0.0.0.0:80         0.0.0.0:*
# ESTAB      0      0      192.168.1.10:22    192.168.1.100:54321
# TIME-WAIT  0      0      192.168.1.10:80    10.0.0.5:12345

앞 섹션에서 살펴본 LISTEN은 연결 대기 상태이고, ESTAB(ESTABLISHED의 약어로, ss는 출력 폭을 줄이기 위해 이렇게 표기)은 TCP 3-way handshake가 완료되어 양쪽이 데이터를 주고받을 수 있는 정상 연결 상태입니다. ss 출력에서 이 두 상태가 대부분을 차지한다면 서비스는 정상 동작 중입니다.


TIME-WAIT는 연결이 정상 종료(FIN 교환 완료)된 후 일정 시간 유지되는 상태입니다. Linux에서는 이 시간이 60초로 하드코딩되어 있고, 다른 운영체제에서는 다를 수 있습니다. 종료된 연결이 상태를 유지하는 이유는 TCP의 연결 식별 방식과 관련이 있습니다. TCP는 (출발지 IP, 출발지 포트, 목적지 IP, 목적지 포트)의 네 값 조합, 즉 4-tuple로 연결을 식별합니다. 연결이 종료된 직후 같은 4-tuple로 새 연결을 열면, 이전 연결에서 네트워크를 떠돌던 지연 패킷이 새 연결에 뒤늦게 도착할 수 있습니다. 수신 측은 그 패킷이 이전 연결의 잔재인지 새 연결의 정상 데이터인지 구별할 수 없으므로, 데이터 스트림이 오염됩니다.

TIME-WAIT는 같은 4-tuple의 재사용을 일정 시간 차단하여, 지연 패킷이 완전히 소멸한 뒤에만 새 연결을 허용합니다.

트래픽이 많은 서버에서 TIME-WAIT가 수천 개 쌓이더라도 그 자체로 문제가 되지는 않습니다.


SYN-SENT는 클라이언트가 SYN을 보내고 SYN-ACK를 기다리는 상태, CLOSE-WAIT는 상대방이 FIN을 보냈지만 로컬 애플리케이션이 아직 소켓을 닫지 않은 상태입니다. 어느 쪽이든 소수가 잠깐 나타났다 사라지는 것은 정상입니다.

하지만 지속적으로 쌓인다면 원인을 파악해야 합니다. SYN-SENT가 오래 남아 있다면 서버가 응답하지 않거나 방화벽이 SYN 패킷을 DROP하고 있을 가능성이 높고, CLOSE-WAIT가 누적된다면 애플리케이션이 소켓을 제때 닫지 않는 버그를 의심해야 합니다.

포트 도달 가능성 테스트

ss로 서버 측 소켓 상태를 확인했다면, 클라이언트 측에서 해당 포트에 실제로 닿을 수 있는지도 검증해야 합니다. nc(netcat)는 TCP/UDP 연결을 직접 시도하는 도구입니다. -z 옵션은 데이터를 보내지 않고 연결 가능 여부만 확인하며, -v는 결과를 상세히 출력합니다.


ping은 ICMP 기반이라 특정 포트의 서비스 상태를 알 수 없지만, nc는 실제 TCP 연결을 시도하므로 포트 수준의 도달 가능성을 직접 확인할 수 있습니다.

1
2
3
4
5
6
7
# nc (netcat)
nc -zv example.com 80
# Connection to example.com 80 port [tcp/http] succeeded!

# 실패 시:
nc -zv example.com 12345
# nc: connect to example.com port 12345 (tcp) failed: Connection refused

“succeeded”는 TCP 3-way handshake가 완료되어 해당 포트에서 서비스가 실행 중임을 뜻합니다. “Connection refused”는 대상 호스트에 도달했지만 해당 포트에서 리슨하는 프로세스가 없거나, 방화벽이 REJECT 규칙으로 거부 응답을 보내는 상황입니다. 이 두 경우와 달리, 아무 응답 없이 일정 시간이 지난 뒤 타임아웃이 발생한다면 방화벽이 패킷을 DROP하고 있을 가능성이 높습니다.

방화벽 규칙 확인

nc에서 타임아웃이 발생했다면, 다음으로 살펴볼 곳은 방화벽 규칙입니다. Linux에서는 iptables, nftables, UFW 등 여러 방화벽 프레임워크가 사용됩니다. iptables는 전통적인 방화벽 도구이고, nftables는 이를 대체하기 위해 도입된 후속 프레임워크이며, UFW는 Ubuntu에서 iptables를 쉽게 다루기 위한 프론트엔드입니다.

sudo iptables -L -n -v 명령은 현재 적용된 필터 규칙을 보여줍니다. -n은 DNS 역조회 없이 IP와 포트를 숫자 그대로 표시하고, -v를 붙이면 각 규칙에 매칭된 패킷 수까지 확인할 수 있어 어떤 규칙이 트래픽을 차단하는지 파악하는 데 도움이 됩니다.

1
2
3
4
5
6
7
8
# iptables (Linux)
sudo iptables -L -n -v

# nftables (최신 Linux)
sudo nft list ruleset

# UFW (Ubuntu)
sudo ufw status verbose

출력에서 특정 포트에 대한 DROP 또는 REJECT 규칙이 있다면, 해당 규칙이 nc 타임아웃의 원인일 수 있습니다. DROP은 패킷을 아무 응답 없이 폐기하므로 nc에서 타임아웃으로 나타나고, REJECT는 거부 응답을 돌려보내므로 nc에서 “Connection refused”로 나타납니다.


DNS 진단

L4까지의 진단으로 포트 연결이 정상임을 확인했는데도, 도메인 이름으로 접속하면 실패하는 경우가 있습니다. IP 주소로 직접 접속하면 정상 동작한다면, DNS가 도메인을 올바른 IP로 해석하지 못하고 있을 가능성이 높습니다. dig와 nslookup으로 DNS 해석 과정을 직접 질의하면 어디에서 문제가 발생하는지 특정할 수 있습니다.

dig

DNS 문제를 확인할 때 가장 먼저 꺼내는 도구가 dig(Domain Information Groper)입니다. DNS 질의의 상세한 결과 – 응답 레코드, 응답 시간, 질의에 사용된 DNS 서버 – 를 한 번에 보여주기 때문입니다.

1
2
3
4
5
6
7
8
9
10
11
dig example.com

# ;; QUESTION SECTION:
# ;example.com.                   IN      A
#
# ;; ANSWER SECTION:
# example.com.            3600    IN      A       93.184.216.34
#
# ;; Query time: 45 msec
# ;; SERVER: 8.8.8.8#53(8.8.8.8)
# ;; WHEN: Mon Jan 20 22:31:00 KST 2026

QUESTION SECTION은 질의 내용입니다. IN은 인터넷 클래스(사실상 모든 DNS 질의가 이 클래스), A는 도메인에 대응하는 IPv4 주소를 요청하는 레코드 타입입니다. ANSWER SECTION은 응답 결과로, 3600은 이 응답을 캐시해도 되는 시간(TTL, 초 단위)이며, 마지막의 IP 주소가 실제 해석 결과입니다. Query time으로 응답 시간을, SERVER로 질의에 사용된 DNS 서버를 확인합니다.

기본 출력 외에도 dig는 진단 목적에 따라 옵션을 바꿔 사용할 수 있습니다.

1
2
# 특정 DNS 서버를 지정하여 질의
dig @8.8.8.8 example.com

@ 뒤에 DNS 서버 주소를 붙이면 해당 서버에 직접 질의합니다. 로컬 DNS 서버의 응답이 의심될 때, Google DNS(8.8.8.8)나 Cloudflare DNS(1.1.1.1) 같은 공개 DNS에 같은 도메인을 질의하여 결과를 비교하는 용도로 사용합니다. 로컬 DNS에서만 다른 IP가 반환된다면 로컬 DNS 설정이나 캐시에 문제가 있는 것입니다.

1
2
3
4
# 특정 레코드 타입 질의
dig example.com MX      # 메일 서버 레코드
dig example.com AAAA    # IPv6 주소 레코드
dig example.com NS      # 네임 서버 레코드

기본 dig 명령은 A 레코드(IPv4 주소)를 질의하지만, 도메인 뒤에 레코드 타입을 명시하면 다른 종류의 DNS 레코드를 조회할 수 있습니다. MX는 해당 도메인으로 이메일을 보낼 때 사용할 메일 서버를, AAAA는 IPv6 주소를, NS는 해당 도메인을 관리하는 권한 네임서버를 반환합니다.

1
2
3
# 짧은 출력
dig +short example.com
# 93.184.216.34

+short는 부가 정보 없이 응답 값만 출력합니다. 스크립트에서 IP 주소만 추출하거나, 여러 도메인을 연속으로 확인할 때 유용합니다.

1
2
# 해석 경로 추적
dig +trace example.com

+trace는 DNS 해석이 실제로 거치는 경로를 처음부터 끝까지 보여줍니다. DNS 시리즈에서 다루었듯이, DNS 해석은 루트 네임서버에서 시작하여 TLD(.com) 네임서버, 그리고 해당 도메인의 권한 네임서버까지 단계별로 위임을 따라가는 과정입니다. +trace는 이 위임 경로를 한 단계씩 실행하면서 각 서버의 응답을 출력합니다. 특정 단계에서 응답이 끊기거나 잘못된 서버로 위임된다면, DNS 설정 오류가 어느 단계에 있는지 특정할 수 있습니다.

nslookup

dig가 상세한 DNS 분석에 적합하다면, nslookup은 간결한 출력으로 빠르게 결과를 확인하는 데 적합합니다. 출력 첫 부분의 “Server”는 질의에 사용된 DNS 서버이고, “Non-authoritative answer”는 권한 있는 네임서버(해당 도메인의 레코드를 직접 관리하는 서버)가 아닌 재귀 리졸버(클라이언트 대신 여러 네임서버를 거쳐 답을 찾아오는 중간 DNS 서버)로부터 온 응답임을 나타냅니다. 일반적인 DNS 조회는 대부분 재귀 리졸버를 통해 이루어지므로, 이 표시는 정상입니다.

1
2
3
4
5
6
7
nslookup example.com
# Server:         192.168.1.1
# Address:        192.168.1.1#53
#
# Non-authoritative answer:
# Name:   example.com
# Address: 93.184.216.34

레코드 타입별 세부 분석이나 해석 경로 추적이 필요하면 dig를, 단순히 도메인이 어떤 IP로 해석되는지만 확인하면 nslookup을 사용합니다.

DNS 캐시 문제

dig나 nslookup으로 조회한 결과가 예상과 다르다면 – 예를 들어 레코드를 변경했는데 이전 IP가 계속 반환된다면 – 로컬 DNS 캐시를 의심해야 합니다. 운영체제에는 DNS 응답을 임시로 저장해 두는 로컬 DNS 리졸버 캐시가 있습니다. 이 캐시는 앞서 dig 출력에서 확인한 TTL(응답을 캐시해도 되는 시간) 값에 따라 유지되며, TTL이 만료되기 전까지는 DNS 서버에 다시 질의하지 않고 저장된 응답을 그대로 사용합니다. 따라서 레코드를 변경하더라도 TTL이 남아 있는 동안에는 이전 IP가 계속 반환됩니다. 캐시를 수동으로 비우면 즉시 DNS 서버에 새로 질의하여 변경된 레코드를 받아올 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Linux (systemd-resolved 사용 환경):
# 캐시에 저장된 항목 수와 히트/미스 통계를 확인합니다.
# 플러시 전후로 실행하면 캐시가 실제로 비워졌는지 검증할 수 있습니다.
resolvectl statistics

# 캐시 플러시
sudo resolvectl flush-caches

# macOS
sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder

# Windows
ipconfig /flushdns

systemd-resolved를 사용하지 않는 Linux 환경(예: dnsmasq, unbound 등을 직접 운영하는 경우)에서는 해당 서비스의 캐시 플러시 명령을 사용합니다.

전파 지연

로컬 캐시를 비운 뒤에도 일부 지역에서 이전 IP가 반환된다면, 문제는 전파 지연(propagation delay)에 있습니다. 전파 지연이란, DNS 레코드를 변경한 뒤 그 변경이 전 세계의 리졸버에 반영되기까지 걸리는 시간을 가리킵니다.

앞 섹션에서 다룬 캐시 플러시는 내 머신의 로컬 캐시만 비웁니다. 하지만 전 세계에는 수많은 재귀 리졸버(ISP, 기업, 공개 DNS 등)가 각자 독립된 캐시를 운영하고 있으며, 이 캐시들을 외부에서 강제로 비울 수는 없습니다. 각 리졸버는 자신이 캐시한 레코드의 TTL이 만료되어야 비로소 새 레코드를 가져옵니다.


TTL이 3600초(1시간)로 설정되어 있다면, 레코드 변경 직전에 캐시한 리졸버는 최대 1시간 뒤에야 새 IP를 반환합니다. 각 리졸버가 캐시한 시점이 서로 다르므로, 같은 시각에 질의해도 리졸버마다 다른 IP를 돌려줄 수 있습니다. 이것이 “일부 지역에서만 이전 IP가 나오는” 현상의 원인입니다.


서로 다른 공개 DNS 서버에 직접 질의하면, 각 리졸버의 캐시 상태가 다르므로 전파가 어느 정도 진행되었는지 확인할 수 있습니다.

1
2
3
4
# 여러 DNS 서버에서 확인
dig @8.8.8.8 example.com +short    # Google DNS
dig @1.1.1.1 example.com +short    # Cloudflare DNS
dig @208.67.222.222 example.com +short  # OpenDNS

세 서버의 응답이 동일하면 주요 공개 리졸버까지는 전파가 완료된 것이고, 서로 다른 IP가 반환된다면 아직 일부 리졸버에 이전 캐시가 남아 있는 상태입니다.


전파 지연 자체는 DNS의 구조적 특성이므로 완전히 없앨 수는 없지만, 레코드를 변경하기 전에 TTL을 미리 낮추어 두면 영향을 줄일 수 있습니다. 예를 들어, 변경 예정 시각보다 현재 TTL(예: 3600초) 이상 전에 TTL을 60초로 낮추어 두면, 실제 변경 시점에는 대부분의 리졸버가 60초 이내에 새 레코드를 가져갑니다.


마무리

이 글에서는 L2(ARP, MAC, 링크 상태), L3(ping, traceroute, 라우팅 테이블), L4(ss, nc, iptables), DNS(dig, nslookup, 캐시)의 순서로 진단 도구를 살펴보았습니다. 네 영역에 걸쳐 도구는 다양하지만, 이 글에서 따른 진단 원칙은 하나입니다. 아래 계층이 정상임을 먼저 확인한 뒤 위로 올라가는 것입니다.


이 원칙이 중요한 이유는, 위 영역의 도구가 아래 계층이 정상이라는 전제 위에서 동작하기 때문입니다. L2 링크가 끊어져 있으면 ping은 응답을 받을 수 없고, IP 통신 자체가 안 되면 DNS 질의 패킷도 DNS 서버에 도달하지 못합니다. 증상이 나타난 곳부터 보고 싶은 유혹이 있지만, 아래부터 순서대로 배제해 가는 편이 결과적으로 더 빠릅니다.


Part 2에서는 패킷 분석 도구를 다룹니다.


관련 글

시리즈

Tags: DNS, netstat, ping, traceroute, 네트워크, 디버깅

Categories: ,