fix(proxy): flush disconnect packet cleanly + label handshake errors

Two related diagnostics from production:

1) "Connection reset" instead of the custom block_message screen.
   Root cause: writer.close() returned before the kernel flushed the
   Login Disconnect packet, and the OS sent RST instead of FIN. Fix:
   write_eof() + await wait_closed() so the FIN goes out after the
   payload and the client has time to read the chat component.

2) Log entries showing reason "handshake error:" with an empty tail.
   Root cause: bare OSError() / ConnectionResetError() have empty
   str(), so the f-string interpolated to nothing. Fix: prepend the
   exception class name so the reason is always informative.
This commit is contained in:
2026-05-23 17:46:12 +09:00
parent d9a1ee1a69
commit 8312cfe861

View File

@@ -228,9 +228,16 @@ async def handle_client(
)
hs = parse_handshake(hs_bytes)
except (HandshakeError, asyncio.TimeoutError, asyncio.IncompleteReadError, OSError) as exc:
log_event(client_ip, None, None, "blocked", f"handshake error: {exc}")
log.info("BLOCK %s reason=handshake_error (%s)", client_ip, exc)
# str(exc) 가 빈 문자열인 예외들(OSError(), ConnectionResetError())
# 도 있어서 class 이름을 함께 남긴다 — 빈 reason 로 보이는 문제 회피.
reason = f"handshake error: {type(exc).__name__}: {exc}".rstrip(": ")
log_event(client_ip, None, None, "blocked", reason)
log.info("BLOCK %s reason=%s", client_ip, reason)
try:
client_writer.close()
await client_writer.wait_closed()
except Exception: # noqa: BLE001
pass
return
domain = hs.server_address.lower().strip()
@@ -247,9 +254,20 @@ async def handle_client(
msg = state.cfg.get("block_message") or DEFAULT_BLOCK_MESSAGE
client_writer.write(build_login_disconnect(msg))
await client_writer.drain()
# FIN 으로 마무리해서 클라이언트가 disconnect 패킷을 다 읽기 전에
# RST 가 가는 (그러면 "Connection reset" 으로 보인다) 일을 막는다.
try:
if client_writer.can_write_eof():
client_writer.write_eof()
except (OSError, NotImplementedError):
pass
except (OSError, ConnectionResetError, BrokenPipeError):
pass
try:
client_writer.close()
await client_writer.wait_closed()
except Exception: # noqa: BLE001
pass
return
backend_host, backend_port = target