네트워크 팀과 소통하다 보면 “그 서버로 telnet 한번 때려보세요”, “traceroute 결과 좀 주세요” 같은 얘기를 자주 듣는다. 그런데 정작 그 도구가 뭘 하는 도구인지, 왜 그걸 쓰라는 건지 모르면 결과를 봐도 해석을 못 한다. 특히 네트워크 팀에서 “ACL 열어드렸어요”라고 했는데 telnet이 안 되면, 이게 네트워크가 안 열린 건지, 목적지 서버 문제인지 헷갈린다. 이 글에서는 TCP/UDP의 차이부터 시작해서, 상황별로 어떤 진단 도구를 왜 쓰는지 정리한다.
먼저 TCP vs UDP
진단 도구를 이해하려면 TCP와 UDP의 차이를 먼저 알아야 한다.
도구마다 “TCP만 되는 것”, “UDP도 되는 것”이 갈리고, 그 이유가 전부 이 차이에서 나오기 때문이다.
| TCP | UDP | |
|---|---|---|
| 연결 | 연결 지향 (3-way handshake로 연결을 맺고 시작) | 비연결 (그냥 보냄) |
| 신뢰성 | 상대가 받았는지 확인(ACK)하고 유실 시 재전송, 순서 보장 | 전송 계층은 도착 보장·재전송을 안 함 (필요하면 앱이 직접 처리) |
| 대표 사용처 | HTTP, DB, SSH 등 대부분 | DNS, 실시간 스트리밍, 일부 로그 전송 |
핵심은 TCP는 통신 시작 전에 “연결”이라는 단계를 거친다는 점이다.
1
2
3
4
5
[TCP 3-way handshake]
출발지 ---- SYN ----> 목적지 "연결하자"
출발지 <-- SYN/ACK -- 목적지 "그래 하자"
출발지 ---- ACK ----> 목적지 "좋아 시작"
이 handshake 덕분에 TCP는 “포트가 열려 있나?”를 연결을 시도해보는 것만으로 확인할 수 있다. 상대가 SYN에 응답하면 열린 것, 거부하면 닫힌 것이다. 뒤에 나올 telnet이 바로 이 성질을 이용한다.
반면 UDP는 handshake가 없다. 그냥 패킷을 던질 뿐이고, 상대가 응답할 의무도 없다. 그래서 “이 UDP 포트가 열려 있나?”를 확인하기가 근본적으로 어렵다. 응답이 없을 때 그게 ① 포트는 열렸지만 앱이 UDP라 굳이 답을 안 한 건지, ② 방화벽에서 패킷이 버려진 건지 구분할 수 없기 때문이다. (TCP라면 열린 포트는 앱이 뭘 하든 커널이 SYN/ACK로 반드시 응답하지만, UDP엔 그런 자동 응답이 없다. 그래서 telnet으로는 UDP를 진단할 수 없다 — telnet은 TCP handshake 전용이다.)
왜 HTTP는 TCP, DNS는 UDP를 쓸까
“UDP는 응답이 없다”가 아니라 “응답을 프로토콜 차원에서 보장하지 않는다”는 뜻이다. DNS도 당연히 도메인에 대한 IP 응답을 받아야 하고 실제로 받는다. 차이는 그 신뢰성을 누가 책임지느냐다. TCP는 전송 계층이 알아서 재전송·순서를 챙기지만, UDP는 “응답을 기다리고 안 오면 재시도하는” 로직을 애플리케이션이 직접 구현해야 한다.
- HTTP가 TCP인 이유: HTTP는 HTML·이미지·JSON 같은 콘텐츠를 주고받는데, 이건 여러 패킷에 나뉘고 하나라도 빠지거나 순서가 틀리면 내용이 깨진다. 완전하고 순서 맞는 전달이 필수라서, 그걸 전송 계층이 보장해주는 TCP가 맞다. 연결 수립 오버헤드는 큰 전송 전체로 나눠지니 감수할 만하다.
- DNS가 UDP인 이유: DNS는 보통 작은 질의 하나 보내고 응답 하나 받으면 끝이다. 이 짧은 1회성 통신에 매번 TCP 핸드셰이크(왕복 1.5번)를 치르는 건 낭비다. 그래서 가볍고 빠른 UDP를 쓰고, 응답을 못 받으면 리졸버가 스스로 재질의하거나(응답이 너무 크면) TCP로 폴백한다.
1
2
TCP: SYN → SYN/ACK → ACK (연결에만 왕복 1.5번) → 요청 → 응답 ...
UDP: 질의 → 응답 (딱 왕복 1번)
정리하면, 주고받는 데이터가 크고 정확·순서가 중요하면 TCP(HTTP), 짧고 가볍고 약간의 유실을 앱이 감당할 수 있으면 UDP(DNS). 도구가 TCP/UDP로 갈리는 이유도 결국 여기서 나온다.
그 순서·재전송은 어디서 보장해줄까 — 라우터가 아니다
TCP의 순서 보장·재전송은 중간 네트워크 장비(라우터)가 아니라, 양 끝 호스트의 OS 커널 안에 있는 TCP 스택이 해준다.
- 라우터는 L3(IP) 담당이라, 각 패킷을 독립적으로 포워딩할 뿐이다. 혼잡하면 그냥 버리기도 하고, 경로가 갈리면 순서가 뒤섞이기도 한다. 이 패킷들이 하나의 TCP 연결에 속하는지 알지도, 신경 쓰지도 않는다.
- TCP는 L4(전송) 담당이고 양 끝 호스트에만 존재한다. TCP는 보내는 바이트마다 시퀀스 번호를 붙여서, 뒤섞여 도착해도 받는 쪽이 버퍼에서 순서를 맞추고, ACK가 안 오는 데이터는 보낸 쪽이 재전송한다.
즉 신뢰성은 네트워크 중간이 아니라 양 끝점에서 책임진다(end-to-end 원칙). 라우터가 패킷을 아무리 흘려도 양 끝 TCP가 복구해준다. UDP는 이 L4 로직이 없으니 라우터가 흘린 게 그대로 앱에 전달될 뿐이다.
telnet — 특정 포트로 TCP 연결이 되나?
telnet 목적지 포트는 그 포트로 TCP 3-way handshake를 딱 한 번 시도하는 도구다.
원래는 원격 접속용 프로토콜이지만, 특정 포트로 TCP 연결이 되는지 확인하는 용도로도 쓴다.
1
telnet 10.0.1.20 8080
결과는 세 갈래로 갈리고, 각각이 서로 다른 원인을 가리킨다.
① 연결 성공 — Connected to 10.0.1.20 → 네트워크 경로도 뚫려 있고, 목적지에서 그 포트로 애플리케이션이 리슨 중이다. 정상.
② 즉시 거부 — Connection refused (바로 튕김) → 패킷이 목적지 서버까지 도달은 했다는 뜻이다. 서버가 “그 포트엔 아무도 없다”며 RST를 돌려보낸 것이다. → 즉 네트워크 ACL은 열려 있고, 목적지에 그 포트로 뜬 애플리케이션이 없다(앱이 안 떴거나 다른 포트에 떠 있다). → 서버/앱 쪽에서 볼 문제.
③ 타임아웃 — Connection timed out (한참 멈춰 있다가 실패) → SYN을 보냈는데 아무 응답도 안 온다. 중간의 방화벽/ACL이 패킷을 조용히 버린(drop) 것이다. → 네트워크(방화벽/ACL) 문제일 가능성이 높다. (거부는 “누가 응답이라도 해준” 것, 타임아웃은 “아무도 대답조차 안 한” 것)
Connection refused= 네트워크는 뚫렸다(→ 서버/앱 문제),Connection timed out= 네트워크에서 막혔을 가능성. 네트워크 팀이 “ACL 열었다”고 했는데 telnet이 안 될 때, 이 둘 중 어느 쪽인지부터 확인하면 누구에게 물어봐야 할지가 갈린다.
nc (netcat) — telnet으로 안 되는 것까지
nc(netcat)는 telnet의 상위호환이다.
TCP뿐 아니라 UDP도 되고, 연결 시도만 하는 게 아니라 직접 포트를 열어 리슨하거나 데이터를 전송할 수도 있다.
포트 확인 (telnet 대체)
1
nc -zv 10.0.1.20 8080
-z: zero-I/O 모드. 애플리케이션 데이터(페이로드)는 안 보내지만 TCP 연결 자체는 telnet처럼 3-way handshake로 끝까지 맺어본다. 연결이 맺어지면 열린 것으로 보고 바로 끊는다. (처음에 헷갈렸던nz가 바로 이nc -z다)-v: 결과를 자세히 출력
UDP 확인
1
nc -zvu 10.0.1.20 514
-u: UDP로 시도한다. 단 앞서 말했듯 UDP는 응답이 없을 수 있어 결과가 애매하다(open인지filtered인지 확신하기 어렵다). 확실히 하려면 목적지에서 패킷이 실제로 수신되는지tcpdump로 같이 봐야 한다.
포트를 열어 리슨 — 네트워크 경로만 순수하게 테스트
telnet ②번(Connection refused) 상황에서, 목적지에 아직 앱이 배포되지 않았으면 네트워크가 열려 있어도 refused가 나서 ACL이 열렸는지 확인할 수 없다. 이때 목적지 서버에 nc로 임시 리슨을 띄우면 “앱 유무” 변수를 제거하고 네트워크 경로만 검증할 수 있다.
1
2
3
4
5
# 목적지 서버에서: 8080 포트를 임시로 열어둔다
nc -l 8080
# 출발지 서버에서: 그 포트로 연결이 되는지 확인
nc -zv 10.0.1.20 8080
→ 여기서 연결이 되면 ACL은 확실히 열린 것이다. 앱 배포 전에 네트워크만 먼저 검증할 때 쓴다.
ss / netstat — 내 서버에서 그 포트가 리슨 중인가
지금까지는 전부 “출발지에서 목적지로 쏴보는” 도구였다.
ss(와 옛날 도구 netstat)는 반대로, 지금 이 서버에서 어떤 포트가 열려(리슨) 있는지를 본다. telnet ②번(Connection refused)의 짝이다 — refused가 나왔다면 “목적지에 그 포트로 뜬 앱이 없다”는 뜻인데, 그걸 목적지 서버에 직접 들어가서 확인할 수 있다.
동작이 앞의 도구들과 근본적으로 다르다. ss는 네트워크로 패킷을 하나도 안 보낸다. 대신 커널이 들고 있는 소켓 테이블을 그대로 읽어 보여준다(ss는 netlink, netstat은 /proc/net/tcp 같은 파일). 그래서 “지금 이 서버가 실제로 리슨 중인 포트”를 네트워크 왕복 없이 즉시 알 수 있다.
1
2
3
ss -tlnp # 리슨 중인 TCP 포트 전부
ss -tlnp | grep 8080 # 8080을 리슨 중인 프로세스가 있나
ss -ulnp # 리슨 중인 UDP 포트
-tTCP /-uUDP,-l리슨 중인 것만,-n이름 해석 없이(숫자 그대로),-p프로세스명까지
언제 쓰나
- 출발지에서 telnet이
refused다 → 목적지 서버에 들어가ss -tlnp | grep 8080→ 아무것도 안 나오면 앱이 그 포트로 안 떠 있는 것(확정). 다른 포트로 떠 있으면 포트 설정 실수. 0.0.0.0:8080으로 떠 있으면 모든 IP에서 접근 가능,127.0.0.1:8080(localhost)으로만 떠 있으면 외부에서는 접근 불가 — 이것도 refused의 흔한 원인이다.
netstat -tlnp도 같은 일을 하지만, 요즘 리눅스에선 더 빠른ss가 표준이다. (netstat은net-tools패키지라 없는 서버도 많다)
traceroute — 목적지까지 경로, 어디서 끊기나
telnet/nc가 “목적지 포트가 열렸나”라는 최종 결과만 알려준다면,
traceroute는 출발지에서 목적지까지 거쳐 가는 라우터(홉)들을 하나씩 보여준다.
“어디까지 갔다가 막히는가”를 볼 때 쓴다.
traceroute는 TTL(Time To Live)을 이용한다. 패킷의 TTL은 홉을 하나 지날 때마다 1씩 줄고 0이 되면 버려지는데, 이걸 역이용한다.
1
2
3
TTL=1 로 전송 → 첫 번째 라우터에서 0이 되어 버려짐 → 그 라우터가 "시간 초과" 응답 → 1번 홉 IP 획득
TTL=2 로 전송 → 두 번째 라우터에서 버려짐 → 2번 홉 IP 획득
TTL=3 ... 목적지에 닿을 때까지 반복
1
traceroute 10.0.1.20
읽는 법
- 홉이 목적지까지 쭉 이어지면 경로 정상.
- 중간부터
* * *만 뜨고 멈추면, 그 지점 이후로 막힌 것이다(방화벽이 차단했거나 경로가 없음). - 특정 홉부터 응답 시간이 확 커지면 그 구간이 병목(지연)이다.
주의: 방화벽이
traceroute용 패킷(기본 UDP/ICMP)만 막고 실제 서비스 포트(TCP)는 열어둔 경우,* * *가 떠도 정작 서비스는 정상일 수 있다. 그래서 경로 확인은 traceroute, 포트 확인은 telnet/nc로 역할이 다르다. 특정 TCP 포트 기준으로 경로를 추적하려면traceroute -T -p 8080처럼 TCP 모드를 쓴다.
nmap — 열린 포트/서비스 전체를 훑는다
telnet/nc가 “이 포트 하나 열렸나”라면,
nmap은 “이 호스트에 열린 포트가 뭐뭐 있나”를 한 번에 훑는 포트 스캐너다.
“훑는다”는 건, 포트 하나하나에 SYN 패킷을 보내고 응답을 보는 동작을 포트 범위만큼 반복하는 것이다. SYN/ACK가 오면 열림, RST가 오면 닫힘, 아무 응답도 없으면 필터링(방화벽)으로 판단한다. 기본 SYN 스캔은 열린 걸 확인해도 handshake를 끝까지 맺지 않고 바로 RST로 끊어버려서(half-open), telnet/nc가 매번 연결을 완성하는 것보다 빠르다.
1
2
3
4
nmap -p 1-1000 10.0.1.20 # 1~1000번 포트 스캔
nmap -p 8080 10.0.1.20 # 특정 포트만
nmap -sV 10.0.1.20 # 열린 포트의 서비스/버전까지 탐지
nmap -sU -p 514 10.0.1.20 # UDP 스캔
언제 쓰나
- 여러 포트를 한꺼번에 확인하고 싶을 때.
- 목적지에 어떤 서비스가 떠 있는지 모를 때 전체를 파악.
- 방화벽 정책 검증 — 열려야 할 포트만 열리고 나머진 닫혔는지.
주의: 포트 스캔은 보안 장비가 침입 시도로 감지할 수 있다. 사내 서버라도 권한/합의 없이 대량 스캔하지 말 것. UDP 스캔(
-sU)은 앞서 말한 UDP 특성 때문에 느리고 결과도 부정확하다.
tcpdump — 마지막 수단: 패킷을 직접 본다
앞의 도구들이 “연결이 되나 / 포트가 열렸나”를 판단해준다면,
tcpdump는 실제로 오가는 패킷 자체를 캡처해서 보여준다.
가장 저수준이고, 위 도구들로 원인이 안 잡힐 때 쓴다.
ss처럼 이 도구도 트래픽을 만들어내지 않는다. 커널의 패킷 필터(BPF)에 필터 조건을 걸어두고, NIC를 드나드는 프레임 중 조건에 맞는 것의 복사본을 그대로 떠서 보여줄 뿐이다(수동 캡처). 그래서 telnet/nc가 만든 실제 패킷이 이 서버까지 왔는지를 있는 그대로 관찰할 수 있다.
1
2
3
tcpdump -i eth0 port 8080 # eth0에서 8080 포트 트래픽
tcpdump -i any host 10.0.1.20 # 특정 호스트와 오가는 트래픽
tcpdump -i any port 8080 -n # 이름 해석 없이(-n) 빠르게
이게 telnet의 추측을 ‘사실’로 확정해준다. 앞에서 telnet 결과로 “네트워크 문제일 것 같다 / 서버 문제일 것 같다”를 추측했는데, tcpdump를 양쪽 서버에서 동시에 찍으면 확정할 수 있다.
- telnet이 timeout인데 목적지에서
tcpdump를 떠보니SYN이 도착조차 안 함 → 중간 네트워크에서 막힌 게 확실하다. (네트워크 팀에게) SYN은 도착했는데 서버가RST를 돌려보냄 → 네트워크는 뚫렸고 앱 문제 확정이다. (서버/앱 담당에게)
즉 “패킷이 실제로 여기까지 왔는가”를 눈으로 확인해서, telnet의 refused/timeout 추측을 근거 있는 사실로 바꿔준다.
정리 — 상황별 도구 선택
| 이럴 때 | 이 도구 |
|---|---|
| 이 TCP 포트로 연결돼? | telnet, nc -zv |
| (내 서버에서) 그 포트가 리슨 중인가? | ss -tlnp |
| UDP 포트를 확인하고 싶어 | nc -u, nmap -sU (+ tcpdump로 실제 수신 확인) |
| 앱 배포 전에 네트워크(ACL)만 검증 | 목적지 nc -l + 출발지 nc -zv |
| 목적지까지 경로가 어디서 끊겨? | traceroute |
| 열린 포트/서비스 전체를 파악 | nmap |
| 위로도 원인이 안 잡혀, 패킷을 직접 봐야겠어 | tcpdump |