0.0.0.0의 정체: 바인딩, 라우팅, DHCP의 와일드카드 주소

0.0.0.0의 정체: 바인딩, 라우팅, DHCP의 와일드카드 주소

Docker 로그나 서버 시작 메시지에서 0.0.0.0을 본 적 있으시죠?

Listening on 0.0.0.0:3000
0.0.0.0:8080->80/tcp

127.0.0.1은 “내 컴퓨터”라는 게 떠올라도, 0.0.0.0은 정확히 무슨 의미인지 한 번에 답하기가 의외로 어렵습니다. 사실 이 주소는 맥락에 따라 의미가 꽤 다른데요. 서버 바인딩에서는 “모든 인터페이스”, 라우팅 테이블에서는 “기본 게이트웨이로 가는 모든 트래픽”, DHCP에서는 “아직 IP가 없다”는 의미로 해석됩니다.

이번 포스팅에서는 0.0.0.0이라는 주소가 어떤 정체이고, 각 맥락에서 어떤 약속으로 쓰이는지 정리해보겠습니다.

0.0.0.0의 정체

표준 문서(RFC 1122)에서는 0.0.0.0을 “unspecified address”라고 부릅니다. 직역하면 “지정되지 않은 주소”인데요. 인터넷 표준에서 일종의 와일드카드 또는 자리표시자(placeholder) 역할을 하는 특수 주소입니다.

핵심은 이 주소가 통신의 실제 주체가 될 수 없다는 점이에요. 어떤 컴퓨터도 자기 IP를 0.0.0.0이라고 영구히 가지고 있지 않습니다. 대신 “여기에 들어갈 진짜 주소가 정해지기 전” 또는 “구체적인 주소를 특정하지 않겠다”는 신호로 사용되죠.

서버 바인딩에서의 의미

가장 자주 보는 맥락이 서버 바인딩입니다. 서버 프로세스가 어떤 주소에 바인딩한다는 건 “어느 네트워크 인터페이스에서 들어오는 연결을 받겠다”는 선언인데요. 127.0.0.1에 바인딩하면 루프백 인터페이스에서만 받고, 와이파이 어댑터(192.168.0.10)에 바인딩하면 와이파이로만 받습니다.

0.0.0.0에 바인딩한다는 건 “이 머신이 가진 모든 네트워크 인터페이스에서 받겠다”는 뜻입니다. 루프백, 와이파이, 이더넷, VPN 가상 인터페이스, 도커 브리지 등 운영체제가 인식하는 모든 IP에서 동시에 접속을 받는 거죠.

0.0.0.0과 127.0.0.1의 차이
# 외부에서 접근 불가
python -m http.server 3000 --bind 127.0.0.1

# 같은 와이파이의 다른 기기에서도 접근 가능
python -m http.server 3000 --bind 0.0.0.0

이 차이는 보안과 직결됩니다. 127.0.0.1 바인딩은 자연스럽게 “내 컴퓨터 안”이라는 경계를 만들어 주지만, 0.0.0.0 바인딩은 그 경계를 다 풀어버려요. 공유 와이파이에서 인증 없이 0.0.0.0으로 개발 서버를 띄우면 옆자리 사람이 그 서버에 그대로 들어올 수 있다는 뜻입니다.

많은 프레임워크가 기본값으로 127.0.0.1을 쓰는 이유가 여기에 있어요. 명시적으로 외부 접근이 필요한 상황이 아니라면 굳이 위험을 키우지 않겠다는 안전한 기본값입니다.

Docker에서 0.0.0.0이 자주 등장하는 이유

Docker 환경에서는 0.0.0.0을 거의 매번 마주치게 됩니다. 이유는 컨테이너의 네트워크 격리 구조와 관련이 있는데요.

컨테이너 내부에서 서버를 127.0.0.1에 바인딩하면 그 컨테이너 안에서만 접근할 수 있습니다. 컨테이너 입장에서 127.0.0.1은 “이 컨테이너 자기 자신”이지, 호스트 머신이 아니거든요. docker exec로 컨테이너에 들어가면 그 서버에 접근할 수 있지만, 호스트나 다른 컨테이너에서는 접근할 수 없습니다.

이러면 호스트에서 접속 불가
CMD ["python", "server.py", "--bind", "127.0.0.1"]

그래서 Docker 컨테이너 안에서 도는 서버는 보통 0.0.0.0에 바인딩합니다. 컨테이너의 모든 네트워크 인터페이스(컨테이너 자신의 가상 IP, 도커 브리지 IP 등)에서 받겠다는 뜻이라, docker run -p 8080:80으로 포트를 매핑하면 호스트에서 접근할 수 있게 되죠.

컨테이너 외부에서도 접근 가능
CMD ["python", "server.py", "--bind", "0.0.0.0"]

docker ps 출력에서 자주 보이는 0.0.0.0:8080->80/tcp도 이 맥락이에요. 호스트의 모든 인터페이스 8080 포트가 컨테이너의 80 포트로 포워딩되고 있다는 뜻입니다.

라우팅 테이블의 0.0.0.0/0

route 명령이나 ip route 명령으로 라우팅 테이블을 보면 0.0.0.0/0이라는 항목이 보이실 거예요.

라우팅 테이블 일부
$ ip route
default via 192.168.0.1 dev wlan0
0.0.0.0/0 via 192.168.0.1 dev wlan0

0.0.0.0/0CIDR 표기에서 prefix 길이가 0이라는 뜻이고, 결과적으로 “모든 IP 주소”를 가리킵니다. 앞 0비트가 매칭 기준이니 어떤 주소든 이 규칙에 해당되는 거죠.

라우팅 테이블에서 0.0.0.0/0은 보통 기본 게이트웨이(default gateway)를 정의할 때 쓰입니다. “다른 더 구체적인 규칙에 안 걸리는 모든 트래픽은 이쪽으로 보내라”는 의미인데, 가정용 인터넷 환경이라면 보통 공유기(192.168.0.1 같은 주소)가 그 대상이에요.

방화벽 규칙에서도 같은 표기를 만나게 됩니다. AWS 보안 그룹이나 iptables에서 0.0.0.0/0은 “어디에서 와도, 어디로 가도 적용”이라는 뜻이라, 인터넷 전체에 노출하는 규칙을 의미합니다. 이 표기를 무심코 쓰면 의도치 않게 SSH 포트를 전 세계에 열어버리는 사고가 흔하니 조심해야 해요.

출발지 주소로서의 0.0.0.0

0.0.0.0은 패킷의 출발지 주소로도 쓰입니다. 가장 대표적인 예가 DHCP 클라이언트의 첫 요청이에요.

기기가 네트워크에 처음 연결되면 아직 IP를 받지 못한 상태인데요. “DHCP 서버가 누구예요? 저한테 IP를 하나 주세요”라는 의미의 DHCPDISCOVER 패킷을 보내야 합니다. 이때 출발지 주소에 적을 자기 IP가 없으니 0.0.0.0을 채워 넣고, 목적지는 255.255.255.255(브로드캐스트)로 보냅니다.

DHCP DISCOVER 패킷의 IP 헤더
src = 0.0.0.0          ← 아직 내 IP가 없다
dst = 255.255.255.255  ← 같은 네트워크의 모두에게

DHCP 서버가 이 패킷을 받고 IP를 제안하면, 클라이언트는 그 IP를 받아 “내 주소”로 쓰기 시작합니다. 일종의 “IP 없음” 상태를 표현하는 신호인 거죠.

IPv6의 :: 와일드카드

IPv6에서는 0.0.0.0 대신 ::이 같은 역할을 합니다. 0:0:0:0:0:0:0:0을 압축한 표기인데, 의미는 IPv4의 0.0.0.0과 동일해요.

서버를 ::에 바인딩하면 IPv6의 모든 인터페이스에서 받겠다는 뜻이고, 운영체제 설정에 따라 IPv4-mapped IPv6 주소를 통해 IPv4 트래픽까지 한 소켓으로 처리하기도 합니다.

IPv4와 IPv6를 모두 받기
node server.js --host ::

이 한 줄로 IPv4 클라이언트와 IPv6 클라이언트 양쪽 모두에게 응답할 수 있다는 게 듀얼 스택 환경의 편리함이에요.

마치며

0.0.0.0은 여러 맥락에서 같은 모양으로 등장하지만 의미는 조금씩 달랐습니다. 서버 바인딩에서는 “모든 인터페이스에서 받겠다”, 라우팅 테이블에서는 “모든 목적지”, DHCP에서는 “아직 내 IP가 없다”라는 식으로 말이죠. 공통점이 있다면 모두 “구체적이지 않은, 와일드카드”라는 성격이에요.

특히 서버 바인딩에서의 0.0.0.0은 보안 경계와 직결되는 결정이라 가볍게 다룰 부분은 아닙니다. “왜 이걸 쓰는지” 한 번 의식하고 선택하는 것만으로도 의도치 않은 노출 사고를 크게 줄일 수 있어요.

Docker나 클라우드 환경에서 자주 마주치는 표기인 만큼, 한 번 정리해 두면 두고두고 도움이 될 겁니다.

더 깊이 들여다보고 싶다면 RFC 1122의 Robustness Principle 부분을 참고해 보세요.

This work is licensed under CC BY 4.0 CC BY

개발자를 위한 뉴스레터

달레가 정리한 AI 개발 트렌드와 직접 만든 콘텐츠를 전해드립니다.

Discord