📚 네트워크 · 패킷 분석 › 02. 포트 · 프로토콜 분석 — 58편
이전 글: 57. Destination Port · 다음 글: 59. Established Connection
Listening Port는 서버 프로그램이 "이 주소·포트로 오는 연결을 받겠다"고 운영체제에 등록해 둔 포트입니다. 클라이언트의 연결 요청은 LISTEN 중인 포트가 있어야만 받아들여집니다.
이 글은 LISTEN 소켓이 어떻게 생기고, 어디까지 열려 있으며, 출력에서 어떻게 읽는지를 다룹니다. 서버에서 불필요한 LISTEN을 찾아 정리하는 점검 절차는 89. 비표준 포트에서 동작하는 서비스 식별과 Listening Port 보안 점검에서 다룹니다.
| 구분 | TCP | UDP |
|---|---|---|
| 대기 방식 | listen() 호출 → LISTEN 상태 | 연결 개념이 없어 bind()만 하고 대기 |
ss 상태 표시 | LISTEN | UNCONN |
Windows netstat 표시 | LISTENING | 상태 칸 비어 있음 (*:*) |
| 확인 옵션 | ss -tln | ss -uln |
TCP 서버가 LISTEN 상태가 되는 과정입니다.
socket() 소켓 생성
↓
bind(주소, 포트) "어느 주소의 몇 번 포트"인지 지정
↓ (1024 미만이면 권한 필요 — 53편)
listen(backlog) LISTEN 상태 진입, 연결 대기열 크기 지정
↓
SYN 도착 → 커널이 Handshake 처리 → 완료된 연결을 accept 대기열에 보관
↓
accept() 프로그램이 대기열에서 연결을 꺼냄
→ 새 소켓(ESTABLISHED) 생성, LISTEN 소켓은 그대로 대기 계속
LISTEN 소켓은 연결 하나를 받아도 사라지지 않고 계속 다음 연결을 기다립니다. 실제 데이터는 accept로 생긴 별도의 소켓이 주고받습니다(59. Established Connection, 60. TCP Connection과 Port).
바인드 주소가 접근 범위를 결정합니다.
| 바인드 주소 | 의미 | 접근 가능 범위 |
|---|---|---|
0.0.0.0 | 모든 IPv4 인터페이스 | 네트워크상 누구나 (방화벽이 허용하면) |
[::] | 모든 IPv6 인터페이스 | Linux에서는 설정(net.ipv6.bindv6only, 기본 0)에 따라 IPv4 연결도 함께 받을 수 있음 |
127.0.0.1 / [::1] | 루프백 | 같은 호스트 내부에서만 |
192.168.10.20 등 특정 IP | 그 인터페이스만 | 해당 IP로 들어오는 연결만 |
ss -tlnp 출력의 각 열을 LISTEN 소켓 기준으로 읽는 법입니다.
| 열 | LISTEN 소켓에서의 의미 |
|---|---|
Recv-Q | 현재 accept 대기열에 쌓인 연결 수 (계속 크면 프로그램이 연결을 제때 못 꺼냄) |
Send-Q | 대기열 최대 크기(backlog) |
Local Address:Port | 바인드 주소와 포트 — 접근 범위 판단 |
Peer Address:Port | * 또는 0.0.0.0:* — 아직 상대가 없음 |
Process | 포트를 연 프로세스·PID (root 권한으로 실행해야 전체가 보임) |
LISTEN ≠ 외부에서 접근 가능이라는 점도 중요합니다.
서버에서 ss로 LISTEN 확인 ─→ [호스트 방화벽 firewalld/ufw/Windows 방화벽]
↓ 허용?
[네트워크 방화벽·보안그룹]
↓ 허용?
외부에서 실제 도달 가능
그래서 "서버 내부에서 본 LISTEN 목록"과 "외부에서 본 열린 포트"는 다를 수 있으며, 두 결과를 비교하는 것 자체가 점검 방법이 됩니다.
실습 예시 — 본인 소유 Linux VM에서 바인드 주소에 따른 차이를 확인합니다(Rocky/Ubuntu 공통).
# 터미널 1: 루프백에만 바인드한 테스트 서버 (종료는 Ctrl+C)
python3 -m http.server 8081 --bind 127.0.0.1
# 터미널 2: LISTEN 확인
sudo ss -tlnp '( sport = :8081 )'
# 같은 VM에서는 접속됨, 다른 VM에서 192.168.10.20:8081로는 거부됨
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8081/
출력 형식 예시(값은 환경마다 다름, 일부 열 생략):
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 5 127.0.0.1:8081 0.0.0.0:* users:(("python3",pid=3310,fd=3))
호스트 방화벽 허용 상태도 함께 봅니다.
# Rocky Linux (firewalld)
sudo firewall-cmd --list-all
# Ubuntu (ufw)
sudo ufw status verbose
Windows에서는 다음과 같습니다(예시).
netstat -ano | findstr LISTENING
Get-NetTCPConnection -State Listen | Select-Object LocalAddress, LocalPort, OwningProcess
0.0.0.0으로 바인드되면, 방화벽 한 겹에만 의존하게 됩니다.Recv-Q가 backlog에 닿아 있으면 새 연결이 밀리며, 과부하나 SYN Flood 영향을 의심할 수 있습니다.| 흔적 위치 | 확인할 수 있는 것 |
|---|---|
서버 ss/netstat, EDR | LISTEN 포트, 바인드 주소, 프로세스, 신규 LISTEN 발생 시점 |
| 취약점 점검·외부 스캔 결과 | 외부에서 실제로 도달 가능한 포트 |
| 방화벽 로그 | 해당 포트로의 접근 시도와 허용·차단 |
| 서비스 관리 기록 | systemctl·Windows 서비스 등록 여부 |
관제자가 확인할 질문
0.0.0.0 vs 127.0.0.1)?오탐 주의: ss에서 Process가 비어 보이는 것은 권한 부족 때문일 수 있으므로 sudo로 다시 확인합니다. 짧게 떴다 사라지는 LISTEN(설치 중인 프로그램, 개발 도구)도 흔하므로 시점과 실행 계정을 함께 봅니다.
bind() → listen()으로 생기며, TCP는 LISTEN, UDP는 UNCONN으로 표시됩니다.0.0.0.0, 127.0.0.1, [::], 특정 IP)가 접근 가능 범위를 결정합니다.