EJClaw 40b8894a98
Some checks failed
build / build (push) Has been cancelled
docs: 참고 대상의 제작자·저장소·서버 주소를 문서에서 제거
코드와 산출물 jar 에는 애초에 외부 서버 주소가 없었으나(rendezvous 기본값이
빈 문자열), 설계 문서에 참고 대상의 제작자명과 저장소 경로가 남아 있었다.
기술적 관찰 사실은 설계 판단의 근거로 필요하므로 유지하되 "기존 구현" 으로
중립화했다.

더불어 이 프로젝트가 남의 인프라에 얹혀 가지 않는다는 점을 README 에 명시했다.
rendezvous 는 빈 값으로 시작해 사용자가 직접 띄운 서버만 쓰며, 유일하게 남는
외부 호출인 STUN 기본값도 제거하는 방법을 함께 적었다.

검증: 작업 트리와 양쪽 산출물 jar 전수 검색 0건, 테스트 57개 전부 통과.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-09-28 11:33:14 +09:00

mc_p2p_mod

초대코드 하나로 싱글플레이 월드를 친구와 함께 플레이하는 마인크래프트 P2P 모드.

jar 파일 하나가 모든 마인크래프트 버전에서 동작한다. 1.21 용, 1.21.8 용, 26.3 용을 따로 내려받지 않는다. 버전별 빌드도, 매핑도, 믹스인도 없다. 어떻게 가능한지는 docs/DESIGN.md 에 근거와 함께 적어 두었다.

전송 QUIC (TLS 1.3 종단간 암호화)
네이티브 라이브러리 0개
jar 크기 약 0.71 MB
지원 버전 Fabric 이 도는 모든 버전 (JVM 17 이상)
믹스인 없음
마인크래프트 클래스 참조 0개 (테스트로 강제)

설치

  1. Fabric Loader 0.14 이상을 설치한다.
  2. mc_p2p_mod-<버전>.jar 을 mods 폴더에 넣는다.
  3. 게임을 켠다. 로그에 조작 주소가 찍힌다.
[mc_p2p] 준비 완료 — 조작은 http://127.0.0.1:25585 에서 한다

Fabric API 는 필요하지 않다. 다른 모드와 충돌할 여지도 사실상 없다 — 이 모드는 게임 코드를 한 줄도 건드리지 않는다.


처음 한 번: 시그널링 서버 준비

P2P 는 두 사람이 서로의 주소를 알아야 시작된다. 그 주소를 교환해 주는 작은 서버가 필요하다. 남의 서버에 의존하지 않도록, 이 저장소에 서버가 같이 들어 있다.

아무 VPS 한 대에서:

java -jar mc_p2p_mod-<버전>-rendezvous.jar 25580 40000-40100
  • 25580 — 시그널링용 TCP 포트
  • 40000-40100 — 중계용 UDP 포트 범위. 방화벽에서 이 범위만 열면 된다. 생략하면 임의 포트를 쓰므로 방화벽 설정이 번거로워진다.

메모리는 수십 MB 면 충분하다. 홀펀칭이 성공하는 한 트래픽은 이 서버를 지나지 않는다.

서버를 띄웠으면 게임에서 http://127.0.0.1:25585 를 열고 주소를 넣고 저장한다. 예: p2p.example.com:25580

외부 서버를 쓰지 않는다

이 모드에는 다른 사람의 시그널링·중계 서버 주소가 기본값으로 들어 있지 않다. rendezvous 는 비어 있는 상태로 시작하고, 사용자가 직접 띄운 서버를 넣어야 비로소 동작한다. 남의 인프라에 얹혀 가는 구성을 아예 만들 수 없게 한 것이다.

남는 외부 호출은 stun 기본값 하나뿐이다(stun.l.google.com:19302). 공인 주소를 알아내는 데만 쓰이고 게임 트래픽은 지나가지 않는다. 그래도 완전히 없애고 싶으면 두 가지 방법이 있다.

  • 같은 공유기 안에서만 쓸 거라면 stun 을 빈 값으로 둔다. 외부 호출이 0 이 된다.
  • 인터넷을 넘어야 하면 직접 띄운 STUN 서버(coturn 등)를 적는다.

방 만들기 (호스트)

  1. 웹 콘솔에서 호스트 준비 를 누른다.
  2. 게임에서 월드에 들어가 일시정지 → LAN 에 공개 를 누른다.
  3. 웹 콘솔에 초대코드가 나타난다. 친구에게 준다.
K7Q2M9-F4M1TX-9VB0RS-T3WXZ8

바닐라의 "LAN 에 공개" 를 그대로 쓴다. 모드가 그 순간을 감지해서 방을 연다. 월드를 닫으면 방도 알아서 정리된다.

방 들어가기 (참가자)

  1. 웹 콘솔에 초대코드를 붙여넣고 참가 를 누른다.
  2. 게임의 멀티플레이 화면을 연다.
  3. LAN 목록에 방이 나타난다. 누르면 접속된다.

주소를 입력하거나 서버를 추가할 필요가 없다. 목록에 직결/중계와 지연 시간이 같이 표시된다.

P2P 방 · 직결 23ms

연결 방식

경로를 동시에 시도해서 되는 것을 쓴다.

  1. 직결 (host) — 같은 공유기 안이면 가장 빠르다.
  2. 직결 (srflx) — STUN 으로 공인 주소를 알아내 양쪽이 동시에 패킷을 보내 NAT 에 구멍을 뚫는다. 가정용 공유기 대부분에서 성공한다.
  3. 중계 (relay) — 대칭 NAT·CGNAT 처럼 구멍이 안 뚫리는 환경의 보험. 시그널링 서버가 UDP 데이터그램을 그대로 넘긴다.

중계는 거의 항상 즉시 성공하므로, 아무 장치 없이 "먼저 된 것" 을 쓰면 직결이 가능한데도 중계로 굳어버린다. 그래서 직결에 기본 600ms 의 유예를 준다. 그 안에 홀펀칭이 되면 직결을 쓰고, 안 되면 중계로 넘어간다.

중계를 지나더라도 QUIC 의 TLS 1.3 이 종단간이라 중계 서버는 게임 트래픽을 읽을 수 없다.


설정

config/mc_p2p.properties. 웹 콘솔에서 바꾸는 값도 여기 저장된다.

키 기본값 설명
rendezvous (비어 있음) 시그널링 서버 호스트:포트
stun stun.l.google.com:19302 비우면 같은 LAN 전용이 된다
webPort 25585 웹 콘솔 포트. 루프백에만 열린다
relayOnly false 직결을 건너뛰고 중계만 쓴다(디버깅용)
directPreferenceMs 600 직결에 주는 유예 시간
pathTimeoutMs 8000 경로 탐색 포기 시각
debug false 상세 로그

보안

  • 방 비밀 — 초대코드는 120비트 난수다. 호스트는 QUIC 스트림 첫머리에서 이 값을 대조한 뒤에야 통합 서버로 연결을 넘긴다. 코드가 없으면 방 ID 를 알아도 들어올 수 없다.
  • 시그널링 서버는 초대코드를 모른다 — 서버에 등록되는 방 ID 는 코드의 SHA-256 에서 파생된 값이다. 서버 운영자도 방에 몰래 들어오거나 호스트를 사칭할 수 없다.
  • 중간자 차단 — 호스트는 매번 새 인증서를 만들고, 참가자는 시그널링으로 받은 SHA-256 지문과 실제 핸드셰이크의 인증서를 대조한다. 다르면 즉시 끊는다.
  • 웹 콘솔은 127.0.0.1 에만 바인딩된다 — 방을 열고 닫을 수 있는 창구이므로 LAN 에도 노출하지 않는다.
  • 참가자 리스너도 127.0.0.1 전용이다 — 같은 공유기의 다른 기기가 초대코드 없이 올라타지 못한다.

시그널링 연결 자체는 아직 평문 TCP 다. 중간자가 이 경로를 장악하면 지문을 바꿔칠 수 있다. TLS 적용은 남은 과제로 docs/DESIGN.md 에 적어 두었다.


빌드

./gradlew build

Fabric Loom 을 쓰지 않으므로 마인크래프트를 내려받지 않고 몇 초 안에 끝난다. 산출물은 build/libs/ 에 두 개가 나온다.

  • mc_p2p_mod-<버전>.jar — 모드
  • mc_p2p_mod-<버전>-rendezvous.jar — 시그널링/중계 서버

테스트만 돌리려면 ./gradlew test.


검증 상태

솔직하게 적는다.

기계로 확인된 것 (./gradlew test, 57개):

  • 터널 전 구간 — 펀칭 프로브, 경로 선택, QUIC 핸드셰이크, 인증서 지문 고정, 방 비밀 대조, TCP↔QUIC 양방향 운반. 직결과 중계 양쪽 모두 실제 소켓으로 왕복한다.
  • 512KB 연속 전송이 손실 없이 도착한다(청크 전송 상황).
  • 틀린 초대코드는 호스트가 거절하고 통합 서버에 닿지 않는다.
  • 지문이 다르면 참가자가 연결을 끊는다.
  • 직결과 중계가 동시에 유효할 때 직결이 이긴다.
  • LAN 비콘을 실제 멀티캐스트로 쏘고 받아 포트를 읽는다.
  • 시그널링 프로토콜 — 등록, 참가, 방 중복, 호스트 이탈 시 방 정리, 잘못된 입력.
  • 웹 콘솔 — 실제 HTTP 요청/응답, 루프백 전용 바인딩.
  • 마인크래프트 클래스 참조 0개 — 컴파일된 클래스의 상수 풀을 뒤져서 강제한다.

아직 확인되지 않은 것:

  • 실제 마인크래프트 클라이언트로 접속해 본 검증. 루프백 테스트는 프로토콜 계층까지만 본다.
  • 실제 NAT 를 넘는 홀펀칭 성공률. 테스트는 같은 기기 안이라 NAT 가 없다.
  • 1.21.x 와 26.x 양쪽에서의 실제 로드 확인.

라이선스

MIT. LICENSE 참고.

Description
No description provided
Readme MIT 219 KiB
Languages
Java 97.7%
Python 2.3%