Client Server Connection | Gaffer On Games
Client Server Connection | Gaffer On Games
Introduction Hi, I’m Glenn Fiedler and welcome to Building a Game Network Protocol. So far in this article series we’ve discussed how games read and write packets, how to unify packet read and write into a single function, how to fragment and re-assemb
gafferongames.com
※ 번역이 아니라 정리입니다!
UDP를 사용하는 이유
게임 개발에서 굳이 UDP를 사용하는 이유가 무엇일까. 웹 개발자들은 TCP로도 충분하다고 생각하지만, 게임에서는 TCP만으로는 Time critical 데이터를 보내기 어렵기 때문이다. TCP는 패킷의 순서를 지켜주고 드랍된 패킷에 대해 재전송하는 특징이 있다. 이러한 작업 때문에 패킷이 전달되는 시간이 지체되는게 일반적이다.
Head of line blocking이란, 이런 특성에 의해 시간이 지체되는 현상을 말한다. 패킷이 드랍되면 이 패킷을 재전송 해야함과 동시에 뒤이어 전송할 패킷과의 순서까지 보장해주어야 하므로 뒤에 있는 패킷은 대기를 해야하는 상황에 놓이게 된다. 따라서 클라이언트가 받는 패킷의 정보는 과거의 정보일 경우가 많다. 이러한 정보는 특히 FPS와 같은 게임에서는 쓸모없는 옛날 정보이므로 버려야 한다. 이런 정보를 사용하게 되면 게임이 끊기는 현상이 발생하며 유저가 실시간으로 상호작용하기 불편하다.
UDP Server Protocol
UDP는 연결성을 제공해주지 않으므로 클라이언트와 서버가 지속적으로 소통을 해야 하는 단점이 있다. UDP 서버는 클라이언트와 주기적으로 1초에 30-60번 정도 패킷을 송수신한다. 그리고 게임의 상태는 모든 클라이언트에게 1초에 10, 20 혹은 60번 보낸다. 만약 패킷이 일정시간 전달되지 않는다면 연결이 해제되었음을 의미한다. 저자는 이 timeout을 5초 정도로 설정하는 것이 적절하다고 생각한단다. 물론 게임의 특성에 따라 더 짧게 혹은 길게 설정해도 된다.
서버와의 연결을 위한 연결 요청 패킷을 UDP로 보낸다. 그런데 UDP이기 때문에 패킷이 드랍되어도 재전송하지 않는다. 따라서 서버로부터 Accept 패킷이 전달될 때까지 주기적으로 연결 요청 패킷을 다시 보내야 한다. 서버는 연결 요청 패킷 받으면 남은 자리를 할당해주고 Accept 패킷을 보낸다. 자리 판단은 클라이언트의 주소 정보로 한다. 자리가 없으면 Deny 패킷을 보낸다. 이 때, 클라이언트는 지속적으로 패킷을 보냈기 때문에 서버는 요청 패킷을 한번 더 받을 수도 있다. 이 경우에는 Accept를 보내야 한다. 서버 측에서는 엉뚱한 클라이언트(완전히 다른 프로그램이나 이전 버전의 프로그램)가 의도적으로 보내는 패킷을 걸러야 한다. 이것은 TCP의 Listen 소켓에서도 마찬가지인데, 클라이언트를 판별하기 위한 CRC32 값을 같이 보내야한다. TCP는 연결 후에는 이러한 작업이 필요없지만, UDP의 경우 매번 올바른 클라이언트인지 판별하기 위해 필요하다.
※ CRC32: 데이터의 오류를 검증하는 코드를 발행하는 기술, 생성 다항식을 사용해 값을 도출해낸다. 일종의 해시 함수.
그러나 이것이 어떻게 에러를 검증할 수 있는지는 모르겠다. 클라이언트 코드에 해시함수가 있다면 그것을 악의적인 프로그램에 그대로 적용해서 보낼 수도 있지 않을까?
문제점
- 패킷이 도청당하면 클라이언트의 주소정보를 알아낼 수 있고, 이는 DDoS 공격에 이용된다.
- 도청된 패킷의 주소정보로 서버의 클라이언트 슬롯들을 악의적으로 채울 수 있어 문제가 된다.
- 공격자가 클라이언트의 포트번호만 바꿔서 악의적으로 접속을 시도할 수 있다. 포트번호는 클라이언트의 독립적인 끝점을 구성하는 빠질 수 없는 중요한 요소라서 IP 주소만으로 클라이언트를 판별할 수는 없다. 같은 NAT를 공유하는 클라이언트가 포트정보만 다른 상태에서 접속하는 사례는 아주 흔하다. 만약 IP로만 판별하는 게임이 존재한다면 PC방 같이 하나의 공유기로 여러 컴퓨터를 연결하는 장소에서는 서비스 자체가 불가능하다.
- 공격자가 패킷을 중간에서 수정할 수 있다. CRC32가 있긴 하지만, 다시 계산해서 적용하면 변경 사실을 숨길 수 있다.
- 공격자는 클라이언트와 서버의 IP/Port 정보를 알 수 있다. 그리고 이를 이용해 가짜 서버 혹은 가짜 클라이언트로 위장하여 네트워크 통신을 탈취할 수 있다.
- 클라이언트 혹은 서버의 연결 해제를 바로바로 확인할 수 없다. UDP 환경에서는 Time out으로 연결 해제를 판별하기 때문이다. 따라서 Disconnect 패킷을 전송하는 것으로 해결할 수 있으나, 공격자가 서버 / 클라이언트를 위장할 수 있는 상태에서는 큰 의미가 없다.
- 만약 클라이언트가 불안정하게 접속이 끊겨서 재접속을 시도하면 서버는 단순히 접속을 허용할 것이다. 하지만 클라이언트가 현재 안정된 상황에 있지 않은데도 접속된 상태로 판별하게 된다. 잘 이해가 안됨
Connection Protocol 개선
서버는 이제 연결 요청이 오는 즉시 허용하지 않고, Challenge 패킷을 보내어 3-way 방식으로 유효성을 검증하는 과정을 거친다. Challenge 패킷에는 Challenge 데이터를 담아서 보낸다. 클라이언트는 이 데이터를 가지고 challenge reponse 를 생성하여 보낸다. 추가적으로 클라이언트에서 서버로 보내는 패킷에 padding 데이터를 붙인다. padding 데이터는 DDoS 공격을 최소화하기 위해 사용된다. 잘 이해가 안됨
보안성을 더 강화하기 위해 salt라는 랜덤값을 붙인다. 요청 패킷에 클라이언트의 salt를 붙이고, challenge에는 서버의 salt를 붙인다. 이렇게 하면 클라이언트의 salt값을 가지고 클라이언트의 주소 값을 구별할 수 있다. challenge response에서는 클라이언트의 salt와 서버의 salt를 XOR 연산자로 계산하여 보낸다. 이 값은 이제 나머지 통신에서 클라이언트를 확인하기 위한 용도로 사용될 것이다.
Disconnect 패킷은 10개를 한번에 보낸다. 10개 중 하나라도 닿으면 연결이 해제되도록 한다.
결론
IP와 포트번호만 알고도 DDoS 공격 혹은 위장과 같은 네트워크 공격으로부터 보호할 수 있게 되었다. 하지만 패킷은 여전히 외부에서 수정될 수 있으며, salt 값을 읽어버리면 보완기능이 무산될 수 있다.
정말 너무 어렵다. 특히 네트워크 공격에 대비하는 방법들이 정말로 정교해야 한다는 것을 알았다. 그리고 여전히 이해가 안되는 부분이 많다. 특히 padding이나 salt 부분이 이해가 안됐다. padding이 왜 DDoS의 공격 규모를 줄여주는지 모르겠고 salt값을 중간에 가로채고 클라이언트 프로그램의 소스코드를 열람하면 salt의 의미가 있는 것인지 모르겠다. challenge 데이터는 공개키 암호화를 생각하면 이해가 되는 것 같기도 하다.
'Network' 카테고리의 다른 글
[Gaffer On Games] Reading and Writing Packets 정리 (0) | 2022.06.23 |
---|