20년도 더 된 사건인데 왜 지금 다시 봐야 할까요
2003년 봄, 미국 위스콘신 대학교(University of Wisconsin-Madison) 네트워크 팀이 이상한 걸 발견했어요. 학교가 운영하던 시간 서버 한 대로 정체불명의 트래픽이 끝없이 쏟아져 들어오고 있었거든요. 처음엔 당연히 DDoS 공격인 줄 알았대요. 그런데 파헤쳐 보니 범인은 해커가 아니라, 전 세계 가정집 거실에 놓여 있던 넷기어(Netgear) 공유기 수십만 대였어요.
이 사건을 분석해서 보고서로 남긴 사람이 당시 위스콘신 대학 네트워크 엔지니어였던 데이브 플롱카(Dave Plonka)인데요, 이 보고서는 지금도 “하드코딩의 위험”을 설명할 때 교과서처럼 인용돼요. IoT 기기가 수십억 대로 늘어난 지금 오히려 더 무겁게 다가오는 이야기라서 한번 자세히 풀어볼게요.
NTP가 뭐냐면
먼저 배경부터요. NTP(Network Time Protocol)는 컴퓨터가 “지금 몇 시야?”를 네트워크로 물어보고 시계를 맞추는 프로토콜이에요. 여러분 노트북이 항상 정확한 시간을 보여주는 것도 뒤에서 조용히 NTP가 돌고 있기 때문이거든요. 공유기 같은 장비도 로그에 시간을 찍거나 예약 기능을 쓰려면 시간을 알아야 하니까, 부팅할 때 어딘가의 시간 서버에 물어봐요. SNTP는 NTP를 단순화한 버전으로 작은 임베디드 기기에서 많이 쓰고요.
위스콘신 대학은 오래전부터 누구나 시간을 물어볼 수 있는 공개 NTP 서버를 운영하고 있었어요. 일종의 인터넷 공공 서비스였던 거죠.
무슨 일이 벌어졌나
넷기어의 몇몇 소비자용 공유기 모델(MR814, DG814, RP614, HR314 등) 펌웨어에는 시간 서버 주소가 도메인이 아니라 IP 주소로 하드코딩되어 있었어요. 그 주소가 바로 위스콘신 대학 NTP 서버였던 128.105.39.11이었고요. 넷기어 입장에서는 “잘 돌아가는 공개 서버 하나 골라서 박아 넣자” 정도의 가벼운 결정이었을 거예요.
그런데 진짜 문제는 재시도 로직이었어요. 이 공유기들은 시간 서버에 요청을 보내고 응답이 없으면 1초마다 다시 물어봤거든요. 응답을 받으면 그때부터는 1분에 한 번씩 물어보고요. 제대로 만든 NTP 클라이언트라면 응답이 없을 때 간격을 점점 늘려야 하는데(이걸 지수 백오프, exponential backoff라고 해요), 이 펌웨어에는 그런 게 전혀 없었어요.
이게 왜 치명적이냐면요. 위스콘신 대학이 트래픽을 감당 못 해서 방화벽으로 막기 시작하자, 공유기들은 “응답이 없네?” 하고 더 열심히 1초마다 두드리기 시작한 거예요. 막을수록 더 밀려오는 악순환이었죠. 피크 때는 초당 약 25만 개의 패킷, 대역폭으로는 150Mbps 이상이 이 서버 하나로 쏟아졌어요. 2003년 기준으로는 대학 전체 네트워크를 흔들 수 있는 양이에요.
더 무서운 건 이 트래픽을 “고칠 수 없다”는 점이었어요. 공유기 주인들은 자기 공유기가 뭘 하고 있는지 전혀 몰랐고, 펌웨어를 업데이트할 이유도 없었거든요. 공유기는 멀쩡히 잘 돌아가고 있었으니까요. 이런 소비자용 장비는 한번 팔리면 5년, 10년씩 그대로 쓰여요. 즉 위스콘신 대학은 자기 잘못이 아닌 트래픽을 앞으로 몇 년 동안 받아내야 하는 처지가 된 거예요.
어떻게 해결했나
플롱카는 트래픽 패턴과 패킷 내용을 분석해서 특정 벤더의 특정 모델이라는 걸 밝혀냈고, 넷기어와 협상에 들어갔어요. 결과적으로 넷기어는 문제를 인정하고 위스콘신 대학에 37만 5천 달러를 지원해서 서버 인프라를 증설하도록 했어요. 새 펌웨어에서는 IP 대신 넷기어 소유의 도메인 이름을 쓰고, 재시도 간격도 제대로 늘리도록 고쳤고요.
서버 쪽에서 프로토콜 차원의 “그만 물어봐” 신호(NTP의 Kiss-o'-Death 같은 것)를 보내는 방법도 검토됐지만, 이미 팔린 펌웨어는 그런 신호를 이해하지 못했어요. 그래서 근본적인 해결은 결국 “장비가 수명을 다해 사라지는 것”뿐이었죠.
업계에 남긴 것
이 사건은 NTP 커뮤니티가 NTP Pool 프로젝트(pool.ntp.org)를 키우는 데 중요한 동기가 됐어요. 특정 서버 하나에 의존하지 말고, DNS로 수천 대의 자원봉사 서버에 부하를 분산시키자는 아이디어예요. 지금 여러분이 쓰는 리눅스 배포판이나 공유기 대부분이 pool.ntp.org를 기본값으로 쓰는 건 이런 사건들 덕분이에요. 0.debian.pool.ntp.org처럼 벤더 전용 풀을 따로 만드는 관행도 여기서 나왔고요.
비슷한 사고는 이후에도 반복됐어요. 2005년 무렵에는 덴마크의 한 개인이 운영하던 NTP 서버가 D-Link 장비의 하드코딩 때문에 같은 일을 겪었고, 2016년에는 스냅챗(Snapchat) iOS 앱이 NTP 요청을 폭주시켜 NTP Pool 운영진이 공개 경고문을 올린 일도 있었어요. 패턴은 항상 같아요. “적당한 서버 하나 골라서 박아 넣고, 실패하면 무한 재시도.”
한국 개발자에게 주는 시사점
임베디드나 IoT 개발자에게만 해당되는 이야기가 아니에요. 백엔드나 앱 개발자도 매일 똑같은 선택을 하고 있거든요.
첫째, 외부 서버 주소를 IP로 하드코딩하지 마세요. 최소한 도메인을 쓰고, 가능하면 설정으로 빼서 업데이트 없이도 바꿀 수 있게 해두세요. 앱스토어 심사 때문에 배포 주기가 긴 모바일 앱이라면 더욱 그렇고요.
둘째, 재시도에는 반드시 백오프와 지터(jitter)를 넣으세요. 실패하면 1초, 2초, 4초, 8초 이렇게 간격을 늘리고, 거기에 랜덤한 오차를 섞어서 모든 클라이언트가 동시에 재시도하는 현상(thundering herd)을 막아야 해요. 요즘은 대부분의 HTTP 클라이언트 라이브러리에 옵션이 있으니 켜기만 하면 돼요.
셋째, 서버가 “그만해”라고 말할 수 있는 경로를 만들고, 클라이언트가 그걸 실제로 존중하는지 테스트하세요. HTTP라면 429 상태 코드와 Retry-After 헤더가 그 역할이에요. 넷기어 펌웨어처럼 신호를 보내도 못 알아들으면 아무 소용이 없거든요.
넷째, 남의 공개 서비스를 내 제품의 인프라로 쓸 때는 허락을 구하세요. 공개 API, 공개 NTP, 공개 DNS 모두 마찬가지예요. 제품이 수십만 대 팔리면 그 서비스 운영자에게 어떤 일이 벌어질지 미리 상상해 보는 거죠.
마무리
한 줄로 정리하면, 작은 펌웨어 한 줄의 실수가 수년 동안 남의 인프라를 두들기는 좀비 트래픽이 될 수 있다는 거예요. 그리고 그걸 막는 방법은 대단한 기술이 아니라 백오프, 설정 분리, 서버 신호 존중 같은 기본기예요.
여러분 프로젝트에서는 외부 서비스 호출이 실패했을 때 어떤 재시도 정책을 쓰고 계세요? 혹시 “일단 재시도” 로직만 있고 백오프는 빠져 있는 곳이 있진 않은지, 한번 점검해 볼 만한 이야기 아닐까요?
🔗 출처: Hacker News