44. UDP 연결 마이그레이션의 경로 검증 부재 (세션 탈취·오배달, C#)
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: UDP 기반 실시간 게임 서버의 세션 라우팅과 "연결 마이그레이션".
// - 클라이언트는 로그인 시 발급받은 connectionId(암호학적으로 큰 난수)를 매 패킷 헤더에 담는다.
// - UDP 라 소스 주소(IP:포트)는 NAT 리바인딩·모바일 네트워크 전환으로 세션 도중 바뀔 수 있다.
// - 그래서 서버는 connectionId 로 세션을 찾고, 패킷이 새 주소에서 오면 세션의 원격 주소를
// 그 주소로 갱신해 이후 송신을 그쪽으로 보낸다(연결 마이그레이션).
//
// 요구사항:
// 1) 세션의 원격 주소를 바꿀 때는, 그 새 경로가 실제로 정당한 피어인지 검증해야 한다.
// 2) connectionId 만으로 원격 주소를 즉시 신뢰해 갱신하면 안 된다(스푸핑/탈취 위험).
// 3) 마이그레이션 검증이 끝나기 전까지는 기존(검증된) 경로로 계속 송신한다.
//
// 과제: OnPacket 을 검토하여 요구사항 1~3이 깨지는 지점을 (A)(B) 마커로 진단하고,
// 정확한 수정안과 더 나은 설계를 제시하라.
using System.Net;
public sealed class Session
{
public long ConnId;
public IPEndPoint Remote; // 현재 송신 대상 주소
public byte[] Key; // AEAD 세션 키
public void Dispatch(byte[] plaintext) { /* 게임 로직 처리 */ }
public void SendTo(IPEndPoint ep, byte[] data) { /* UDP 송신 */ }
}
public sealed class UdpSessionRouter
{
private readonly System.Collections.Generic.Dictionary<long, Session> _byConn = new();
public void Register(Session s) { _byConn[s.ConnId] = s; }
// 소켓 수신 콜백: 하나의 UDP 데이터그램 도착.
public void OnPacket(IPEndPoint source, long connId, byte[] frame)
{
if (!_byConn.TryGetValue(connId, out var s))
return;
if (!s.Remote.Equals(source))
{
s.Remote = source; // (A) 새 주소로 즉시 원격 갱신(마이그레이션)
}
byte[] plaintext = AeadOpen(s.Key, frame);
if (plaintext == null)
return;
s.Dispatch(plaintext); // (B) 갱신된 remote 로 처리·이후 송신
}
private static byte[] AeadOpen(byte[] key, byte[] frame)
{
// 복호화+인증 성공 시 평문, 실패 시 null (구현 생략)
return frame;
}
} 결함 코드 · C++
// 시나리오: UDP 기반 실시간 게임 서버의 세션 라우팅과 연결 마이그레이션 (C++ 서버).
// - 클라이언트는 connectionId(큰 난수)를 매 패킷에 담는다.
// - UDP 소스 주소(IP:포트)는 NAT 리바인딩/모바일 전환으로 바뀔 수 있어, 새 주소에서 온
// 패킷이면 세션의 remote 를 갱신해 이후 송신을 그쪽으로 보낸다.
//
// 요구사항:
// 1) remote 를 바꿀 때는 새 경로가 정당한 피어인지 검증한다.
// 2) connectionId 만으로 remote 를 즉시 신뢰·갱신하지 않는다.
// 3) 검증 완료 전에는 기존(검증된) 경로로 계속 송신한다.
// 4) 두 주소가 같은지(같은 피어인지) 비교할 때, 주소 동등성을 정확히 판정한다.
//
// 과제: onPacket 을 검토하여 요구사항 1~4가 깨지는 지점을 (A)(B) 마커로 진단하고,
// 정확한 수정안과 더 나은 설계를 제시하라.
//
// 컴파일: g++ -std=c++17 -pthread -fsyntax-only problem.cpp
#include <cstdint>
#include <cstring>
#include <vector>
#include <unordered_map>
#include <netinet/in.h> // sockaddr_in, sockaddr_storage
struct Session
{
std::int64_t connId = 0;
sockaddr_storage remote{}; // 현재 송신 대상
std::vector<std::uint8_t> key;
void dispatch(const std::vector<std::uint8_t>& pt); // 게임 로직
void sendTo(const sockaddr_storage& ep, const std::uint8_t* d, std::size_t n);
};
class UdpSessionRouter
{
public:
void registerSession(Session* s) { byConn_[s->connId] = s; }
void onPacket(const sockaddr_storage& source, std::int64_t connId,
const std::uint8_t* frame, std::size_t len)
{
auto it = byConn_.find(connId);
if (it == byConn_.end()) return;
Session* s = it->second;
// (A) 소스가 현재 remote 와 다르면 마이그레이션
if (std::memcmp(&s->remote, &source, sizeof(sockaddr_storage)) != 0)
{
s->remote = source; // 새 주소로 즉시 원격 갱신
}
std::vector<std::uint8_t> pt;
if (!aeadOpen(s->key, frame, len, pt))
return;
s->dispatch(pt); // (B) 갱신된 remote 로 처리·이후 송신
}
private:
static bool aeadOpen(const std::vector<std::uint8_t>&, const std::uint8_t*,
std::size_t, std::vector<std::uint8_t>& out)
{ out.clear(); return true; } // 복호화+인증 성공 여부 (구현 생략)
std::unordered_map<std::int64_t, Session*> byConn_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.