feat: send Login Disconnect with custom message on blocked join
Previously a blocked join just dropped the socket, so the MC client showed 'Internal Exception: SocketException: Connection reset'. Now when next_state=2 (login), the proxy sends a proper Login Disconnect (0x00) packet containing a JSON chat component, and the client displays the message on its disconnect screen. - block_message added to config (default Korean message); editable in Settings UI as a textarea - build_login_disconnect() encodes (varint length)+(0x00)+(JSON str) - Status/ping (next_state=1) still silently dropped so the proxy presence is not announced to scanners - Backward-compat: load_config() backfills block_message on old files
This commit is contained in:
@@ -19,6 +19,7 @@ DEFAULT_CONFIG = {
|
||||
"allowed_domains": [
|
||||
{"domain": "mc.tkrmagid.kr", "enabled": True, "note": "메인 도메인"}
|
||||
],
|
||||
"block_message": "이 서버는 허용된 도메인에서만 접속 가능합니다.",
|
||||
}
|
||||
|
||||
_lock = threading.Lock()
|
||||
|
||||
@@ -10,6 +10,7 @@ asyncio 기반 TCP 프록시. 동작 순서:
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sqlite3
|
||||
@@ -19,6 +20,39 @@ from pathlib import Path
|
||||
import config as cfg_mod
|
||||
from handshake import HandshakeError, parse_handshake, read_handshake_bytes
|
||||
|
||||
DEFAULT_BLOCK_MESSAGE = "이 서버는 허용된 도메인에서만 접속 가능합니다."
|
||||
|
||||
|
||||
def _encode_varint(n: int) -> bytes:
|
||||
out = bytearray()
|
||||
while True:
|
||||
b = n & 0x7F
|
||||
n >>= 7
|
||||
if n:
|
||||
out.append(b | 0x80)
|
||||
else:
|
||||
out.append(b)
|
||||
break
|
||||
return bytes(out)
|
||||
|
||||
|
||||
def build_login_disconnect(message: str) -> bytes:
|
||||
"""Login Disconnect (clientbound, login state, packet id 0x00).
|
||||
|
||||
Body: JSON chat component as length-prefixed UTF-8 string.
|
||||
클라이언트는 이 패킷을 받으면 "서버에서 연결을 거부했습니다" 화면 대신
|
||||
여기 담긴 chat 컴포넌트를 그대로 보여준다.
|
||||
"""
|
||||
chat_json = json.dumps(
|
||||
{"text": message, "color": "red"}, ensure_ascii=False
|
||||
).encode("utf-8")
|
||||
body = (
|
||||
_encode_varint(0x00)
|
||||
+ _encode_varint(len(chat_json))
|
||||
+ chat_json
|
||||
)
|
||||
return _encode_varint(len(body)) + body
|
||||
|
||||
LOG_DB = Path(os.environ.get("MC_LOG_DB", "/data/logs.db"))
|
||||
RESTART_SIGNAL = Path(os.environ.get("MC_RESTART_SIGNAL", "/data/restart.signal"))
|
||||
|
||||
@@ -184,6 +218,15 @@ async def handle_client(
|
||||
log.info(
|
||||
"BLOCK %s domain=%r next_state=%d", client_ip, domain, hs.next_state
|
||||
)
|
||||
# next_state=2 (login) 인 경우 Login Disconnect 패킷으로 메시지 전달,
|
||||
# next_state=1 (status/ping) 은 그냥 끊는다 (프록시 존재 자체를 굳이 노출 X).
|
||||
if hs.next_state == 2:
|
||||
try:
|
||||
msg = state.cfg.get("block_message") or DEFAULT_BLOCK_MESSAGE
|
||||
client_writer.write(build_login_disconnect(msg))
|
||||
await client_writer.drain()
|
||||
except (OSError, ConnectionResetError, BrokenPipeError):
|
||||
pass
|
||||
client_writer.close()
|
||||
return
|
||||
|
||||
|
||||
Reference in New Issue
Block a user