Redo ip bans - reduce amount of db queries on login

Works by loading all ip bans on startup and querying the collection in memory
rather than making calls on every login.
This commit is contained in:
P0nk
2024-09-30 07:18:01 +02:00
parent 167937bb88
commit 7661cd0f75
15 changed files with 228 additions and 36 deletions

View File

@@ -315,25 +315,6 @@ public class Client extends ChannelInboundHandlerAdapter {
return inServerTransition;
}
// TODO: load ipbans on server start and query it on demand. This query should not be run on every login!
@Deprecated
public boolean hasBannedIP() {
boolean ret = false;
try (Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT COUNT(*) FROM ipbans WHERE ? LIKE CONCAT(ip, '%')")) {
ps.setString(1, remoteAddress);
try (ResultSet rs = ps.executeQuery()) {
rs.next();
if (rs.getInt(1) > 0) {
ret = true;
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return ret;
}
// TODO: load hwidbans on server start and query it on demand. This query should not be run on every login!
@Deprecated
public boolean hasBannedHWID() {

View File

@@ -1,6 +1,7 @@
package database;
import database.account.AccountRowMapper;
import database.ban.IpBanRowMapper;
import database.drop.GlobalMonsterDropRowMapper;
import database.drop.MonsterDropRowMapper;
import database.maker.MakerIngredientRowMapper;
@@ -36,7 +37,8 @@ public final class JdbiConfig {
new GlobalMonsterDropRowMapper(),
new ShopRowMapper(),
new ShopItemRowMapper(),
new MonsterCardRowMapper()
new MonsterCardRowMapper(),
new IpBanRowMapper()
);
}
}

View File

@@ -0,0 +1,12 @@
package database.ban;
import lombok.Builder;
import java.util.Objects;
@Builder
public record IpBan(String ip, Integer accountId) {
public IpBan {
Objects.requireNonNull(ip);
}
}

View File

@@ -0,0 +1,45 @@
package database.ban;
import database.PgDatabaseConnection;
import lombok.extern.slf4j.Slf4j;
import org.jdbi.v3.core.Handle;
import java.util.List;
/**
* @author Ponk
*/
@Slf4j
public class IpBanRepository {
private final PgDatabaseConnection connection;
public IpBanRepository(PgDatabaseConnection connection) {
this.connection = connection;
}
public List<IpBan> getAllIpBans() {
String sql = """
SELECT ip, account_id
FROM ip_ban""";
try (Handle handle = connection.getHandle()) {
return handle.createQuery(sql)
.mapTo(IpBan.class)
.list();
}
}
public boolean saveIpBan(int accountId, String ip) {
String sql = """
INSERT INTO ip_ban (account_id, ip)
VALUES (:accountId, :ip)""";
try (Handle handle = connection.getHandle()) {
return handle.createUpdate(sql)
.bind("accountId", accountId)
.bind("ip", ip)
.execute() > 0;
} catch (Exception e) {
log.error("Failed to save ip ban. The ip is already banned? accountId: {}, ip: {}", accountId, ip, e);
return false;
}
}
}

View File

@@ -0,0 +1,18 @@
package database.ban;
import org.jdbi.v3.core.mapper.RowMapper;
import org.jdbi.v3.core.statement.StatementContext;
import java.sql.ResultSet;
import java.sql.SQLException;
public class IpBanRowMapper implements RowMapper<IpBan> {
@Override
public IpBan map(ResultSet rs, StatementContext ctx) throws SQLException {
return IpBan.builder()
.ip(rs.getString("ip"))
.accountId(rs.getObject("account_id", Integer.class))
.build();
}
}

View File

@@ -8,6 +8,7 @@ import database.character.CharacterLoader;
import database.character.CharacterSaver;
import database.drop.DropProvider;
import lombok.Builder;
import server.ban.IpBanManager;
import server.shop.ShopFactory;
import service.AccountService;
import service.BanService;
@@ -25,7 +26,7 @@ public record ChannelDependencies(
CharacterCreator characterCreator, CharacterLoader characterLoader, CharacterSaver characterSaver,
NoteService noteService, FredrickProcessor fredrickProcessor, MakerProcessor makerProcessor,
DropProvider dropProvider, CommandsExecutor commandsExecutor, ShopFactory shopFactory,
TransitionService transitionService, BanService banService
TransitionService transitionService, IpBanManager ipBanManager, BanService banService
) {
public ChannelDependencies {
@@ -40,6 +41,7 @@ public record ChannelDependencies(
Objects.requireNonNull(commandsExecutor);
Objects.requireNonNull(shopFactory);
Objects.requireNonNull(transitionService);
Objects.requireNonNull(ipBanManager);
Objects.requireNonNull(banService);
}
}

View File

@@ -282,7 +282,7 @@ public final class PacketProcessor {
registerHandler(RecvOpcode.CHARLIST_REQUEST, new CharlistRequestHandler());
registerHandler(RecvOpcode.CHAR_SELECT, new CharSelectedHandler(channelDeps.transitionService()));
registerHandler(RecvOpcode.LOGIN_PASSWORD, new LoginPasswordHandler(channelDeps.accountService(),
channelDeps.transitionService()));
channelDeps.transitionService(), channelDeps.banService()));
registerHandler(RecvOpcode.RELOG, new RelogRequestHandler());
registerHandler(RecvOpcode.SERVERLIST_REQUEST, new ServerlistRequestHandler());
registerHandler(RecvOpcode.SERVERSTATUS_REQUEST, new ServerStatusRequestHandler());

View File

@@ -45,6 +45,7 @@ import constants.net.ServerConstants;
import database.PgDatabaseConfig;
import database.PgDatabaseConnection;
import database.account.AccountRepository;
import database.ban.IpBanRepository;
import database.character.CharacterLoader;
import database.character.CharacterRepository;
import database.character.CharacterSaver;
@@ -85,6 +86,7 @@ import server.CashShop.CashItemFactory;
import server.SkillbookInformationProvider;
import server.ThreadManager;
import server.TimerManager;
import server.ban.IpBanManager;
import server.expeditions.ExpeditionBossLog;
import server.life.PlayerNPC;
import server.quest.Quest;
@@ -716,6 +718,7 @@ public class Server {
futures.add(initExecutor.submit(CashItemFactory::loadAllCashItems));
futures.add(initExecutor.submit(Quest::loadAllQuests));
futures.add(initExecutor.submit(SkillbookInformationProvider::loadAllSkillbookInformation));
futures.add(initExecutor.submit(channelDependencies.ipBanManager()::loadIpBans));
initExecutor.shutdown();
TimeZone.setDefault(TimeZone.getTimeZone(YamlConfig.config.server.TIMEZONE));
@@ -829,7 +832,8 @@ public class Server {
NoteService noteService = new NoteService(new NoteDao(connection));
DropProvider dropProvider = new DropProvider(new DropRepository(connection));
ShopFactory shopFactory = new ShopFactory(new ShopDao(connection));
BanService banService = new BanService(accountService, transitionService);
IpBanManager ipBanManager = new IpBanManager(new IpBanRepository(connection));
BanService banService = new BanService(accountService, transitionService, ipBanManager);
ChannelDependencies channelDependencies = ChannelDependencies.builder()
.accountService(accountService)
.characterCreator(new CharacterCreator(connection, characterRepository))
@@ -843,6 +847,7 @@ public class Server {
characterSaver, transitionService, banService)))
.shopFactory(shopFactory)
.transitionService(transitionService)
.ipBanManager(ipBanManager)
.banService(banService)
.build();

View File

@@ -36,6 +36,7 @@ import net.server.world.World;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import service.AccountService;
import service.BanService;
import service.TransitionService;
import tools.BCrypt;
import tools.HexTool;
@@ -49,10 +50,13 @@ public final class LoginPasswordHandler implements PacketHandler {
private final AccountService accountService;
private final TransitionService transitionService;
private final BanService banService;
public LoginPasswordHandler(AccountService accountService, TransitionService transitionService) {
public LoginPasswordHandler(AccountService accountService, TransitionService transitionService,
BanService banService) {
this.accountService = accountService;
this.transitionService = transitionService;
this.banService = banService;
}
@Override
@@ -110,7 +114,7 @@ public final class LoginPasswordHandler implements PacketHandler {
}
boolean banCheckDisabled = false;
if (!banCheckDisabled && (c.hasBannedIP() || c.hasBannedMac() || c.hasBannedHWID())) {
if (!banCheckDisabled && (banService.isBanned(c) || c.hasBannedMac() || c.hasBannedHWID())) {
c.sendPacket(PacketCreator.getLoginFailed(3));
return;
}

View File

@@ -0,0 +1,45 @@
package server.ban;
import database.ban.IpBan;
import database.ban.IpBanRepository;
import lombok.extern.slf4j.Slf4j;
import net.jcip.annotations.ThreadSafe;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @author Ponk
*/
@ThreadSafe
@Slf4j
public class IpBanManager {
private final IpBanRepository ipBanRepository;
private final Set<String> bannedIps = new HashSet<>();
public IpBanManager(IpBanRepository ipBanRepository) {
this.ipBanRepository = ipBanRepository;
}
public synchronized void loadIpBans() {
List<IpBan> ipBans = ipBanRepository.getAllIpBans();
log.debug("Loaded {} ip bans", ipBans.size());
bannedIps.addAll(ipBans.stream().map(IpBan::ip).toList());
}
public synchronized boolean isBanned(String ip) {
return bannedIps.contains(ip);
}
public synchronized void banIp(String ip, int accountId) {
if (ip == null) {
throw new IllegalArgumentException("ip cannot be null");
}
// TODO: validate ip format. Or create "Ip" model class.
bannedIps.add(ip);
ipBanRepository.saveIpBan(accountId, ip);
}
}

View File

@@ -8,6 +8,7 @@ import lombok.extern.slf4j.Slf4j;
import net.packet.Packet;
import net.server.Server;
import server.TimerManager;
import server.ban.IpBanManager;
import tools.PacketCreator;
import java.time.Duration;
@@ -19,10 +20,12 @@ import java.util.concurrent.TimeUnit;
public class BanService {
private final AccountService accountService;
private final TransitionService transitionService;
private final IpBanManager ipBanManager;
public BanService(AccountService accountService, TransitionService transitionService) {
public BanService(AccountService accountService, TransitionService transitionService, IpBanManager ipBanManager) {
this.accountService = accountService;
this.transitionService = transitionService;
this.ipBanManager = ipBanManager;
}
public void autoban(Character chr, AutobanFactory type, String reason) {
@@ -111,4 +114,13 @@ public class BanService {
}
accountService.ban(accountId, bannedUntil, reason, description);
}
public boolean isBanned(Client c) {
return isIpBanned(c);
}
private boolean isIpBanned(Client c) {
String ip = c.getRemoteAddress();
return ip != null && ipBanManager.isBanned(ip);
}
}