Redo hwid bans - reduce amount of db queries on login

Works by loading all hwid bans on startup and querying the collection in memory
rather than making calls on every login.
This commit is contained in:
P0nk
2024-10-01 07:04:25 +02:00
parent 7661cd0f75
commit af02f8b744
20 changed files with 235 additions and 55 deletions

View File

@@ -315,32 +315,6 @@ public class Client extends ChannelInboundHandlerAdapter {
return inServerTransition;
}
// TODO: load hwidbans on server start and query it on demand. This query should not be run on every login!
@Deprecated
public boolean hasBannedHWID() {
if (hwid == null) {
return false;
}
boolean ret = false;
try (Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT COUNT(*) FROM hwidbans WHERE hwid LIKE ?")) {
ps.setString(1, hwid.hwid());
try (ResultSet rs = ps.executeQuery()) {
if (rs != null && rs.next()) {
if (rs.getInt(1) > 0) {
ret = true;
}
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return ret;
}
// TODO: load macbans on server start and query it on demand. This query should not be run on every login!
@Deprecated
public boolean hasBannedMac() {
@@ -490,8 +464,6 @@ public class Client extends ChannelInboundHandlerAdapter {
}
public void updateHwid(Hwid hwid) {
this.hwid = hwid;
try (Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("UPDATE accounts SET hwid = ? WHERE id = ?")) {
ps.setString(1, hwid.hwid());

View File

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

View File

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

View File

@@ -0,0 +1,25 @@
package database.ban;
import database.PgDatabaseConnection;
import org.jdbi.v3.core.Handle;
import java.util.List;
public class HwidBanRepository {
private final PgDatabaseConnection connection;
public HwidBanRepository(PgDatabaseConnection connection) {
this.connection = connection;
}
public List<HwidBan> getAllHwidBans() {
String sql = """
SELECT hwid, account_id
FROM hwid_ban""";
try (Handle handle = connection.getHandle()) {
return handle.createQuery(sql)
.mapTo(HwidBan.class)
.list();
}
}
}

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 HwidBanRowMapper implements RowMapper<HwidBan> {
@Override
public HwidBan map(ResultSet rs, StatementContext ctx) throws SQLException {
return HwidBan.builder()
.hwid(rs.getString("hwid"))
.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.HwidBanManager;
import server.ban.IpBanManager;
import server.shop.ShopFactory;
import service.AccountService;
@@ -26,7 +27,8 @@ public record ChannelDependencies(
CharacterCreator characterCreator, CharacterLoader characterLoader, CharacterSaver characterSaver,
NoteService noteService, FredrickProcessor fredrickProcessor, MakerProcessor makerProcessor,
DropProvider dropProvider, CommandsExecutor commandsExecutor, ShopFactory shopFactory,
TransitionService transitionService, IpBanManager ipBanManager, BanService banService
TransitionService transitionService, IpBanManager ipBanManager, HwidBanManager hwidBanManager,
BanService banService
) {
public ChannelDependencies {
@@ -42,6 +44,7 @@ public record ChannelDependencies(
Objects.requireNonNull(shopFactory);
Objects.requireNonNull(transitionService);
Objects.requireNonNull(ipBanManager);
Objects.requireNonNull(hwidBanManager);
Objects.requireNonNull(banService);
}
}

View File

@@ -280,7 +280,8 @@ public final class PacketProcessor {
registerHandler(RecvOpcode.AFTER_LOGIN, new AfterLoginHandler(channelDeps.accountService()));
registerHandler(RecvOpcode.SERVERLIST_REREQUEST, new ServerlistRequestHandler());
registerHandler(RecvOpcode.CHARLIST_REQUEST, new CharlistRequestHandler());
registerHandler(RecvOpcode.CHAR_SELECT, new CharSelectedHandler(channelDeps.transitionService()));
registerHandler(RecvOpcode.CHAR_SELECT, new CharSelectedHandler(channelDeps.banService(),
channelDeps.transitionService()));
registerHandler(RecvOpcode.LOGIN_PASSWORD, new LoginPasswordHandler(channelDeps.accountService(),
channelDeps.transitionService(), channelDeps.banService()));
registerHandler(RecvOpcode.RELOG, new RelogRequestHandler());
@@ -290,17 +291,18 @@ public final class PacketProcessor {
registerHandler(RecvOpcode.CREATE_CHAR, new CreateCharHandler(channelDeps.characterCreator()));
registerHandler(RecvOpcode.DELETE_CHAR, new DeleteCharHandler());
registerHandler(RecvOpcode.VIEW_ALL_CHAR, new ViewAllCharHandler());
registerHandler(RecvOpcode.PICK_ALL_CHAR, new ViewAllCharSelectedHandler(channelDeps.transitionService()));
registerHandler(RecvOpcode.PICK_ALL_CHAR, new ViewAllCharSelectedHandler(channelDeps.banService(),
channelDeps.transitionService()));
registerHandler(RecvOpcode.REGISTER_PIN, new RegisterPinHandler(channelDeps.accountService()));
registerHandler(RecvOpcode.GUEST_LOGIN, new GuestLoginHandler());
registerHandler(RecvOpcode.REGISTER_PIC, new RegisterPicHandler(channelDeps.accountService(),
channelDeps.transitionService()));
registerHandler(RecvOpcode.CHAR_SELECT_WITH_PIC, new CharSelectedWithPicHandler(
registerHandler(RecvOpcode.REGISTER_PIC, new RegisterPicHandler(channelDeps.banService(),
channelDeps.accountService(), channelDeps.transitionService()));
registerHandler(RecvOpcode.CHAR_SELECT_WITH_PIC, new CharSelectedWithPicHandler(channelDeps.banService(),
channelDeps.transitionService()));
registerHandler(RecvOpcode.SET_GENDER, new SetGenderHandler(channelDeps.accountService()));
registerHandler(RecvOpcode.VIEW_ALL_WITH_PIC, new ViewAllCharSelectedWithPicHandler(
registerHandler(RecvOpcode.VIEW_ALL_WITH_PIC, new ViewAllCharSelectedWithPicHandler(channelDeps.banService(),
channelDeps.transitionService()));
registerHandler(RecvOpcode.VIEW_ALL_PIC_REGISTER, new ViewAllCharRegisterPicHandler(
registerHandler(RecvOpcode.VIEW_ALL_PIC_REGISTER, new ViewAllCharRegisterPicHandler(channelDeps.banService(),
channelDeps.accountService(), channelDeps.transitionService()));
}

View File

@@ -45,6 +45,7 @@ import constants.net.ServerConstants;
import database.PgDatabaseConfig;
import database.PgDatabaseConnection;
import database.account.AccountRepository;
import database.ban.HwidBanRepository;
import database.ban.IpBanRepository;
import database.character.CharacterLoader;
import database.character.CharacterRepository;
@@ -86,6 +87,7 @@ import server.CashShop.CashItemFactory;
import server.SkillbookInformationProvider;
import server.ThreadManager;
import server.TimerManager;
import server.ban.HwidBanManager;
import server.ban.IpBanManager;
import server.expeditions.ExpeditionBossLog;
import server.life.PlayerNPC;
@@ -719,6 +721,7 @@ public class Server {
futures.add(initExecutor.submit(Quest::loadAllQuests));
futures.add(initExecutor.submit(SkillbookInformationProvider::loadAllSkillbookInformation));
futures.add(initExecutor.submit(channelDependencies.ipBanManager()::loadIpBans));
futures.add(initExecutor.submit(channelDependencies.hwidBanManager()::loadHwidBans));
initExecutor.shutdown();
TimeZone.setDefault(TimeZone.getTimeZone(YamlConfig.config.server.TIMEZONE));
@@ -833,7 +836,8 @@ public class Server {
DropProvider dropProvider = new DropProvider(new DropRepository(connection));
ShopFactory shopFactory = new ShopFactory(new ShopDao(connection));
IpBanManager ipBanManager = new IpBanManager(new IpBanRepository(connection));
BanService banService = new BanService(accountService, transitionService, ipBanManager);
HwidBanManager hwidBanManager = new HwidBanManager(new HwidBanRepository(connection));
BanService banService = new BanService(accountService, transitionService, ipBanManager, hwidBanManager);
ChannelDependencies channelDependencies = ChannelDependencies.builder()
.accountService(accountService)
.characterCreator(new CharacterCreator(connection, characterRepository))
@@ -848,6 +852,7 @@ public class Server {
.shopFactory(shopFactory)
.transitionService(transitionService)
.ipBanManager(ipBanManager)
.hwidBanManager(hwidBanManager)
.banService(banService)
.build();

View File

@@ -31,6 +31,7 @@ import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult;
import net.server.world.World;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import service.BanService;
import service.TransitionService;
import tools.PacketCreator;
@@ -40,9 +41,11 @@ import java.net.UnknownHostException;
public final class CharSelectedHandler extends AbstractPacketHandler {
private static final Logger log = LoggerFactory.getLogger(CharSelectedHandler.class);
private final BanService banService;
private final TransitionService transitionService;
public CharSelectedHandler(TransitionService transitionService) {
public CharSelectedHandler(BanService banService, TransitionService transitionService) {
this.banService = banService;
this.transitionService = transitionService;
}
@@ -74,6 +77,7 @@ public final class CharSelectedHandler extends AbstractPacketHandler {
c.updateMacs(macs);
c.updateHwid(hwid);
c.setHwid(hwid);
AntiMulticlientResult res = SessionCoordinator.getInstance().attemptGameSession(c, c.getAccID(), hwid);
if (res != AntiMulticlientResult.SUCCESS) {
@@ -81,7 +85,7 @@ public final class CharSelectedHandler extends AbstractPacketHandler {
return;
}
if (c.hasBannedMac() || c.hasBannedHWID()) {
if (banService.isBanned(c) || c.hasBannedMac()) {
SessionCoordinator.getInstance().closeSession(c, true);
return;
}

View File

@@ -10,6 +10,7 @@ import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult;
import net.server.world.World;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import service.BanService;
import service.TransitionService;
import tools.PacketCreator;
@@ -19,9 +20,11 @@ import java.net.UnknownHostException;
public class CharSelectedWithPicHandler extends AbstractPacketHandler {
private static final Logger log = LoggerFactory.getLogger(CharSelectedWithPicHandler.class);
private final BanService banService;
private final TransitionService transitionService;
public CharSelectedWithPicHandler(TransitionService transitionService) {
public CharSelectedWithPicHandler(BanService banService, TransitionService transitionService) {
this.banService = banService;
this.transitionService = transitionService;
}
@@ -54,8 +57,9 @@ public class CharSelectedWithPicHandler extends AbstractPacketHandler {
c.updateMacs(macs);
c.updateHwid(hwid);
c.setHwid(hwid);
if (c.hasBannedMac() || c.hasBannedHWID()) {
if (banService.isBanned(c) || c.hasBannedMac()) {
SessionCoordinator.getInstance().closeSession(c, true);
return;
}

View File

@@ -114,7 +114,7 @@ public final class LoginPasswordHandler implements PacketHandler {
}
boolean banCheckDisabled = false;
if (!banCheckDisabled && (banService.isBanned(c) || c.hasBannedMac() || c.hasBannedHWID())) {
if (!banCheckDisabled && (banService.isBanned(c) || c.hasBannedMac())) {
c.sendPacket(PacketCreator.getLoginFailed(3));
return;
}

View File

@@ -11,6 +11,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.PacketCreator;
@@ -20,10 +21,12 @@ import java.net.UnknownHostException;
public final class RegisterPicHandler extends AbstractPacketHandler {
private static final Logger log = LoggerFactory.getLogger(RegisterPicHandler.class);
private final BanService banService;
private final TransitionService transitionService;
private final AccountService accountService;
public RegisterPicHandler(AccountService accountService, TransitionService transitionService) {
public RegisterPicHandler(BanService banService, AccountService accountService, TransitionService transitionService) {
this.banService = banService;
this.accountService = accountService;
this.transitionService = transitionService;
}
@@ -47,6 +50,7 @@ public final class RegisterPicHandler extends AbstractPacketHandler {
c.updateMacs(macs);
c.updateHwid(hwid);
c.setHwid(hwid);
AntiMulticlientResult res = SessionCoordinator.getInstance().attemptGameSession(c, c.getAccID(), hwid);
if (res != AntiMulticlientResult.SUCCESS) {
@@ -54,7 +58,7 @@ public final class RegisterPicHandler extends AbstractPacketHandler {
return;
}
if (c.hasBannedMac() || c.hasBannedHWID()) {
if (banService.isBanned(c) || c.hasBannedMac()) {
SessionCoordinator.getInstance().closeSession(c, true);
return;
}

View File

@@ -11,6 +11,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.PacketCreator;
import tools.Randomizer;
@@ -21,10 +22,13 @@ import java.net.UnknownHostException;
public final class ViewAllCharRegisterPicHandler extends AbstractPacketHandler {
private static final Logger log = LoggerFactory.getLogger(ViewAllCharRegisterPicHandler.class);
private final BanService banService;
private final AccountService accountService;
private final TransitionService transitionService;
public ViewAllCharRegisterPicHandler(AccountService accountService, TransitionService transitionService) {
public ViewAllCharRegisterPicHandler(BanService banService, AccountService accountService,
TransitionService transitionService) {
this.banService = banService;
this.accountService = accountService;
this.transitionService = transitionService;
}
@@ -49,8 +53,9 @@ public final class ViewAllCharRegisterPicHandler extends AbstractPacketHandler {
c.updateMacs(mac);
c.updateHwid(hwid);
c.setHwid(hwid);
if (c.hasBannedMac() || c.hasBannedHWID()) {
if (banService.isBanned(c) || c.hasBannedMac()) {
SessionCoordinator.getInstance().closeSession(c, true);
return;
}

View File

@@ -31,6 +31,7 @@ import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult;
import net.server.world.World;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import service.BanService;
import service.TransitionService;
import tools.PacketCreator;
import tools.Randomizer;
@@ -41,9 +42,11 @@ import java.net.UnknownHostException;
public final class ViewAllCharSelectedHandler extends AbstractPacketHandler {
private static final Logger log = LoggerFactory.getLogger(ViewAllCharSelectedHandler.class);
private final BanService banService;
private final TransitionService transitionService;
public ViewAllCharSelectedHandler(TransitionService transitionService) {
public ViewAllCharSelectedHandler(BanService banService, TransitionService transitionService) {
this.banService = banService;
this.transitionService = transitionService;
}
@@ -76,8 +79,9 @@ public final class ViewAllCharSelectedHandler extends AbstractPacketHandler {
c.updateMacs(macs);
c.updateHwid(hwid);
c.setHwid(hwid);
if (c.hasBannedMac() || c.hasBannedHWID()) {
if (banService.isBanned(c) || c.hasBannedMac()) {
SessionCoordinator.getInstance().closeSession(c, true);
return;
}

View File

@@ -10,6 +10,7 @@ import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult;
import net.server.world.World;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import service.BanService;
import service.TransitionService;
import tools.PacketCreator;
import tools.Randomizer;
@@ -20,9 +21,11 @@ import java.net.UnknownHostException;
public class ViewAllCharSelectedWithPicHandler extends AbstractPacketHandler {
private static final Logger log = LoggerFactory.getLogger(ViewAllCharSelectedWithPicHandler.class);
private final BanService banService;
private final TransitionService transitionService;
public ViewAllCharSelectedWithPicHandler(TransitionService transitionService) {
public ViewAllCharSelectedWithPicHandler(BanService banService, TransitionService transitionService) {
this.banService = banService;
this.transitionService = transitionService;
}
@@ -57,8 +60,9 @@ public class ViewAllCharSelectedWithPicHandler extends AbstractPacketHandler {
c.updateMacs(macs);
c.updateHwid(hwid);
c.setHwid(hwid);
if (c.hasBannedMac() || c.hasBannedHWID()) {
if (banService.isBanned(c) || c.hasBannedMac()) {
SessionCoordinator.getInstance().closeSession(c, true);
return;
}

View File

@@ -0,0 +1,49 @@
package server.ban;
import database.ban.HwidBan;
import database.ban.HwidBanRepository;
import lombok.extern.slf4j.Slf4j;
import net.jcip.annotations.ThreadSafe;
import net.server.coordinator.session.Hwid;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/**
* @author Ponk
*/
@ThreadSafe
@Slf4j
public class HwidBanManager {
private final HwidBanRepository hwidBanRepository;
private final Set<Hwid> bannedHwids = new HashSet<>();
public HwidBanManager(HwidBanRepository hwidBanRepository) {
this.hwidBanRepository = hwidBanRepository;
}
public synchronized void loadHwidBans() {
List<HwidBan> hwidBans = hwidBanRepository.getAllHwidBans();
log.debug("Loaded {} hwid bans", hwidBans.size());
bannedHwids.addAll(hwidBans.stream()
.map(HwidBanManager::createHwid)
.filter(Objects::nonNull)
.toList()
);
}
private static Hwid createHwid(HwidBan hwidBan) {
try {
return new Hwid(hwidBan.hwid());
} catch (IllegalArgumentException e) {
log.warn("Unable to create Hwid from: {} due to bad 'hwid' value in database", hwidBan);
return null;
}
}
public synchronized boolean isBanned(Hwid hwid) {
return bannedHwids.contains(hwid);
}
}

View File

@@ -7,7 +7,9 @@ import config.YamlConfig;
import lombok.extern.slf4j.Slf4j;
import net.packet.Packet;
import net.server.Server;
import net.server.coordinator.session.Hwid;
import server.TimerManager;
import server.ban.HwidBanManager;
import server.ban.IpBanManager;
import tools.PacketCreator;
@@ -21,11 +23,14 @@ public class BanService {
private final AccountService accountService;
private final TransitionService transitionService;
private final IpBanManager ipBanManager;
private final HwidBanManager hwidBanManager;
public BanService(AccountService accountService, TransitionService transitionService, IpBanManager ipBanManager) {
public BanService(AccountService accountService, TransitionService transitionService, IpBanManager ipBanManager,
HwidBanManager hwidBanManager) {
this.accountService = accountService;
this.transitionService = transitionService;
this.ipBanManager = ipBanManager;
this.hwidBanManager = hwidBanManager;
}
public void autoban(Character chr, AutobanFactory type, String reason) {
@@ -116,11 +121,16 @@ public class BanService {
}
public boolean isBanned(Client c) {
return isIpBanned(c);
return isIpBanned(c) || isHwidBanned(c);
}
private boolean isIpBanned(Client c) {
String ip = c.getRemoteAddress();
return ip != null && ipBanManager.isBanned(ip);
}
private boolean isHwidBanned(Client c) {
Hwid hwid = c.getHwid();
return hwid != null && hwidBanManager.isBanned(hwid);
}
}

View File

@@ -6,3 +6,12 @@ CREATE TABLE ip_ban
PRIMARY KEY (ip)
);
GRANT SELECT, INSERT ON TABLE ip_ban TO ${server-username};
CREATE TABLE hwid_ban
(
hwid varchar(30) NOT NULL,
account_id integer,
created_at timestamp DEFAULT now() NOT NULL,
PRIMARY KEY (hwid)
);
GRANT SELECT ON TABLE hwid_ban TO ${server-username};

View File

@@ -30,5 +30,5 @@ GRANT SELECT, INSERT, UPDATE ON TABLE account TO ${server-username};
GRANT USAGE ON SEQUENCE account_id_seq TO ${server-username};
ALTER SEQUENCE account_id_seq RESTART WITH 1000;
-- INSERT INTO account (id, name, password, pin, pic, birthdate)
-- VALUES (1, 'admin', '$2y$12$aFD9BDeUocDMY1X4tDYDyeJw/HhkQwCQWs3KAY7gCaRG0cpqJcaL.', '0000', '000000', '2005-05-11');
INSERT INTO account (id, name, password, pin, pic, birthdate, tos_accepted, chr_slots, login_state)
VALUES (nextval('account_id_seq'), 'admin', '$2y$12$aFD9BDeUocDMY1X4tDYDyeJw/HhkQwCQWs3KAY7gCaRG0cpqJcaL.', '0000', '000000', '2005-05-11', true, 9, 0);

View File

@@ -0,0 +1,48 @@
package server.ban;
import database.DatabaseTest;
import database.ban.HwidBanRepository;
import net.server.coordinator.session.Hwid;
import org.jdbi.v3.core.Handle;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class HwidBanManagerTest extends DatabaseTest {
private HwidBanRepository hwidBanRepository;
private HwidBanManager hwidBanManager;
@BeforeEach
void setUp() {
this.hwidBanRepository = new HwidBanRepository(connection);
this.hwidBanManager = new HwidBanManager(hwidBanRepository);
}
@AfterEach
void deleteHwidBans() {
clearTable("hwid_ban");
}
@Test
void loadHwidBans_shouldLoadFromRepository() {
Hwid hwid = new Hwid("ABC1DEF2");
assertFalse(hwidBanManager.isBanned(hwid));
hwidBanManager.loadHwidBans();
assertFalse(hwidBanManager.isBanned(hwid));
saveHwidBan(hwid);
hwidBanManager.loadHwidBans();
assertTrue(hwidBanManager.isBanned(hwid));
}
private void saveHwidBan(Hwid hwid) {
try (Handle handle = connection.getHandle()) {
handle.execute("INSERT INTO hwid_ban (hwid) VALUES (?)", hwid.hwid());
}
}
}