Filtro de pacotes, monitor de tráfego e alerta de ataque para servidores Tibia / Open Tibia. Auto-instalável em Ubuntu/Debian.
OTGuard NÃO é um anti-DDoS de verdade. Anti-DDoS sério mora em scrubbing centers com terabits de capacidade (Cloudflare Magic Transit, OVH VAC, NEEP/ShieldM, Hetzner DDoS Protection). Você não consegue rodar anti-DDoS dentro de uma VPS de 1 Gbps — quando o ataque é maior que a sua banda, o link satura na borda do datacenter e o OTGuard nem chega a ver o pacote.
O OTGuard faz o outro pedaço do trabalho — aquele que o scrubbing upstream não cobre ou que ele te delega:
| ✅ O OTGuard FAZ | ❌ O OTGuard NÃO faz |
|---|---|
| Descarta tráfego inútil (UDP nas portas TCP-only, pacotes malformados, SYN-flood dentro da capacidade do link) | Defender contra ataque volumétrico (≥ sua banda) |
| Limita conexões por IP de origem e por porta (hashlimit) | Substituir Cloudflare / OVH VAC / NEEP / Hetzner |
Detecta ataque em andamento (pps, conntrack, SYN-RECV) e captura .pcap + relatório técnico |
Mágica genérica contra qualquer DDoS |
| Alerta no Discord com mensagem pronta pra colar no chamado do provedor | Operar a partir da nuvem (é tudo local na VPS) |
| Painel ao vivo no terminal mostrando o que está acontecendo agora | |
Blocklist persistente, sobrevive reboot (otguard ban <ip>) |
|
| Esconder o IP real do servidor (libera 80/443 só pra Cloudflare) |
Se sua VPS tem 1 Gbps e o ataque é de 5 Gbps, o tráfego nem chega na VPS — ele já saturou o uplink antes do seu firewall. Pra isso você precisa de scrubbing upstream do seu IP.
Onde o OTGuard ajuda de verdade:
- Ataques dentro da sua banda (script-kiddies de 50–500 Mbps que uma VPS aguenta se o tráfego for filtrado cedo)
- Floods de SYN/UDP nas portas do jogo (lixo que infla conntrack e derruba CPU mesmo sem encher banda)
- Te dar visibilidade ("você ESTÁ sendo atacado, é de SYN-flood, vem desses 12 IPs, aqui está o pcap")
- Te dar a mensagem pronta pra abrir chamado no suporte do provedor (NEEP, OVH, Hetzner, etc.)
- Te dar uma blocklist que sobrevive reboot pra bloquear bots persistentes
- Te poupar CPU/RAM filtrando o lixo o mais cedo possível (
iptables raw + ipset) — seu jogo continua jogável durante ataques pequenos
Em uma frase: OTGuard faz uns 50% do trabalho de mitigação — o pedaço que cabe dentro da sua VPS. O resto (volumétrico, terabits) tem que vir do upstream.
Na VPS que roda o servidor de OT (Ubuntu 20.04+ / Debian 11+, com apt):
curl -fsSL https://github.com/FeTads/otguard/releases/latest/download/install.sh | sudo shQuando terminar:
sudo otguardO assistente abre uma tela azul tipo instalador do Ubuntu — 8 perguntas rápidas com sugestões prontas em cada tela. Depois disso o OTGuard inicializa sozinho a cada boot.
A URL acima aponta para o install.sh que vai junto com a última release. Esse install.sh é gerado pelo GitHub Actions e traz duas camadas de verificação:
- Assinatura GPG (
.deb.sig) — chave pública fingerprintC357 5800 8A4D EB52 EC99 6C78 5A7E 6EAD E40B B4A0(gravada dentro do próprioinstall.sh). Mesmo se a conta GitHub for comprometida, sem a chave privada o atacante não consegue assinar um.debfalso — oinstall.shaborta. - SHA256 do
.deb— gravado dentro do próprioinstall.sh. Defesa redundante caso a primeira camada não esteja disponível (releases antigas, p.ex.).
Você pode verificar manualmente:
gpg --import otguard-public.gpg
gpg --verify otguard_<ver>_all.deb.sig otguard_<ver>_all.deb
sha256sum -c otguard_<ver>_all.deb.sha256A chave pública mora em otguard-public.gpg na raiz do repo.
Se você quiser instalar antes de qualquer release existir, dá pra usar:
curl -fsSL https://raw.githubusercontent.com/FeTads/otguard/main/install.sh | sudo shEsse modo cai num caminho "bootstrap": consulta a API do GitHub pela última release, baixa o .deb.sha256 separadamente e confere. Requer jq (instalado automaticamente). É o fallback.
| Comando | Faz |
|---|---|
sudo otguard |
menu + status atual |
sudo otguard mon |
painel ao vivo (pps, conntrack, SYN/s, half-open) |
sudo otguard status |
resumo dos serviços + contadores |
sudo otguard ban <ip> |
bloqueia IP nas portas do jogo (permanente, sobrevive reboot) |
sudo otguard unban <ip> |
libera IP |
sudo otguard banlist |
lista os IPs bloqueados |
sudo otguard test |
envia mensagem de teste ao Discord |
sudo otguard reconfig |
refaz o wizard |
sudo otguard uninstall |
remove tudo |
sudo otguard help |
ajuda completa |
Tudo via systemd, levantado no boot:
otguard-mitigacao.service— escreve as regrasiptables raw+ipset+ RPS na inicializaçãootguard-watch.service— vigia pps/conntrack/SYN. Quando dispara, captura.pcap(até 100k pacotes / 120s) + relatório + alerta no Discordotguard-live.service— coleta 1 amostra/s pro painel ao vivootguard-cfupdate.timer— semanal, atualiza ranges da Cloudflare (só se filtro 80/443 ligado)
Logs em /var/log/otguard/. Config em /etc/otguard/otguard.conf. Blocklist persistente em /etc/otguard/blocklist.ipset.
- Ubuntu 20.04+ ou Debian 11+ (qualquer distro com
apt+systemd) - root / sudo
- Interface de rede normal (eth0 / ens3 / etc.)
Dependências (instaladas automaticamente pelo .deb):
iptables, ipset, tcpdump, curl, gawk, whiptail, systemd
O script é auto-empacotável — não tem árvore Debian, control files, nada. Basta:
sh otguard.sh --build-deb 1.1 # gera otguard_1.1_all.deb na pasta atualO .github/workflows/release.yml faz isso sozinho quando você empurra uma tag v*:
git commit -am "release v1.1"
git tag v1.1
git push origin main v1.1
# o Actions builda o .deb e cria a Release automaticamenteA partir de 1.7 o OTGuard inclui um daemon otguard-shadow que detecta padrões de attack via login de personagem. Ele depende de UM hook Lua no seu TFS (não vem instalado por default porque o caminho depende do seu server).
Hook do TFS — adicione ao final do seu onLogin(cid) em data/creaturescripts/scripts/login.lua (antes do return true):
-- otguard-shadow: log persistente de cada login autenticado.
-- Tab-separated. Campos: ts ip nome accid level voc os
pcall(function()
local f = io.open("/var/log/otguard/auth.log", "a")
if f then
f:write(string.format("%d\t%s\t%s\t%d\t%d\t%d\t%d\n",
os.time(),
doConvertIntegerToIp(getPlayerIp(cid)),
getPlayerName(cid),
getPlayerAccountId(cid),
getPlayerLevel(cid),
getPlayerVocation(cid),
getPlayerOperatingSystem(cid)))
f:close()
end
end)Depois faça /reload creaturescripts no chat do jogo (god/admin). Sem o hook, o auth.log fica vazio e o daemon shadow não tem como detectar nada.
| componente | o que faz |
|---|---|
| hook login.lua | a cada login real escreve timestamp IP char accid level voc os em /var/log/otguard/auth.log |
| otguard-shadow (daemon) | a cada 60s analisa últimos 1h e bana automaticamente: char com 5+ IPs distintos (lvl<50) ou IP com 5+ chars lvl≤20 (com safety: skip se IP tem main lvl≥50 na janela, ou se algum account tem main lvl≥100 no DB) |
| otguard-auth-check | script CLI: otguard-auth-check 3600 mostra cobertura auth/conn + distribuição de level + padrões A/B |
otguard mon [5] |
painel inline do auth-check (janela 2h) |
otguard mon [6] |
sweep manual 24h: lista candidatos, pede confirmação, bana em massa + alerta Discord agregado |
Tunable via /etc/otguard/otguard.conf (chaves SHADOW_*). Os defaults são conservadores (5 IPs/char, 5 chars/IP, ban 24h).
A partir de 1.7 o OTGuard inclui otguard-unban — daemon que processa pedidos de desban feitos pelo jogador no site (via accountmanagement.php). A ideia:
- Jogador legit toma ban injusto durante ataque → loga no site (auth da conta dele) → vê os IPs dele → clica "Solicitar desban" → daemon processa em até 30s
- Atacante NÃO escala: pra desbanir N IPs do botnet, precisaria logar uma conta válida pra cada IP, abrir o accountmanagement, pedir o desban — friction enorme
A tabela otguard_unban_requests é criada automaticamente no apply se o TFS for detectado em /home/otserv/*/config.lua (ou aponte com OTG_TFS_CONFIG no conf).
Adicione ao seu pages/accountmanagement.php, logo antes do bloco //########### CHANGE PASSWORD ##########:
//########### OTGUARD: SELF-SERVICE UNBAN ##########
if($action == "unbanip")
{
$accId = (int)$account_logged->getId();
$accName = $account_logged->getName();
$remoteIp = $_SERVER['REMOTE_ADDR'] ?? '';
$db = Website::getDBHandle();
// Coleta IPs historicos da conta (lastip de cada char + IP atual da sessao)
$ips = [];
if(filter_var($remoteIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$ips[$remoteIp] = 'IP atual (sessão web)';
}
try {
$r = $db->query("SELECT name, INET_NTOA(lastip) AS ip FROM players WHERE account_id = $accId AND lastip > 0");
while($row = $r->fetch()) {
if($row['ip'] && !isset($ips[$row['ip']])) {
$ips[$row['ip']] = 'último login de ' . htmlspecialchars($row['name']);
}
}
} catch(Exception $e) {}
// Le blocklist do otguard
$bl = [];
if(is_readable('/etc/otguard/blocklist.ipset')) {
foreach(file('/etc/otguard/blocklist.ipset') as $line) {
if(preg_match('/^add\s+otguard_bl\s+(\S+)/', $line, $m)) $bl[$m[1]] = true;
}
}
// POST: solicita desban
$msg = '';
if(isset($_POST['unban_ip'])) {
$reqIp = trim($_POST['unban_ip']);
if(!filter_var($reqIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) $msg = '<font color=red>IP inválido.</font>';
elseif(!isset($ips[$reqIp])) $msg = '<font color=red>IP não pertence à sua conta.</font>';
elseif(!isset($bl[$reqIp])) $msg = '<font color=orange>IP não está banido.</font>';
else {
$now = time();
$rl = $db->query("SELECT COUNT(*) AS n FROM otguard_unban_requests WHERE account_id=$accId AND requested_at > ".($now-3600))->fetch();
if(($rl['n'] ?? 0) >= 1) $msg = '<font color=red>Você já fez um pedido na última hora.</font>';
else {
$db->query("INSERT INTO otguard_unban_requests (account_id, account_name, ip, remote_ip, requested_at, status) VALUES ($accId, ".$db->quote($accName).", ".$db->quote($reqIp).", ".$db->quote($remoteIp).", $now, 'pending')");
$msg = '<font color=green>Pedido enviado. Processado em até 30s.</font>';
}
}
}
// Renderiza tabela com IPs + botão
$rows = '';
foreach($ips as $ip => $origem) {
$ipEsc = htmlspecialchars($ip);
if(isset($bl[$ip])) {
$rows .= '<tr><td>'.$ipEsc.'</td><td>'.htmlspecialchars($origem).'</td><td><font color=red><b>BANIDO</b></font></td><td><form method=post action="?subtopic=accountmanagement&action=unbanip" style=margin:0><input type=hidden name=unban_ip value="'.$ipEsc.'"><input type=submit value="Solicitar desban" onclick="return confirm(\'Confirma desban?\')"></form></td></tr>';
} else {
$rows .= '<tr><td>'.$ipEsc.'</td><td>'.htmlspecialchars($origem).'</td><td><font color=green>OK</font></td><td>—</td></tr>';
}
}
$main_content .= '<div class="TableContainer"><table class="Table1"><tr><td><div class="InnerTableContainer">'
.($msg ? '<p style=text-align:center>'.$msg.'</p>' : '')
.'<p>Limite: 1 pedido por hora, 5 por dia.</p>'
.'<table style=width:100% border=1 cellpadding=4><tr><th>IP</th><th>Origem</th><th>Status</th><th>Ação</th></tr>'.$rows.'</table>'
.'</div></td></tr></table></div>';
}E opcionalmente adicione um link na action "manage" (logo após o fechamento </tr><br/>';):
$main_content .= '<p style="text-align:center;padding:10px"><a href="?subtopic=accountmanagement&action=unbanip" style="color:#cc0000;font-weight:bold">⚠️ Gerenciar IPs banidos (OTGuard)</a></p>';UNBAN_INTERVAL=30 # poll a cada 30s
UNBAN_MAX_PER_HOUR=1 # max desbans por account por hora
UNBAN_MAX_PER_DAY=5 # max desbans por account por dia
| ação | quem decide |
|---|---|
| ban automático | otguard (slowread/shadow): thresholds em tempo real |
| ban manual | admin via otguard ban <ip> ou [6] sweep |
| unban automático | jogador no site → daemon valida + executa |
| unban manual | admin via otguard unban <ip> ou [u] no mon |
otguard/
├── otguard.sh # o instalador auto-extraivel (~33 KB)
├── install.sh # one-liner que baixa o .deb da release
├── README.md # este arquivo
├── LICENSE # MIT
└── .github/workflows/release.yml # CI: builda .deb em cada tag v*
MIT. Veja LICENSE.
Disclaimer honesto: se você está sob ataque volumétrico ativo (>= a sua banda), nenhuma ferramenta local te salva. Abra chamado no seu provedor agora, peça scrubbing L4 always-on, e considere migrar pra um host com proteção upstream incluída. O OTGuard te ajuda a provar o ataque (com pcap + relatório) e a lidar com o que sobra quando o scrubbing já está mitigando o resto. Não é magia.