뜯어본 생각공개

clumsy

윈도우 PC의 인터넷을 일부러 나쁘게 만드는 도구입니다. PC 게임의 끊김·지연 테스트에 쓰려고 열었고 소스 2,709줄을 다 읽었습니다. 판정은 채택인데, 같은 걸 8월에 이미 분석했고 두 번 다 한 번도 안 돌렸다는 게 이 편의 교훈입니다(2026-10-02).

수확 2.5 · 실행 0회github.com/jagt/clumsy ↗

clumsy는 윈도우 PC의 인터넷을 일부러 나쁘게 만드는 무료 프로그램입니다. 제가 QA하는 PC 게임이 끊기거나 느려질 때 제대로 버티는지 보려고 열었습니다. 판정은 「쓴다」인데, 이 편의 진짜 이야기는 따로 있습니다 — 같은 레포를 8월에 이미 한 번 분석했고, 두 번 다 한 번도 돌려 보지 않았습니다.

소포 하나가 지나가는 길 — 갈고리가 집어 들고, 조작판이 괴롭힌 뒤 다시 내려놓는다 게임 (이 PC) 보내고 받는 쪽 WinDivert 갈고리 윈도우 네트워크 길목 clumsy 조작판 늦추기 · 버리기 · 끊기 · 속도 제한 게임 서버 인터넷 너머 필터에 안 맞는 소포(메신저·원격 접속)는 갈고리를 그냥 지나간다 켜는 법 두 가지 — 창에서 체크하거나, 명령줄 한 줄로 창: Start → 모듈 체크 → 값 입력 → Stop 명령줄: --filter … --drop on --timeout 30 ⇒ 이 PC가 직접 주고받는 소포만 잡는다 — 다른 기기의 소포에는 손이 닿지 않는다
소포를 집는 것은 윈도우 드라이버이고, clumsy는 그 위에 얹은 조작판이다. 그래서 이 PC 밖의 기기에는 손이 닿지 않는다.

먼저 — 「인터넷을 나쁘게 만든다」는 게 무슨 뜻인가요

인터넷으로 오가는 데이터는 한 덩어리로 가지 않습니다. 작은 소포 수십 개로 잘려서 오가고, 이 소포 하나를 「패킷」이라고 부릅니다. 게임은 1초에도 수십 개씩 서버와 패킷을 주고받습니다.

clumsy는 PC의 인터넷 출입문에 서서 이 소포들을 붙잡습니다. 그리고 시킨 대로 괴롭힙니다.

시키는 것 비유하면 게임에서는
늦추기 소포를 문 앞에 잠깐 세워 둠 핑이 높아짐 — 누르고 한참 뒤에 반응
몇 개 버리기 소포 몇 개를 쓰레기통에 캐릭터가 순간이동하듯 튐
전부 버리기 출입문을 아예 닫음 서버와 연결이 끊김
속도 제한 한 번에 지나가는 양을 묶음 로딩이 길어짐

이 밖에 순서 섞기·복사하기·내용 망가뜨리기·연결 강제로 끊기까지 모두 여덟 가지가 있습니다. 게임은 켜 둔 채로 창에서 숫자만 바꾸면 됩니다.

왜 이게 필요한가요 — 강제 종료로는 안 지나가는 길이 있습니다

게임 QA에는 「접속이 끊겼다가 다시 들어오면 데이터가 맞나」를 보는 일이 많습니다. 보통은 게임을 강제로 꺼 버리고 다시 켜서 확인합니다.

그런데 강제 종료는 게임 자체가 죽습니다. 실제 사용자가 겪는 끊김은 다릅니다 — 게임은 살아 있는데 선만 끊깁니다. 이때 게임은 「연결이 끊겼네 → 다시 붙어 보자 → 재연결 창을 띄우자」라는 별도의 길을 타는데, 강제 종료로는 이 길을 한 번도 지나가지 않습니다.

랜선을 뽑으면 되긴 합니다. 하지만 메신저도 원격 접속도 같이 끊깁니다. clumsy는 게임 서버로 가는 소포만 골라서 막을 수 있고, 그게 이 도구를 쓰는 이유입니다.

소스를 열어 보니, 설명서에 없는 것이 넷 있었습니다

clumsy는 C로 2,709줄입니다. 작아서 전부 읽었습니다. 설명서와 다르거나 설명서에 아예 없는 것이 넷 나왔습니다.

  1. 지연은 40ms 단위로 흔들립니다. 안쪽 시계가 40ms마다 한 번 돌기 때문이고, 그래서 100ms를 넣으면 실제로는 100~140ms 사이가 됩니다. 「조금 느림」과 「많이 느림」을 가르는 데는 충분합니다. 몇 ms를 재는 데는 못 씁니다.
  2. 왕복에는 두 번 걸립니다. 나가는 소포와 들어오는 소포에 같은 값이 붙어서, 핑을 300 늘리고 싶으면 150을 넣어야 합니다.
  3. 명령줄로 켤 땐 관리자 창에서 켜야 합니다. 일반 창에서 켜면 오류 메시지 하나 없이 그냥 꺼집니다.
  4. 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)은 인자를 넘기지 않는다

깨본 결과 — 문서와 소스가 갈리는 다섯 곳

  1. 지연 정확도. #87(열림)은 지연을 0으로 둬도 연속 요청 간격이 80ms, 곧 방향마다 40ms 벌어지고, 1ms 설정에서는 50ms까지 튄다고 보고합니다. 소스의 40ms 시계와 정확히 맞물립니다. 그래서 이 값은 「설정값」으로만 적고 실측 왕복 시간으로 인용하지 않습니다.
  2. 종료 경로. --timeout 이 끝나거나 창을 닫으면 IUP_CLOSE → cleanup() 으로 가는데, 여기서 divertStop() 을 부르지 않습니다. 필터는 프로세스가 끝나며 풀리지만 Lag가 붙잡고 있던 패킷은 내보내지 않고 사라집니다. 모듈을 닫고 남은 패킷을 다 보내는 길은 Stop 버튼 하나뿐입니다.
  3. 명령줄 무음 종료. #141 「옵션을 주면 즉시 꺼짐」의 원인은 위 silent 분기로 보입니다(추정 — 실행으로 확인하지 않았습니다).
  4. 문서 어긋남. 위키는 지연 범위를 0–3,000으로 적었지만 코드는 15,000까지 받습니다. --timeout·--duplicate-count 는 위키에 아예 없습니다(#189).
  5. 환경. 백신이 0.3 zip을 Trojan:Script/Ulthar.A!ml 로 탐지(#187, 2025-11, 열림) · 드라이버 서명 오류(#84) · 닫은 뒤에도 느려짐이 남음(#31, 재부팅으로 해결) · 블루스크린 보고 1건(#107) · 원격 제어와 단축키 없음(#1, 2013년부터 열림).
어디까지 믿고 쓰나 — 세 칸으로 갈랐다 쓴다 특정 서버만 완전히 끊기 100ms 이상 단위의 지연 --timeout 으로 자동 복구 (PC 게임 · 테스트 계정으로만) 미뤄 둔다 TCP 연결 강제로 끊기 확률로 버리기 · 속도 제한 → 판정 기준이 생기면 다시 못 쓴다 휴대폰 실기기 트래픽 몇 ms 단위의 정밀 측정 → 다른 도구로 ⇒ 판정은 8월 분석과 한 글자도 다르지 않다 — 두 번 분석했고, 돌린 것은 0번이다 판정을 움직일 다음 한 걸음 = 엔진 내장 기능 1분 확인 → 회사 PC에서 5분 실측
같은 대상을 두 번 뜯었는데 칸 배치는 그대로였다. 칸을 옮길 수 있는 것은 실측뿐이다.

내 환경에 대봤다 — 빈칸 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을 뺀 탓이었습니다 — 「없다」를 한 자리만 보고 말한 값입니다.