clumsy
윈도우 PC의 인터넷을 일부러 나쁘게 만드는 도구입니다. PC 게임의 끊김·지연 테스트에 쓰려고 열었고 소스 2,709줄을 다 읽었습니다. 판정은 채택인데, 같은 걸 8월에 이미 분석했고 두 번 다 한 번도 안 돌렸다는 게 이 편의 교훈입니다(2026-10-02).
clumsy는 윈도우 PC의 인터넷을 일부러 나쁘게 만드는 무료 프로그램입니다. 제가 QA하는 PC 게임이 끊기거나 느려질 때 제대로 버티는지 보려고 열었습니다. 판정은 「쓴다」인데, 이 편의 진짜 이야기는 따로 있습니다 — 같은 레포를 8월에 이미 한 번 분석했고, 두 번 다 한 번도 돌려 보지 않았습니다.
먼저 — 「인터넷을 나쁘게 만든다」는 게 무슨 뜻인가요
인터넷으로 오가는 데이터는 한 덩어리로 가지 않습니다. 작은 소포 수십 개로 잘려서 오가고, 이 소포 하나를 「패킷」이라고 부릅니다. 게임은 1초에도 수십 개씩 서버와 패킷을 주고받습니다.
clumsy는 PC의 인터넷 출입문에 서서 이 소포들을 붙잡습니다. 그리고 시킨 대로 괴롭힙니다.
| 시키는 것 | 비유하면 | 게임에서는 |
|---|---|---|
| 늦추기 | 소포를 문 앞에 잠깐 세워 둠 | 핑이 높아짐 — 누르고 한참 뒤에 반응 |
| 몇 개 버리기 | 소포 몇 개를 쓰레기통에 | 캐릭터가 순간이동하듯 튐 |
| 전부 버리기 | 출입문을 아예 닫음 | 서버와 연결이 끊김 |
| 속도 제한 | 한 번에 지나가는 양을 묶음 | 로딩이 길어짐 |
이 밖에 순서 섞기·복사하기·내용 망가뜨리기·연결 강제로 끊기까지 모두 여덟 가지가 있습니다. 게임은 켜 둔 채로 창에서 숫자만 바꾸면 됩니다.
왜 이게 필요한가요 — 강제 종료로는 안 지나가는 길이 있습니다
게임 QA에는 「접속이 끊겼다가 다시 들어오면 데이터가 맞나」를 보는 일이 많습니다. 보통은 게임을 강제로 꺼 버리고 다시 켜서 확인합니다.
그런데 강제 종료는 게임 자체가 죽습니다. 실제 사용자가 겪는 끊김은 다릅니다 — 게임은 살아 있는데 선만 끊깁니다. 이때 게임은 「연결이 끊겼네 → 다시 붙어 보자 → 재연결 창을 띄우자」라는 별도의 길을 타는데, 강제 종료로는 이 길을 한 번도 지나가지 않습니다.
랜선을 뽑으면 되긴 합니다. 하지만 메신저도 원격 접속도 같이 끊깁니다. clumsy는 게임 서버로 가는 소포만 골라서 막을 수 있고, 그게 이 도구를 쓰는 이유입니다.
소스를 열어 보니, 설명서에 없는 것이 넷 있었습니다
clumsy는 C로 2,709줄입니다. 작아서 전부 읽었습니다. 설명서와 다르거나 설명서에 아예 없는 것이 넷 나왔습니다.
- 지연은 40ms 단위로 흔들립니다. 안쪽 시계가 40ms마다 한 번 돌기 때문이고, 그래서 100ms를 넣으면 실제로는 100~140ms 사이가 됩니다. 「조금 느림」과 「많이 느림」을 가르는 데는 충분합니다. 몇 ms를 재는 데는 못 씁니다.
- 왕복에는 두 번 걸립니다. 나가는 소포와 들어오는 소포에 같은 값이 붙어서, 핑을 300 늘리고 싶으면 150을 넣어야 합니다.
- 명령줄로 켤 땐 관리자 창에서 켜야 합니다. 일반 창에서 켜면 오류 메시지 하나 없이 그냥 꺼집니다.
- Stop을 누르고 나서 닫아야 합니다. 창을 바로 닫거나 정해 둔 시간이 다 돼서 꺼지면, 붙잡고 있던 소포를 내보내지 않고 버린 채 끝납니다.
제 환경에 대 보니 — PC 게임에는 맞고, 휴대폰에는 안 닿습니다
clumsy는 그 PC가 직접 주고받는 소포만 잡습니다. 그래서 PC 게임 테스트에는 그대로 쓸 수 있고, 휴대폰 실기기 테스트에는 못 씁니다. 휴대폰의 소포는 PC를 지나가지 않으니까요.
쓸 자리도 이미 있었습니다. 제 TC 생성 파이프라인이 만든 테스트 설계 문서 30편을 훑어보니, 재현 방법 칸에 「네트워크 차단 도구」가 24번, 「지연 주입」이 38번 적혀 있었습니다. 어떤 도구인지는 한 번도 적혀 있지 않았습니다. 그리고 제 TC 파이프라인 규칙은 재현 방법이 없는 케이스를 아예 빼 버립니다. clumsy가 그 빈칸에 들어갈 이름입니다.
그런데 이건 두 번째 분석이었습니다
리포트를 다 쓰고 목록에 올리려다 알았습니다. 8월 18일에 같은 레포를 이미 분석해 뒀습니다. 판정도 같았습니다 — PC에는 쓴다, 휴대폰은 안 된다.
처음 검색에서 못 찾은 이유는 단순합니다. 옛 리포트는 HTML 파일인데, 검색 대상에서 HTML을 뺐습니다.
더 아픈 건 그다음입니다. 6주 동안 한 번도 돌려 보지 않았고, 8월 리포트가 「제일 먼저 할 1분짜리 확인」이라고 적어 둔 것도 손대지 않은 채 그대로 남아 있었습니다.
→ 배운 것: 분석을 한 번 더 해도 판정은 움직이지 않았습니다. 판정을 움직일 수 있는 건 5분짜리 실측뿐이었습니다. 같은 대상을 다시 열기 전에, 지난번에 「다음에 할 일」로 적어 둔 것부터 했는지 봐야 했습니다.
다음에 할 일 — 이번엔 순서까지 적어 둡니다
| 순서 | 할 일 | 걸리는 시간 |
|---|---|---|
| 1 | 게임 엔진에 들어 있는 네트워크 흉내 기능을 콘솔에서 켜 보기 | 1분 · 설치 없음 |
| 2 | 회사 PC에서 clumsy를 받아 핑으로 확인하기 | 5분 |
| 3 | 실제 테스트 케이스 하나를 clumsy로 재현해 보기 | 30분쯤 |
1번이 먹히면 게임 쪽 지연 테스트는 그걸로 충분하고 clumsy는 「완전히 끊기」 전용이 됩니다. 2번에서 백신이 막으면 거기서 멈춥니다. 보안 설정을 풀면서까지 쓰지는 않습니다.
여기부터는 자세한 기록입니다
판정은 PC 쪽 채택(조건부)이고, 같은 판정이 8월에 이미 나와 있었다는 것이 이 편의 사건입니다. clumsy는 윈도우 커널 드라이버 WinDivert 위에 GUI 한 장을 얹은 C 프로그램입니다. 필터에 맞는 패킷을 붙잡아 늦추고·버리고·복제하고·섞고·변조하고·RST를 꽂고·대역폭을 깎은 뒤 다시 주입합니다. 2013년에 나와 별 6,251개를 모았고, 2022년 1월 이후 master 커밋이 없습니다. 저는 PC 게임 클라이언트의 「끊김·지연」 예외 케이스를 앱 수정 없이 재현할 수단으로 열었습니다.
무엇을 하는 물건인가 — 2,709줄짜리 패킷 조작판
| 항목 | 실측 (2026-10-02) |
|---|---|
| 규모 | C 소스 14파일 2,709줄 · 별 6,251 · 포크 620 |
| 활동 | master 마지막 커밋 2022-01-12 · 최신 릴리스 0.3(2023-10-21) |
| 배포 | 같은 0.3을 a·b·c 세 벌로 낸다 — WinDivert 드라이버 서명이 서로 다르다. win64 누적 다운로드 a 400,186 · b 12,811 · c 18,856 |
| 의존 | WinDivert 2.2.0 동봉(상류 최신은 2.2.2) · GUI는 IUP |
| 라이선스 | MIT. GitHub 표기는 NOASSERTION인데, LICENSE 첫 줄에 붙은 설명문 탓으로 보인다 |
| 열린 이슈+PR | 131 |
어떻게 동작하나 — 실 두 개와 40ms 시계
동작은 실(thread) 두 개가 나눠 맡습니다. 하나는 WinDivertRecv 로 패킷을 받을 때마다 목록에 넣고, 모듈 여덟을 차례로 통과시킨 뒤 남은 것을 내보냅니다. 다른 하나는 40ms마다(CLOCK_WAITMS 40) 같은 일을 한 번 더 합니다. 붙잡아 둔 패킷은 다음 패킷이 오거나 이 시계가 돌 때만 풀립니다.
| 모듈 | 하는 일 | 소스에서 본 것 |
|---|---|---|
| Lag | 고정 지연 0–15,000ms | 들어오기·나가기에 같은 값 하나(#172). 2,000개가 쌓이면 800개를 즉시 내보냄 |
| Drop | 패킷마다 확률로 폐기 | 확률을 0~10000 정수로 저장(0.01% 단위). 독립 시행이라 몰아서 잃는 모양은 안 나옴 |
| Throttle | 시간 창 동안 모았다가 한꺼번에 내보냄 | 「Drop Throttled」를 켜면 묶음째 폐기 — 몰아서 잃는 손실은 이걸로만 흉내 낸다 |
| Duplicate · OOD | 2–50개 복제 · 최대 10 차례 뒤로 미루기 | — |
| Tamper | 페이로드 비트 변조 | 체크섬 재계산이 기본 ON이라, 변조된 데이터가 앱까지 도착한다 |
| Reset | TCP RST 플래그 세팅 | TCP 헤더가 있을 때만 — UDP에는 효과 없음 |
| Bandwidth | KB/s 상한 | 줄을 세우지 않고 초과분을 폐기. 들어오기·나가기가 계량기 rateStats 하나를 공유한다 |
대표 설계 결정 — 그리고 그게 성립하는 조건
| 결정 | 성립 조건 |
|---|---|
| 커널 드라이버로 OS 길목에서 가로챈다 | 관리자 권한 · 드라이버 서명 통과 · 보안 제품이 막지 않을 것. 레이어가 WINDIVERT_LAYER_NETWORK 라 이 PC 자신의 트래픽만 잡는다 |
| 필터 문법으로 대상을 고른다 | 서버 IP·포트를 알아야 한다. 이 레이어에는 프로세스 정보가 없어서 프로그램 이름으로는 못 거른다 |
| 시계 하나를 40ms로 돌린다 | 트래픽이 뜸하면 설정값보다 방향마다 0~40ms 늦다. 100ms 단위 체감 테스트에는 무해하다 |
명령줄은 --키 값 을 전역 저장소에 넣고 모듈이 꺼내 쓴다 |
관리자가 아니면 tryElevate 의 silent 분기가 메시지 없이 종료한다. 승격 재실행(runas)은 인자를 넘기지 않는다 |
깨본 결과 — 문서와 소스가 갈리는 다섯 곳
- 지연 정확도. #87(열림)은 지연을 0으로 둬도 연속 요청 간격이 80ms, 곧 방향마다 40ms 벌어지고, 1ms 설정에서는 50ms까지 튄다고 보고합니다. 소스의 40ms 시계와 정확히 맞물립니다. 그래서 이 값은 「설정값」으로만 적고 실측 왕복 시간으로 인용하지 않습니다.
- 종료 경로.
--timeout이 끝나거나 창을 닫으면IUP_CLOSE→cleanup()으로 가는데, 여기서divertStop()을 부르지 않습니다. 필터는 프로세스가 끝나며 풀리지만 Lag가 붙잡고 있던 패킷은 내보내지 않고 사라집니다. 모듈을 닫고 남은 패킷을 다 보내는 길은 Stop 버튼 하나뿐입니다. - 명령줄 무음 종료. #141 「옵션을 주면 즉시 꺼짐」의 원인은 위 silent 분기로 보입니다(추정 — 실행으로 확인하지 않았습니다).
- 문서 어긋남. 위키는 지연 범위를 0–3,000으로 적었지만 코드는 15,000까지 받습니다.
--timeout·--duplicate-count는 위키에 아예 없습니다(#189). - 환경. 백신이 0.3 zip을
Trojan:Script/Ulthar.A!ml로 탐지(#187, 2025-11, 열림) · 드라이버 서명 오류(#84) · 닫은 뒤에도 느려짐이 남음(#31, 재부팅으로 해결) · 블루스크린 보고 1건(#107) · 원격 제어와 단축키 없음(#1, 2013년부터 열림).
내 환경에 대봤다 — 빈칸 62곳, 그리고 한 겹 겹치는 대안
제가 QA하는 것은 PC 클라이언트와 안드로이드 실기기 두 갈래입니다. clumsy가 닿는 건 PC 쪽뿐입니다.
PC 쪽에는 빈칸이 있었습니다. 제 TC 파이프라인이 낸 테스트 설계 문서 30편에서, 재현 수단 자리에 「네트워크 차단 도구」 24회 · 「지연 주입」 38회 · 「비행기 모드」 5회가 도구 이름 없이 적혀 있었습니다(2026-10-02 grep). 파이프라인 규칙은 동시성·경합 케이스를 재현 수단이 확보된 경우에만 쓰고, 나머지는 「재현 수단 필요 항목」으로 빼 둡니다. 수단 하나가 생기면 빠지던 케이스가 돌아옵니다.
겹치는 대안은 셋입니다.
| 대안 | clumsy와의 관계 |
|---|---|
| 프로세스 강제 종료 → 재접속 | 대체가 아니라 보완. 클라이언트가 죽으니 「끊김 감지 → 재연결 → 재연결 팝업」 경로를 아예 안 탄다 |
엔진 내장 네트워크 흉내 (예: 언리얼 엔진의 NetEmulation.PktLag) |
게임 채널의 지연·손실은 겹친다. 드라이버도 관리자 권한도 필요 없고 더 정밀하다. 다만 게임 채널에만 걸리고, 출하용 빌드에는 없고, 깔끔한 완전 단절은 어렵다 |
| 랜선 뽑기 | 메신저·원격 접속까지 같이 끊기고, 원격으로만 쓰는 PC에서는 할 수 없다 |
8월 분석은 사내 빌드의 디버그 심볼에서 엔진 쪽 패킷 시뮬레이션 이름들을 찾아, 이 기능이 켜진 채 컴파일됐을 정황이 강하다고 적었습니다. 그 뒤로 콘솔에 직접 쳐 보는 1분짜리 확인은 아무도 하지 않았습니다.
수확과 판정
| 기법 | 판정 |
|---|---|
| 서버 IP 필터 + Drop 100% = 그 서버만 완전 단절 | ✅ 채택 — PC 끊김 예외 케이스의 재현 수단 |
| Lag 100ms 이상 | ✅ 조건부(0.5) — 엔진 내장이 먹히면 그쪽이 우선. 값은 「설정값」으로만 적는다 |
--timeout N |
✅ 채택 — N초 끊김을 반복 재현하고, 원격 PC에서 스스로를 고립시키지 않게 막는 안전줄 |
| Reset (TCP RST) | ⏸ 보류 — 어떤 연결이 TCP인지 아직 모른다 |
| 확률 손실 · 대역폭 | ⏸ 보류 — 손실 모양이 실제 회선과 다르고 합격선이 없다 |
| 안드로이드 실기기 | ⛔ 기각 — PC를 지나가지 않는다. 핫스팟용 포크(abaza121/clumsy-hotspot)는 폰을 잡는 대신 PC 자신을 못 잡는다 |
되돌리기 비용은 사실상 0입니다. Stop 한 번이면 끝나니, 신뢰도가 중간이어도 해 보는 쪽이 맞습니다. 멈출 신호도 미리 정해 둡니다 — a·b·c 세 벌이 다 서명이나 백신에 막히면 거기서 끝내고, 예외 등록이나 서명 검사 끄기는 하지 않습니다.
못 한 것
- 실행 실측 0회. 커널 드라이버를 올려야 해서 이번에도 돌리지 않았습니다. 위 숫자는 전부 소스와 이슈에서 읽은 것입니다.
- 엔진 내장 기능 확인 0회. 1분이면 되는데 8월부터 남아 있습니다.
- 회사 보안 정책 확인 안 함. 백신이 막는지는 받아 봐야 압니다.
- 휴대폰 쪽 수단은 이번에도 비어 있습니다.
이 편에서 값을 치른 교훈은 순서입니다. 같은 대상을 두 번 분석했는데 판정은 한 글자도 안 바뀌었고, 바뀐 건 쓰는 법이 늘고 소스에서 함정 넷을 더 찾은 것뿐이었습니다. 판정을 움직일 수 있었던 건 처음부터 5분짜리 실측 하나였는데, 그건 두 번 다 「다음에」로 남았습니다. 그리고 두 번째 분석이 시작된 것 자체가 검색 범위에서 HTML을 뺀 탓이었습니다 — 「없다」를 한 자리만 보고 말한 값입니다.