MTA:SA 서버를 DDoS 공격으로부터 방어하기
MTA:SA 서버는 서로 분리된 세 가지 서비스를 제공합니다. 22003 UDP의 게임, 22005 TCP의 HTTP 서버, 22126 UDP의 ASE 쿼리입니다. 각각을 어떻게 지키는지, 그리고 어느 공격 규모부터 서버 앞단 네트워크의 필터링만이 통하는지 정리했습니다.
Multi Theft Auto: San Andreas 서버는 DDoS 공격을 받을 때 다른 어떤 GTA 멀티플레이 프로젝트와도 다르게 움직입니다. 서로 분리된 네트워크 서비스 세 개를 동시에 제공하기 때문입니다. 22003 UDP의 게임 트래픽, 22005 TCP의 완전한 HTTP 서버, 그리고 22126 UDP의 ASE 쿼리입니다. 이 세 서비스는 각각 따로 공격할 수 있고, 각각 다르게 무너집니다. 이 글은 먼저 추가 비용 없이 직접 지킬 수 있는 것을 보여 주고, 그다음 그 조치가 회선의 물리 법칙 앞에서 어디에서 끝나는지, 마지막으로 MTA:SA를 위한 실효성 있는 DDoS 방어가 서버 앞단 네트워크에서 무엇을 해내야 하는지 설명합니다.
공격이 지금 진행 중이라면 가장 중요한 질문은 세 서비스 가운데 어느 쪽이 맞고 있는지입니다. 플레이어는 접속을 유지하는데 접속 시점에 리소스를 더 이상 받아 오지 못한다면 22005의 HTTP 서버가 맞고 있습니다. 접속해 있는 플레이어는 평소처럼 계속 노는데 서버가 브라우저에서 사라진다면 22126의 ASE 쿼리가 맞고 있습니다. 모든 연결이 동시에 끊어진다면 22003이 표적이거나 회선이 꽉 찬 것입니다. 모든 내용은 Debian 12, Debian 13, Ubuntu 22.04 LTS 또는 Ubuntu 24.04 LTS에서 운영하는 MTA 서버를 기준으로 하며, 명령은 root 기준으로 적었으니 일반 사용자라면 앞에 sudo를 붙이세요.
MTA:SA 서버가 DDoS 공격의 표적이 자주 되는 이유
MTA:SA 프로젝트는 자기 주소를 스스로 공개해야 하기 때문에 손쉬운 표적이 됩니다. 서버는 마스터 서버 목록에 등록하고 그다음 외부에서 오는 조회에 응답할 때에만 게임 브라우저에 나타납니다. 목록에는 IP 주소와 포트가 평문으로 들어 있으니, 공격자에게 사전 정찰은 아예 필요하지 않습니다.
여기에 씬 자체가 더해집니다. 독일어권과 브라질의 롤플레이 서버, 드리프트 서버, DayZ 이식 프로젝트가 같은 플레이어층을 두고 경쟁하고, 사람이 가장 많은 시간대의 장애는 가장 크게 눈에 띕니다. 차단된 플레이어, 갈라진 팀, 경쟁 프로젝트 가운데 어느 쪽도 저녁 한때를 쓸 수 없게 만드는 데에 실력이나 이렇다 할 돈이 필요하지 않습니다. 씬에서 booter 또는 stresser라고 부르는, 돈을 내고 빌리는 공격 서비스는 월 몇 유로에 정확히 두 가지 결과를 팝니다. MTA 서버를 몇 분간 오프라인으로 만드는 것, 또는 랙 스파이크로 플레이할 수 없게 만드는 것입니다. DDoS 공격이 기술적으로 무엇이고 어떤 공격 유형이 있는지는 DDoS 공격이란 무엇인가? 글에서 설명합니다.
기술적으로 MTA:SA는 다른 멀티플레이 모드보다 두 지점에서 공격자에게 더 유리합니다. 첫째, 쿼리가 별도의 UDP 포트에 놓여 있고 단 1바이트에 대해 수 킬로바이트 크기의 응답을 보냅니다. 둘째, 모든 MTA 서버에는 실행 중인 모든 리소스의 클라이언트 측 파일을 내주는 HTTP 서버가 딸려 있고, 그것도 인증 없이 요청하는 누구에게나 내줍니다.
실제로 문제가 되는 포트
MTA:SA 서버에는 정확히 포트 세 개가 필요합니다. 게임용 22003 UDP, 내부 HTTP 서버용 22005 TCP, ASE 쿼리용 22126 UDP입니다. 세 번째 포트는 자유롭게 고를 수 있는 설정이 아니라 게임 포트에 123을 더한 값으로 고정됩니다. serverport를 22010으로 두면 쿼리는 22133에 놓입니다.
| 포트 | 프로토콜 | 용도 | mtaserver.conf의 지시어 | 공개 네트워크에 열어야 하나요 |
|---|---|---|---|---|
| 22003 | UDP | 게임 트래픽, 연결 수립, 동기화, 음성 전송 | <serverport>22003</serverport> |
예 |
| 22005 | TCP | 내부 HTTP 서버: 리소스 다운로드, webadmin, resourcebrowser | <httpport>22005</httpport> |
예, 다운로드를 외부로 옮기지 않는 한 |
| 22126 | UDP | ASE 쿼리: 서버 브라우저, 마스터 서버 목록, 상태 페이지, Discord 봇 | <serverport>에 123을 더한 값으로 정해짐 |
서버 브라우저 등록용으로만 |
| 22 | TCP | 운영자의 SSH 접속 | mtaserver.conf에 없음 | 아니요, 본인 주소로 제한 |
| 3306 | TCP | 게임모드 뒤에 있는 MariaDB 또는 MySQL | mtaserver.conf에 없음 | 아니요, 127.0.0.1에 바인딩 |
딸려 오는 mtaserver.conf에 그대로 적혀 있는데도 자주 간과되는 세부가 두 가지 있습니다. httpport는 serverport와 같은 숫자를 가질 수 있습니다. 한쪽은 TCP, 다른 쪽은 UDP이기 때문입니다. 그리고 serverip는 auto로 되어 있고 그대로 두어야 합니다. 값을 고정해 넣으면 ASE 소켓이 정확히 그 주소에 묶이고, 주소가 바뀌는 순간 목록 등록이 깨집니다.
ASE 쿼리 프로토콜이 증폭기가 되는 이유
ASE(All-Seeing Eye)는 순수한 UDP 쿼리 프로토콜입니다. 패킷의 첫 바이트가 응답을 결정하고, 연결 수립 절차는 없습니다. MTA 서버는 다섯 가지 조회를 알고 있고 22126에서 그에 응답합니다.
s는 완전한 ASE 조회입니다. 응답은EYE1로 시작하며 서버 이름, 게임 유형, 맵 이름, 버전, 비밀번호 설정 여부, 플레이어 수,setRuleValue로 설정된 모든 규칙의 전체 목록, 그리고 접속해 있는 모든 플레이어의 이름, 점수, 핑을 담습니다. 이 응답에는 크기 제한이 없습니다.b와r는 게임 브라우저를 위한 더 가벼운 조회입니다. 응답은EYE2로 시작하고, 단편화를 피하기 위해 소스 코드에서 1,340 바이트에서 잘립니다.x는 축약된 상태 메시지를,v는 ASE 버전 식별자만 돌려줍니다.
여기서 문제가 나옵니다. 요청은 페이로드 1 바이트 하나로 이뤄지고, 회선 위에서는 29 바이트입니다(IP 헤더 20 바이트, UDP 헤더 8 바이트, 페이로드 1 바이트). 페이로드가 1,400 바이트인 응답은 회선 위에서 1,428 바이트입니다. 비율은 약 49배이고, UDP에는 연결 수립 절차가 없으므로 출발지 주소를 위조할 수 있습니다. 따라서 공격자는 여러분의 게임에 한 번도 들어오지 않고도 여러분의 서버를 제3의 표적을 향한 증폭기로 쓸 수 있습니다. 완전한 조회에서는 이 계수가 플레이어 수와 함께, 그리고 게임모드가 설정하는 규칙 하나하나와 함께 커집니다.
MTA는 이에 맞서 내장된 제동 장치 두 개를 갖고 있는데, 어떤 플러드가 통하고 어떤 것이 통하지 않는지를 설명해 주므로 알아 둘 만합니다. 서버는 출발지 주소별로 6초에 최대 다섯 번의 조회에만 응답하고, 그 뒤 7초 동안 그 주소를 무시합니다. 또한 응답을 요청마다 새로 조립하지 않고 10초 동안 임시로 저장해 둡니다. 그러나 목록에 서로 다른 출발지 주소가 100개를 넘어 동시에 들어오는 순간, 출발지 주소별 집계는 완전히 건너뜁니다. 봇넷에서 오거나 출발지를 위조한 분산 플러드에서는 바로 이것이 일상적인 상황이고, 그래서 내장된 제동 장치는 제대로 된 공격에는 도움이 되지 않습니다.
돈을 쓰기 전에 직접 할 수 있는 일
이 절이 가장 길고, 의도한 바입니다. 설정이 깔끔한 MTA 서버는 어디에 놓여 있든 작거나 중간 규모의 공격을 자체 힘으로 견뎌 냅니다.
1. 현황 파악: 무엇이 대기하고 있나요?
먼저 서버가 외부에 무엇을 내주고 있는지 확인하세요. 추측하지 말고 직접 확인합니다.
ss -lntup
MTA 프로세스의 세 줄이 나오는 것이 정상입니다. UDP의 0.0.0.0:22003, TCP의 0.0.0.0:22005, UDP의 0.0.0.0:22126입니다. 여기에 0.0.0.0:3306의 데이터베이스나 웹 서버, 잊고 방치한 음성 서비스가 더 나타난다면 그것은 내려야 합니다. 공격자의 시선은 외부에서 실행하는 포트 스캔이 보여 줍니다.
nmap -Pn -sU -p 22003,22126 YOUR.SERVER.IP.ADDRESS
nmap -Pn -p 22005 YOUR.SERVER.IP.ADDRESS
서버는 이를 위한 자체 콘솔 명령도 갖고 있습니다. 서버 콘솔에서 openports는 세 포트가 모두 외부에서 닿는지 확인해 줍니다.
2. MTA가 실제로 필요한 세 포트만 열어 두기
UFW에서 쓸 만한 기본 구성은 다음과 같으며, 스스로 접속이 막히지 않도록 반드시 이 순서를 지키세요.
ufw allow 22/tcp comment 'SSH'
ufw allow 22003/udp comment 'MTA 게임'
ufw allow 22005/tcp comment 'MTA HTTP'
ufw allow 22126/udp comment 'MTA ASE'
ufw default deny incoming
ufw default allow outgoing
ufw --force enable
ufw status verbose
복구 방법까지 포함한 전체 안내는 UFW 방화벽 설정하기 글에 있습니다. KernelHost의 KVM 루트 서버와 Dedicated Server라면 회선이 포화된 상태에서도 고객 포털의 VNC 콘솔로 시스템에 접근할 수 있습니다.
데이터베이스는 공개 네트워크에 있어서는 안 됩니다. ss -lntp | grep 3306이 0.0.0.0:3306을 보여 주면 /etc/mysql/mariadb.conf.d/50-server.cnf에 bind-address = 127.0.0.1 줄을 넣고 서비스를 다시 시작하세요.
3. 서버 목록에서 사라지지 않으면서 ASE 포트 제한하기
SA-MP와 달리 MTA:SA에서는 쿼리가 별도의 포트에 놓여 있으므로, 게임 운영과 무관하게 제한할 수 있습니다. 이것이 이 구조의 가장 큰 실질적 장점입니다. 22126에 규칙을 하나 걸어도 플레이어는 단 한 명도 튕기지 않습니다.
규칙 세트가 UFW와 부딪히지 않도록 nftables에서 별도 테이블을 쓰면 다음과 같습니다.
nft add table inet mtaguard
nft add chain inet mtaguard input '{ type filter hook input priority -150 ; policy accept ; }'
nft add rule inet mtaguard input udp dport 22126 meter aseperip '{ ip saddr limit rate over 3/second burst 6 packets }' drop
nft add rule inet mtaguard input udp dport 22126 limit rate over 2000/second burst 500 packets drop
nft list table inet mtaguard
첫 번째 규칙은 개별 출발지 주소를, 두 번째 규칙은 포트 전체를 제한합니다. 두 규칙이 함께 있어야 합니다. 주소별로만 제한하면 분산 플러드가 수많은 개별 출발지 사이의 틈으로 흘러 들어옵니다. 값은 좁게 잡았고, 실제 서버 브라우저가 몇 초에 한 번만 서버를 조회하므로 여기서는 그래도 괜찮습니다. iptables에서는 hashlimit 모듈이 같은 일을 합니다.
iptables -A INPUT -p udp --dport 22126 -m hashlimit --hashlimit-name mta_ase \
--hashlimit-mode srcip --hashlimit-above 3/sec --hashlimit-burst 6 \
--hashlimit-htable-expire 30000 -j DROP
포트를 완전히 닫으려는 시도는 저울질이지 비법이 아닙니다. ASE가 없으면 서버가 게임 브라우저에서 사라지고, 그와 함께 자연 유입도 사라집니다. 그래도 그렇게 하고 싶다면 <ase>0</ase>만으로는 충분하지 않습니다. 소스 코드에서 포트 개방은 인터넷 모드와 LAN 모드의 논리합에 걸려 있으므로, <donotbroadcastlan>0</donotbroadcastlan>이 그대로 있는 한 <ase>0</ase>에서도 소켓은 계속 열려 있습니다. 정말로 포트를 닫으려면 둘 다 설정해야 합니다.
<ase>0</ase>
<donotbroadcastlan>1</donotbroadcastlan>
성장하는 프로젝트에게 더 정직한 길은 이렇습니다. 포트는 열어 두고, 속도를 제한하고, 게임모드가 setRuleValue로 불필요한 규칙을 공개하지 않게 해서 증폭 효과를 작게 유지하는 것입니다. 규칙 하나하나가 완전한 조회에 들어가고 응답을 키웁니다.
4. 내부 HTTP 서버의 부담 덜기
22005의 HTTP 서버는 MTA:SA에서 독자적인 공격 표면입니다. 접속하는 플레이어마다 실행 중인 모든 리소스의 클라이언트 측 파일 전부를 여기서 받아 가기 때문입니다. 자체 모델을 쓰는 롤플레이 프로젝트라면 이것이 금세 수백 메가바이트가 되고, 그것도 수백 개의 개별 파일로 나뉘어 있습니다. 내장 서버는 의도적으로 단순하게 만들어져 있습니다. 압축이 없고, 작업 스레드도 정해진 수만큼만 있습니다. 동시 요청 몇십 개면 실제 플레이어가 몇 분씩 로딩 화면에 매달리게 됩니다.
가장 효과적인 조치는 다운로드를 게임 서버에서 완전히 빼내는 것입니다. MTA는 이를 위해 내줄 파일을 mods/deathmatch/resource-cache/http-client-files에 스스로 준비해 둡니다. 이 폴더를 nginx나 lighttpd로 내주고 그 주소를 mtaserver.conf에 적어 넣습니다.
<httpdownloadurl>http://cdn.your-domain.tld/mta</httpdownloadurl>
이것으로 두 가지를 한꺼번에 얻습니다. 다운로드가 그 일을 하도록 만들어진 웹 서버를 거쳐 나가고, 더 이상 게임 서버의 주소를 거치지 않습니다. 웹 서버가 다른 머신에 있거나 Content Delivery Network 뒤에 있으면 다운로드를 향한 플러드가 게임 운영에 닿지 않습니다. 중요한 점이 하나 있습니다. 외부 주소가 잘못되어 있거나 닿지 않으면 MTA는 아무 말 없이 내부 서버로 되돌아갑니다.
내부 서버를 계속 쓴다면 그 자체의 한계를 활용하세요. mtaserver.conf에서는 다음과 같습니다.
<httpmaxconnectionsperclient>5</httpmaxconnectionsperclient>
<httpdosthreshold>20</httpdosthreshold>
<http_dos_exclude></http_dos_exclude>
<httpthreadcount>8</httpthreadcount>
httpmaxconnectionsperclient는 클라이언트별 동시 연결을 허용 범위 1에서 8 사이에서 5로 제한합니다. httpdosthreshold는 IP 주소 하나가 짧은 시간에 몇 개의 연결을 맺을 수 있는지를 제한하며 기본값은 20입니다. http_dos_exclude는 여기서 개별 주소를 제외하는데, 예를 들어 여러분의 상태 페이지가 그렇습니다. httpthreadcount는 작업 스레드 수를 정하며 기본값은 8, 범위는 1에서 20입니다. 값을 높이면 작은 파일이 많을 때 도움이 되지만, 게임 운영에서 빠지는 연산 시간을 대가로 씁니다.
같은 포트에서 무엇이 더 나가고 있는지도 생각하세요. 리소스 webadmin과 resourcebrowser는 딸려 오는 설정에서 이미 실행되어 있고 22005를 통해 브라우저에서 닿습니다. 관리 화면은 보호 없이 공개 네트워크에 두어서는 안 됩니다. acl.xml에 권한을 깔끔하게 부여하고, 긴 무작위 비밀번호를 쓰는 별도 계정을 만들고, 필요하지 않으면 리소스를 중지하세요.
5. mtaserver.conf의 내장 한계 활용하기
MTA에는 대부분의 프로젝트가 쓰는 것보다 많은 보호 한계가 들어 있습니다. 일부는 소스 코드에 고정되어 있고, 일부는 mtaserver.conf에 있습니다. 이 표는 공격 상황에서 역할을 하는 것들을 정리한 것입니다.
| 한계 | 기본값 | 허용 범위 | 무엇에 통하나 |
|---|---|---|---|
| 출발지 주소별 ASE 조회(소스 코드에 고정) | 6초에 5회, 그 뒤 7초 동안 무시 | 설정 불가 | 단일 조회 플러더, 분산 플러드에는 통하지 않음 |
| ASE 응답의 임시 저장(소스 코드에 고정) | 10초 | 설정 불가 | 반복 조회로 생기는 연산 부하 |
| 출발지 주소별 접속(소스 코드에 고정) | 30초에 4회, 그 뒤 30초 동안 무시 | 설정 불가 | 개별 주소에서 오는 접속 플러드 |
httpdosthreshold |
20 | 1에서 100 | 주소별 HTTP 연결 플러드 |
httpmaxconnectionsperclient |
5 | 1에서 8 | 한 클라이언트의 병렬 다운로드 |
httpthreadcount |
8 | 1에서 20 | 리소스 다운로드 시 생기는 대기열 |
player_triggered_event_interval |
1000밀리초 | 50에서 5000 | 클라이언트에서 오는 이벤트 플러드 |
max_player_triggered_events_per_interval |
100 | 1에서 1000 | 클라이언트에서 오는 이벤트 플러드 |
maxplayers |
32 | 자유 | 완전한 조회의 크기와 슬롯 고갈 |
bandwidth_reduction |
medium | none, medium, maximum | 서버가 꽉 찼을 때의 송신 대역폭 |
세 가지 설정은 의식적인 결정을 할 만합니다. maxplayers는 32로 되어 있고 실제에 맞아야 합니다. 슬롯이 하나 늘어날 때마다 완전한 조회가 커지고, 공격자가 차지할 수 있는 연결 수도 늘어납니다. bandwidth_reduction은 medium으로 되어 있으며, maximum 값은 송신 부하를 눈에 띄게 낮추지만 동기화 정확도를 대가로 씁니다. 그리고 <password></password>는 목록 등록을 유지한 채로 서버를 손쉽게 닫힌 모임으로 만듭니다. 접속 플러드가 진행되는 중에 쓸 수 있는 가장 빠른 비상 제동입니다.
6. 접속 플러드와 이벤트 플러드 구분하기
두 가지 공격 패턴은 회선이 아니라 게임 로직을 노리고, 자주 서로 혼동됩니다.
접속 플러드는 실제 연결을 빠르게 잇달아 맺어, 모든 슬롯이 차거나 서버가 연결 수립을 더 이상 따라가지 못할 때까지 밀어붙입니다. MTA는 이를 스스로 출발지 주소별로 30초에 네 개의 연결로 제한하고, 그 뒤 30초 동안 그 주소를 무시합니다. 제동 장치가 지금 무엇을 하고 있는지는 콘솔 명령 debugjoinflood가 보여 줍니다. 이 한계는 주소별로 작동하므로 주소가 천 개인 봇넷은 그 옆을 지나갑니다. 이에는 서버 비밀번호, 게임모드의 허용 목록, 22003의 속도 제한이 도움이 됩니다.
이벤트 플러드는 반대로 이미 접속한 플레이어에게서 옵니다. 조작된 클라이언트가 triggerServerEvent를 반복문으로 내보내, 서버가 연산 시간을 더 이상 대지 못할 때까지 밀어붙입니다. MTA는 이를 위해 공장 설정으로 플레이어당 1초에 100개의 이벤트를 허용하고, 그것을 넘어서면 이벤트 플러드에 관한 메시지와 함께 내보냅니다. 게임모드가 작은 이벤트를 많이 쓴다면 값을 낮추기 전에 먼저 확인하세요. 너무 좁게 설정하면 자기 플레이어를 내쫓습니다.
이와 별개로 서버 측에는 어디서나 같은 원칙이 적용됩니다. 클라이언트가 함께 보내 온 값을 절대 믿지 말고, 플레이어는 이벤트의 발신자에서 알아내고, 데이터베이스 조회를 일으키는 모든 것을 제한하세요. 조회를 시작하는 검증되지 않은 이벤트 하나면 어떤 네트워크 공격도 없이 서버를 멈춰 세울 수 있습니다.
7. 서버 목록, IP 주소, 그리고 주소가 또 무엇을 드러내는가
여러분의 IP 주소는 비밀로 지킬 수 없습니다. 한 번 접속한 플레이어는 모두 그것을 알고 있고, 마스터 서버 목록의 등록이 어차피 그것을 공개합니다. 앞에 도메인을 두는 것도 도움이 되지 않습니다. 클라이언트는 이름을 한 번 풀어낸 뒤 그다음부터 주소와 직접 이야기합니다.
대신 여러분의 주소가 또 무엇을 드러내는지 확인하세요. MTA 프로젝트에서 흔한 누출은 DNS의 오래된 A와 AAAA 레코드, 같은 머신에 있는 프로젝트 홈페이지, ASE 쿼리를 공개적으로 읽어 오는 상태 표시 Discord 봇, 오래된 호스트명이 담긴 TLS 인증서, 그리고 초창기의 포럼 글입니다. 여기서 많은 프로젝트가 너무 늦게 배우는 원칙이 나옵니다. 보호되는 주소로 옮길 때는 옛 주소도 동시에 바꾸세요. 옛 주소가 그대로 남아 있으면 모든 스캐너 데이터베이스에 남아 있고, 공격은 방어 옆을 지나갑니다.
mtaserver.conf의 두 항목은 노출과 직접 관련됩니다. <serverip>auto</serverip>는 왜 아닌지를 정확히 알지 않는 한 auto로 남겨 둡니다. 그리고 <owner_email_address>는 채워 두어야 합니다. 항목이 없거나 잘못되어 있으면 마스터 서버 목록에서의 노출이 나빠질 수 있습니다.
8. 비상 상황에 데이터가 있도록 로그 기록하기
가장 중요하면서 거의 아무도 미리 하지 않는 단계가 있습니다. 모든 것이 정상으로 돌아가는 동안에 기준값을 만들어 두는 것입니다. 평상시 값이 없으면 사건이 끝난 뒤에 초당 40,000 패킷이 많았던 것인지 그냥 금요일 저녁이었던 것인지 말할 수 없습니다. apt-get install -y vnstat sysstat로 측정이 상시 돌아갑니다.
사건이 진행되는 동안에는 먼저 세 포트를 서로 떼어 놓습니다. 다음 네 개의 명령으로 충분합니다.
sar -n DEV 1 10
nstat -az | grep -E 'Udp(InDatagrams|InErrors|NoPorts|RcvbufErrors)'
tcpdump -ni eth0 -c 200 -q 'udp port 22126'
ss -tn state established '( dport = :22005 or sport = :22005 )' | wc -l
해석은 보기보다 쉽습니다. CPU 부하가 낮은데 버퍼 오류가 올라간다면 프로세스가 처리할 수 있는 것보다 많은 트래픽이 닿고 있습니다. 트래픽은 평범해 보이는데 코어 하나가 한계까지 올라간다면 문제는 네트워크가 아니라 게임모드에 있습니다. 22126의 캡처에 페이로드가 1 바이트뿐인 패킷이 많이 보이면 ASE 플러드입니다. 22005의 열린 연결 수가 계속 네 자리에 머물러 있으면 HTTP 서버가 맞고 있습니다. tcpdump에는 항상 원칙이 하나 있습니다. -c로 개수를 제한하세요. 과부하 상태에서의 캡처는 이미 과부하인 서버에 부담을 더 얹습니다. 측정값을 하나하나 어떻게 해석하는지는 서버에서 DDoS 공격 알아내기 글에서 설명합니다.
서버 로그 자체는 logs/server.log에, 스크립트 로그는 logs/scripts.log에 있습니다. 두 경로 모두 mtaserver.conf에 적혀 있고 옮길 수 있습니다.
이 조치들이 한계에 이르는 지점
이제 어떤 설정 파일로도 해결할 수 없는 부분입니다. 지금까지의 모든 조치는 여러분의 서버, 즉 회선의 끝에서 동작합니다. 방화벽 규칙은 이미 케이블을 지나온 패킷을 두고 판단합니다. 그 패킷을 폐기할 수는 있지만, 보내지 않은 것으로 만들 수는 없습니다.
한번 계산해 보겠습니다. 일반적인 게임 서버는 1 Gbit/s 회선에 물려 있고, 이는 초당 125 메가바이트이며, 64 바이트짜리 패킷이라면 초당 약 149만 개입니다. 이 규모의 게임 서버 프로젝트를 향한 공격은 보통 5에서 50 Gbit/s 사이, 곧 여러분 회선의 5배에서 50배입니다. 그 뒤에 있는 여러분의 nftables 규칙이 훌륭한지는 그때 더 이상 상관이 없습니다. 플레이어의 패킷이 그 앞에서 이미 통과하지 못하기 때문입니다.
이때 패킷 전송률이 대역폭보다 먼저 터지는 일이 잦습니다. 일반적인 서버 커널은 CPU와 네트워크 카드에 따라 초당 수십만 개의 패킷을 처리한 뒤 폐기를 시작합니다. 그러므로 여러분의 회선을 3분의 1도 채우지 못하는 공격이 서버를 멈춰 세울 수 있습니다. 폐기하는 데에 연산 시간이 다 들어가기 때문입니다. 운영자는 이것을 “사용률은 전혀 높지 않았는데 그래도 전부 죽었다”로 경험합니다.
MTA:SA에는 세 번째 한계가 더해지고, 그것이 가장 먼저 걸립니다. 서버는 네트워크 포트를 하나의 작업 흐름에서 읽습니다. 22126의 쿼리 플러드가 이 흐름을 붙잡아 두면, 회선이 꽉 차기 훨씬 전에 실제 플레이어의 동기화 패킷이 수신 버퍼에서 버려집니다. 이때 프로세스가 죽는 것은 아니고 느려지기만 하며, 플레이어는 고무줄 현상을 봅니다. HTTP 서버도 마찬가지입니다. 게임 운영과 연산 시간을 나눠 씁니다.
어느 정도 규모가 실제로 나타나는지 가늠하기 위해 덧붙입니다. KernelHost 서버에서는 그중에서도 음성 서버를 향한 초당 4,150만 패킷과 함께 473.4 Gbit/s를 넘는 공격, 그리고 게임 서버를 향한 초당 870만 패킷과 함께 112.2 Gbit/s를 넘는 UDP 플러드가 걸러졌습니다. 첫 번째 경우는 1 Gbit/s 회선이 애초에 받아들일 수 있는 대역폭의 약 473배, 패킷 전송률의 약 28배입니다. 여기에는 로컬 설정이 없습니다. 볼류메트릭 공격은 서버 앞단 네트워크에서 끝나야 합니다.
KernelHost가 이에 맞서 제공하는 것
모든 서버에 포함된 상시 방어
KernelHost의 모든 서버는 상시 동작하는 2단계 필터링 뒤에 놓여 있습니다.
- 1단계: 글로벌 스크러빙 네트워크의 17 Tbps 방어 용량. 볼류메트릭 공격은 발생지에 가까운 곳에서 정화되어 프랑크푸르트암마인의 데이터센터에 닿기도 전에 걸러집니다.
- 2단계: 프랑크푸르트암마인 현지의 3.2 Tbps Arbor 실시간 필터링. 서버 바로 앞에서 프로토콜별 패턴을 찾아내 패킷 하나하나를 폐기합니다.
세 가지 특성이 결정적입니다. 이 방어는 상시 동작하므로 여러분의 서버가 오프라인이 되는 탐지 구간이 없습니다. null-routing을 쓰지 않습니다. 공격받는 주소는 네트워크에 그대로 남고, 폐기되는 것은 악성 패킷뿐이며, 실제 플레이어의 연결은 계속 이어집니다. 그리고 추가 비용이 들지 않고, 서버 제공 시점부터 KVM 루트 서버에서 게임 서버, Dedicated Server까지 모든 서버 상품에 포함되어 있습니다. 필터링은 Layer 3, 4, 7에서 모든 TCP 또는 UDP 포트에 적용되므로 22003 UDP, 22005 TCP, 22126 UDP에 동시에 적용됩니다. 운영 장소는 독일 프랑크푸르트암마인의 maincubes 데이터센터입니다. 어떤 게임과 프로토콜이 자체 프로필을 갖고 있는지는 실시간 게임 서버 DDoS 방어 글에 정리되어 있습니다.
계속 공격받는 프로젝트를 위한 Advanced DDoS Protection
어떤 프로젝트는 어쩌다 한두 번 맞는 것이 아니라, 몇 주에 걸쳐 표적이 되어 공격받습니다. 이런 경우를 위해 Advanced DDoS Protection이 월 50.00 EUR부터, PrePaid 방식으로, 최소 이용 기간 없이 제공됩니다. 차이는 용량이 더 커지는 데 있지 않고 통제권에 있습니다.
- 프랑크푸르트 코어에서 발급되는 전용 방어 IP. 서버는 KernelHost 네트워크 안에서 이 IP로 전환되고, 여러분 쪽에서 손볼 것은 없습니다.
- 포트와 프로토콜별로 직접 관리하는 방어 규칙을 고객 포털에서 다룹니다. MTA:SA에서는 바로 이것이 핵심입니다. 성격이 매우 다른 세 서비스를 한 묶음으로 다루는 대신, 22003 UDP, 22005 TCP, 22126 UDP에 각각 다른 규칙을 설정합니다.
- 변경은 실시간으로 반영되며, 티켓도 대기 시간도 필요하지 않습니다. 그래서 공격이 진행되는 중에도 값을 조정할 수 있습니다.
- 게임에 맞춘 방어 프로필. Multi Theft Auto는 자체 프로필로 준비되어 있고, 웹 서버와 음성 서버, 같은 방어 주소 뒤에 들어가는 자체 TCP 또는 UDP 애플리케이션도 마찬가지입니다.
두 단계 비교
| 항목 | 포함된 상시 방어 | Advanced DDoS Protection |
|---|---|---|
| 요금 | 모든 서버 상품에 추가 요금 없이 포함 | 월 50.00 EUR부터, PrePaid |
| 활성화 | 서버 제공 시점부터 동작, 설정할 것 없음 | 주문, 방어 IP 수령, 서버 전환 |
| 필터링 용량 | 17 Tbps 글로벌 스크러빙과 프랑크푸르트암마인의 3.2 Tbps Arbor 실시간 필터링 | 동일한 인프라에 자체 규칙이 더해짐 |
| 주소 | 상품에 딸린 서버 IP | 추가로 받는 전용 방어 IP |
| 규칙 관리 | 미리 설정되어 자동으로 동작 | 고객 포털에서 직접 관리, 포트와 프로토콜별로 분리 |
| 방어 프로필 | 자동 패턴 인식 | 게임별 프로필 선택 가능, Multi Theft Auto 포함 |
| null-routing | 없음 | 없음 |
| 어울리는 경우 | 일반적인 경우, 어쩌다 공격받는 경우까지 | 계속해서 표적이 되어 공격받는 프로젝트 |
| 이용 기간 | 서버 상품에 연동 | PrePaid, 최소 이용 기간 없음, 해지 통보 기간 없음, 설치비 없음 |
대부분의 MTA:SA 프로젝트에는 깔끔한 서버 설정과 포함된 상시 방어면 충분합니다. Advanced DDoS Protection은 누군가 이 일을 개인적으로 받아들였을 때의 답입니다.
자주 나오는 실수와 해결 방법
“22126을 막았는데도 서버가 어떤 목록에도 없고, 그런데 조회는 계속 들어옵니다”: 그렇다면 소켓이 아직 열려 있습니다. <donotbroadcastlan>0</donotbroadcastlan>이 설정되어 있는 한 <ase>0</ase>만으로는 포트가 닫히지 않습니다. ss -lnup | grep 22126으로 정말 아무것도 대기하고 있지 않은지 확인하세요.
“플레이어가 로딩 화면에 매달려 있는데 게임 자체는 정상으로 돌아갑니다”: 이것은 22003을 향한 공격이 아니라 22005의 HTTP 서버가 한계에 이른 상태입니다. httpdownloadurl로 다운로드를 외부로 옮기고 httpmaxconnectionsperclient와 httpthreadcount를 확인하세요.
“서버가 브라우저에서 사라졌는데, 접속해 있는 플레이어는 아무것도 느끼지 못합니다”: 그렇다면 22126만 맞고 있습니다. 접속해 있는 플레이어에게는 아무 결과가 없지만, 새 플레이어의 유입에는 그렇지 않습니다. 이 포트 하나에 대한 속도 제한이 올바른 답이며, 게임 포트에 대한 제한이 아닙니다.
“IP 주소를 바꿨는데 두 시간 뒤에 다시 오프라인이 되었습니다”: 공격자는 새 주소를 옛 주소와 같은 곳에서 얻었고, 대개 목록 등록이나 상태 조회를 하는 Discord 봇, 또는 오래된 DNS 레코드입니다. 주소 변경은 시간을 벌어 주지만 해결책은 아닙니다.
“22003에 주소별로 초당 20 패킷의 속도 제한을 걸었습니다”: 그것은 너무 좁습니다. 동기화가 활발할 때는 플레이어 한 명만으로도 그 값을 넘고, 같은 NAT 주소 뒤에 있는 여러 플레이어는 같은 몫을 나눠 씁니다. 그러면 자기 플레이어를 내쫓게 됩니다. 반대로 22126에서는 좁은 값이 문제가 되지 않습니다.
“방화벽으로 스스로 접속이 막혔습니다”: 다시 시작해도 도움이 되지 않습니다. UFW가 부팅 때 규칙을 복원하기 때문입니다. KernelHost에서는 고객 포털의 VNC 콘솔을 열고 거기서 ufw disable을 실행하세요. VNC 콘솔은 게스트 시스템의 네트워크와 무관하게 동작합니다.
“기존 업체가 제 IP 주소를 차단했습니다”: 그것이 null-routing입니다. 업체는 그렇게 자기 네트워크를 지키지만, 여러분에게 남는 결과는 공격이 성공한 것과 똑같고, 보통 그 뒤로 몇 시간 더 이어집니다. 확실하지 않으면 필터링을 하는지 null-routing을 하는지 물어보세요. 그 답이 어떤 하드웨어 사양보다 여러분의 가용성을 더 크게 좌우합니다.
“공격이 지나가기를 그냥 기다립니다”: 효과를 낸 공격은 반복됩니다. 시각을 시간대와 함께, 지속 시간, 최고값, 그리고 영향을 받은 포트를 기록해 두세요. 필터링을 정확히 다시 맞추려면 지원 티켓에도 바로 이 정보가 필요합니다.
핵심 요약
- MTA:SA 서버에는 정확히 포트 세 개가 필요합니다. 게임용 22003 UDP, 내부 HTTP 서버용 22005 TCP, ASE 쿼리용 22126 UDP입니다. 세 번째는 게임 포트에 123을 더한 값으로 고정됩니다.
- ASE 쿼리는 별도의 포트에 놓여 있어서, 플레이어를 단 한 명도 막지 않고 제한할 수 있습니다. 게임과 쿼리가 같은 포트를 나눠 쓰는 SA-MP와의 가장 중요한 차이입니다.
- 22126에 요청 1 바이트를 보내면 최대 수 킬로바이트의 응답이 생기고, 출발지 주소는 위조할 수 있습니다. 제한하지 않은 ASE 포트는 그래서 표적이면서 동시에 증폭기입니다.
- MTA의 내장 제동 장치는 출발지 주소별로 작동합니다. 6초에 조회 다섯 번, 30초에 접속 네 번입니다. 출발지 주소가 동시에 100개를 넘으면 조회 집계가 건너뛰어지므로 분산 플러드는 그대로 통과합니다.
- 22005의 내부 HTTP 서버는 독자적인 공격 표면입니다.
httpdownloadurl로 다운로드를 외부 웹 서버로 옮기면 그것을 게임 운영에서 빼내게 됩니다. - 서버에서 돌아가는 모든 것은 작은 공격까지만 결정합니다. 1 Gbit/s에서는 초당 약 149만 패킷에서 끝이며, 규칙의 품질과는 무관합니다.
- KernelHost의 2단계 상시 방어는 모든 서버 상품에 추가 요금 없이 포함되어 있고 null-routing을 쓰지 않습니다. 포트별 규칙을 직접 다루려는 운영자는 월 50.00 EUR부터의 Advanced DDoS Protection을 더합니다.
프로젝트가 이미 KernelHost에 있다면 필터링은 상시 동작하고 있으므로 따로 켜실 것이 없습니다. 그래도 이상한 점이 보이면 기간, 포트, 관찰한 동작을 담아 지원 티켓을 열어 주세요. 해당 주소의 규칙을 다시 맞춰 드립니다. 공격이 진행 중일 때는 WhatsApp 긴급 채팅 +43 650 8209883으로도 연락하실 수 있습니다. 아직 다른 곳에서 호스팅하면서 자주 공격받는다면 프랑크푸르트암마인으로 옮기는 것이 더 짧은 해결책입니다. 급한 상황에서의 추가 단계는 심각한 DDoS 공격, 어떻게 대응하나요? 글에 있습니다.
자주 묻는 질문
MTA:SA 서버에 실제로 필요한 포트는 무엇인가요?
MTA:SA에서 ASE 포트 22126이 독자적인 위험인 이유는 무엇인가요?
플레이어를 막지 않고 쿼리 포트를 제한할 수 있나요?
포트를 닫으려면 ase를 0으로 두는 것으로 충분한가요?
서버가 지금 이상합니다. 세 서비스 가운데 어느 쪽이 맞고 있나요?
서버는 돌아가는데 플레이어가 로딩 화면에 매달리는 이유는 무엇인가요?
MTA의 내장 조회 제동 장치가 저를 지켜 주나요?
서버의 방화벽만으로 DDoS 공격을 막을 수 있나요?
KernelHost의 서버는 공격 중에 오프라인이 되나요?
KernelHost의 DDoS 방어는 추가 요금이 드나요?
Advanced DDoS Protection이 추가로 필요한 때는 언제인가요?
2026 KernelHost GmbH. 모든 권리를 보유합니다. 본 가이드는 저작권법의 보호를 받습니다. 전체든 일부든, 또는 수정된 형태이든 저희의 서면 동의 없이 다른 웹사이트에 게시할 수 없습니다. 출처를 밝히고 링크를 덧붙인 인용은 언제든지 환영합니다.

