24. 인증 전 연결의 핸드셰이크 타임아웃·자원 상한 부재 (slowloris 류 고갈)

난이도 중 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 인증 전(pre-auth) 연결의 핸드셰이크 타임아웃·자원 상한
// ----------------------------------------------------------------------------
// 시나리오 (세션/접속 - 서버-클라):
//   - 클라가 TCP 로 접속하면 서버는 세션을 만들고 첫 패킷(인증 토큰)을 기다린다.
//   - 인증이 끝나야 그 세션이 "플레이어"로 승격되어 게임 패킷을 처리한다.
//   - 인증 전에도 서버는 세션 객체와 수신 버퍼를 잡아 두고 비동기 수신을 건다.
//
// 상황:
//   - 어떤 연결은 TCP 만 맺고 인증 패킷을 보내지 않거나, 1바이트씩 아주 천천히 흘려보낸다.
//   - 수백~수천 개의 그런 연결이 동시에 들어온다(정상 유저 접속과 섞여서).
//
// 과제:
//   이 코드가 위와 같은 연결들을 어떻게 취급하는지 분석하라. (A)에서 시작하는 세션의
//   수명, (B)에서 컬렉션에 넣는 동작, (C)의 버퍼 할당이 인증을 끝내지 않는 연결에 대해
//   각각 무엇을 보장(또는 보장하지 못)하는지 설명하라. 정상 유저 접속이 받는 영향과
//   수정안, 더 나은 설계를 제시하라.
//   (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Threading.Tasks;

public sealed class Session
{
    public readonly Socket Socket;
    public readonly byte[] RecvBuffer = new byte[64 * 1024];   // (C) 연결마다 즉시 확보
    public bool Authenticated;
    public long PlayerId;
    public Session(Socket s) { Socket = s; }
}

public sealed class SessionManager
{
    private readonly List<Session> _sessions = new();
    private readonly object _lock = new();

    // accept 루프가 새 연결마다 호출
    public void OnAccept(Socket socket)
    {
        var session = new Session(socket);          // (A) 세션 생성, 이후 인증 대기
        lock (_lock) { _sessions.Add(session); }    // (B) 활성 세션으로 등록

        _ = ReceiveLoopAsync(session);
    }

    private async Task ReceiveLoopAsync(Session s)
    {
        while (true)
        {
            int n = await s.Socket.ReceiveAsync(s.RecvBuffer, SocketFlags.None);
            if (n <= 0) { Drop(s); return; }
            HandlePacket(s, n);
        }
    }

    private void HandlePacket(Session s, int n)
    {
        if (!s.Authenticated)
        {
            if (TryAuthenticate(s, s.RecvBuffer, n))   // 토큰 검증
                s.Authenticated = true;                // 승격
            return;
        }
        // ... 인증된 세션의 게임 패킷 처리 ...
    }

    private bool TryAuthenticate(Session s, byte[] buf, int n) { /* 토큰 파싱/검증 */ return n >= 16; }

    private void Drop(Session s)
    {
        lock (_lock) { _sessions.Remove(s); }
        try { s.Socket.Close(); } catch { }
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.