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++
// ============================================================================
// [코드리뷰 문제] C++ - 인증 전(pre-auth) 연결의 핸드셰이크 타임아웃·자원 상한
// ----------------------------------------------------------------------------
// 시나리오 (세션/접속 - 서버-클라):
// - 클라가 TCP 로 접속하면 서버는 세션을 만들고 첫 패킷(인증 토큰)을 기다린다.
// - 인증이 끝나야 그 세션이 "플레이어"로 승격되어 게임 패킷을 처리한다.
// - 인증 전에도 서버는 세션 객체와 수신 버퍼를 잡아 두고 비동기 수신을 건다.
//
// 상황:
// - 어떤 연결은 TCP 만 맺고 인증 패킷을 보내지 않거나, 1바이트씩 아주 천천히 흘려보낸다.
// - 수백~수천 개의 그런 연결이 동시에 들어온다(정상 유저 접속과 섞여서).
//
// 과제:
// 이 코드가 위와 같은 연결들을 어떻게 취급하는지 분석하라. (A)에서 만든 세션의 수명,
// (B)에서 컨테이너에 넣는 동작, (C)의 버퍼 확보가 인증을 끝내지 않는 연결에 대해 각각
// 무엇을 보장(또는 보장하지 못)하는지 설명하라. 정상 유저 접속이 받는 영향과 수정안,
// 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <vector>
#include <memory>
#include <mutex>
int recvSome(int fd, uint8_t* buf, int cap); // <=0 이면 종료
void closeFd(int fd);
bool tryAuthenticate(const uint8_t* buf, int n); // 토큰 검증
struct Session {
int fd = -1;
std::vector<uint8_t> recvBuffer; // (C) 연결마다 즉시 확보
bool authenticated = false;
long playerId = 0;
explicit Session(int f) : fd(f), recvBuffer(64 * 1024) {}
};
class SessionManager {
public:
// accept 루프가 새 연결마다 호출
void onAccept(int fd) {
auto s = std::make_shared<Session>(fd); // (A) 세션 생성, 이후 인증 대기
{
std::lock_guard<std::mutex> lk(mtx_);
sessions_.push_back(s); // (B) 활성 세션으로 등록
}
// 각 연결의 수신은 워커/리액터가 receiveLoop 로 구동한다고 가정.
}
void receiveLoop(const std::shared_ptr<Session>& s) {
for (;;) {
int n = recvSome(s->fd, s->recvBuffer.data(), (int)s->recvBuffer.size());
if (n <= 0) { drop(s); return; }
handlePacket(s, n);
}
}
private:
void handlePacket(const std::shared_ptr<Session>& s, int n) {
if (!s->authenticated) {
if (tryAuthenticate(s->recvBuffer.data(), n))
s->authenticated = true; // 승격
return;
}
// ... 인증된 세션의 게임 패킷 처리 ...
}
void drop(const std::shared_ptr<Session>& s) {
{
std::lock_guard<std::mutex> lk(mtx_);
for (size_t i = 0; i < sessions_.size(); ++i)
if (sessions_[i] == s) { sessions_.erase(sessions_.begin() + i); break; }
}
closeFd(s->fd);
}
std::vector<std::shared_ptr<Session>> sessions_;
std::mutex mtx_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.