diff --git a/src/main/java/net/server/coordinator/session/HostHwid.java b/src/main/java/net/server/coordinator/session/HostHwid.java new file mode 100644 index 0000000000..b6c7a6590e --- /dev/null +++ b/src/main/java/net/server/coordinator/session/HostHwid.java @@ -0,0 +1,16 @@ +package net.server.coordinator.session; + +import net.server.Server; + +import java.time.Instant; +import java.util.concurrent.TimeUnit; + +record HostHwid(String hwid, Instant expiry) { + static HostHwid createWithDefaultExpiry(String hwid) { + return new HostHwid(hwid, getDefaultExpiry()); + } + + private static Instant getDefaultExpiry() { + return Instant.ofEpochMilli(Server.getInstance().getCurrentTime() + TimeUnit.DAYS.toMillis(7)); + } +} diff --git a/src/main/java/net/server/coordinator/session/HostHwidCache.java b/src/main/java/net/server/coordinator/session/HostHwidCache.java new file mode 100644 index 0000000000..7f75ae854f --- /dev/null +++ b/src/main/java/net/server/coordinator/session/HostHwidCache.java @@ -0,0 +1,48 @@ +package net.server.coordinator.session; + +import net.server.Server; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +class HostHwidCache { + private final ConcurrentHashMap hostHwidCache = new ConcurrentHashMap<>(); // Key: remoteHost + + void clearExpired() { + SessionDAO.deleteExpiredHwidAccounts(); + + Instant now = Instant.ofEpochMilli(Server.getInstance().getCurrentTime()); + List remoteHostsToRemove = new ArrayList<>(); + for (Map.Entry entry : hostHwidCache.entrySet()) { + if (now.isAfter(entry.getValue().expiry())) { + remoteHostsToRemove.add(entry.getKey()); + } + } + + for (String remoteHost : remoteHostsToRemove) { + hostHwidCache.remove(remoteHost); + } + } + + void addEntry(String remoteHost, String remoteHwid) { + hostHwidCache.put(remoteHost, HostHwid.createWithDefaultExpiry(remoteHwid)); + } + + HostHwid getEntry(String remoteHost) { + return hostHwidCache.get(remoteHost); + } + + String removeEntryAndGetItsHwid(String remoteHost) { + HostHwid hostHwid = hostHwidCache.remove(remoteHost); + return hostHwid == null ? null : hostHwid.hwid(); + } + + String getEntryHwid(String remoteHost) { + HostHwid hostHwid = hostHwidCache.get(remoteHost); + return hostHwid == null ? null : hostHwid.hwid(); + } + +} diff --git a/src/main/java/net/server/coordinator/session/HwidRelevance.java b/src/main/java/net/server/coordinator/session/HwidRelevance.java index bfd1e47219..6dbbf6f838 100644 --- a/src/main/java/net/server/coordinator/session/HwidRelevance.java +++ b/src/main/java/net/server/coordinator/session/HwidRelevance.java @@ -1,4 +1,7 @@ package net.server.coordinator.session; public record HwidRelevance(String hwid, int relevance) { + public int getIncrementedRelevance() { + return relevance < Byte.MAX_VALUE ? relevance + 1 : relevance; + } } diff --git a/src/main/java/net/server/coordinator/session/MapleSessionCoordinator.java b/src/main/java/net/server/coordinator/session/MapleSessionCoordinator.java index dc1e45ca7b..46d746339f 100644 --- a/src/main/java/net/server/coordinator/session/MapleSessionCoordinator.java +++ b/src/main/java/net/server/coordinator/session/MapleSessionCoordinator.java @@ -34,8 +34,6 @@ import java.sql.SQLException; import java.time.Instant; import java.util.*; import java.util.Map.Entry; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** @@ -62,13 +60,10 @@ public class MapleSessionCoordinator { private final SessionInitialization sessionInit = new SessionInitialization(); private final LoginStorage loginStorage = new LoginStorage(); - private final Map onlineClients = new HashMap<>(); - private final Set onlineRemoteHwids = new HashSet<>(); - private final Map> loginRemoteHosts = new HashMap<>(); - private final Set pooledRemoteHosts = new HashSet<>(); - - private final ConcurrentHashMap cachedHostHwids = new ConcurrentHashMap<>(); - private final ConcurrentHashMap cachedHostTimeout = new ConcurrentHashMap<>(); + private final Map onlineClients = new HashMap<>(); // Key: account id + private final Set onlineRemoteHwids = new HashSet<>(); // Hwid/nibblehwid + private final Map> loginRemoteHosts = new HashMap<>(); // Key: Ip (+ nibblehwid) + private final HostHwidCache hostHwidCache = new HostHwidCache(); private MapleSessionCoordinator() { } @@ -81,12 +76,7 @@ public class MapleSessionCoordinator { if (!routineCheck) { // better update HWID relevance as soon as the login is authenticated Instant expiry = HwidAssociationExpiry.getHwidAccountExpiry(hwidRelevance.relevance()); - int relevance = hwidRelevance.relevance(); - if (relevance < Byte.MAX_VALUE) { - relevance++; - } - - SessionDAO.updateAccountAccess(con, nibbleHwid, accountId, expiry, relevance); + SessionDAO.updateAccountAccess(con, nibbleHwid, accountId, expiry, hwidRelevance.getIncrementedRelevance()); } return true; @@ -121,20 +111,26 @@ public class MapleSessionCoordinator { return (MapleClient) session.getAttribute(MapleClient.CLIENT_KEY); } - public void updateOnlineSession(IoSession session) { + /** + * Overwrites any existing online client for the account id, making sure to disconnect it as well. + */ + public void updateOnlineClient(IoSession session) { MapleClient client = getSessionClient(session); if (client != null) { int accountId = client.getAccID(); - MapleClient ingameClient = onlineClients.get(accountId); - if (ingameClient != null) { // thanks MedicOP for finding out a loss of loggedin account uniqueness when using the CMS "Unstuck" feature - ingameClient.forceDisconnect(); - } - + disconnectClientIfOnline(accountId); onlineClients.put(accountId, client); } } + private void disconnectClientIfOnline(int accountId) { + MapleClient ingameClient = onlineClients.get(accountId); + if (ingameClient != null) { // thanks MedicOP for finding out a loss of loggedin account uniqueness when using the CMS "Unstuck" feature + ingameClient.forceDisconnect(); + } + } + public boolean canStartLoginSession(IoSession session) { if (!YamlConfig.config.server.DETERRED_MULTICLIENT) { return true; @@ -152,39 +148,31 @@ public class MapleSessionCoordinator { } try { - String knownHwid = cachedHostHwids.get(remoteHost); - if (knownHwid != null) { - if (onlineRemoteHwids.contains(knownHwid)) { - return false; - } - } - - if (loginRemoteHosts.containsKey(remoteHost)) { + final HostHwid knownHwid = hostHwidCache.getEntry(remoteHost); + if (knownHwid != null && onlineRemoteHwids.contains(knownHwid.hwid())) { + return false; + } else if (loginRemoteHosts.containsKey(remoteHost)) { return false; } - Set lrh = new HashSet<>(2); - lrh.add(session); - loginRemoteHosts.put(remoteHost, lrh); - + addRemoteHostSession(remoteHost, session); return true; } finally { sessionInit.finalize(remoteHost); } } + private void addRemoteHostSession(String remoteHost, IoSession session) { + Set sessions = new HashSet<>(2); + sessions.add(session); + loginRemoteHosts.put(remoteHost, sessions); + } + public void closeLoginSession(IoSession session) { - String nibbleHwid = (String) session.removeAttribute(MapleClient.CLIENT_NIBBLEHWID); String remoteHost = getSessionRemoteHost(session); - - Set lrh = loginRemoteHosts.get(remoteHost); - if (lrh != null) { - lrh.remove(session); - if (lrh.isEmpty()) { - loginRemoteHosts.remove(remoteHost); - } - } - + removeRemoteHostSession(remoteHost, session); + + String nibbleHwid = (String) session.removeAttribute(MapleClient.CLIENT_NIBBLEHWID); if (nibbleHwid != null) { onlineRemoteHwids.remove(nibbleHwid); @@ -200,6 +188,17 @@ public class MapleSessionCoordinator { } } + private void removeRemoteHostSession(String remoteHost, IoSession session) { + Set sessions = loginRemoteHosts.get(remoteHost); + if (sessions != null) { + sessions.remove(session); + + if (sessions.isEmpty()) { + loginRemoteHosts.remove(remoteHost); + } + } + } + public AntiMulticlientResult attemptLoginSession(IoSession session, String nibbleHwid, int accountId, boolean routineCheck) { if (!YamlConfig.config.server.DETERRED_MULTICLIENT) { session.setAttribute(MapleClient.CLIENT_NIBBLEHWID, nibbleHwid); @@ -215,24 +214,16 @@ public class MapleSessionCoordinator { try { if (!loginStorage.registerLogin(accountId)) { return AntiMulticlientResult.MANY_ACCOUNT_ATTEMPTS; + } else if (routineCheck && !attemptAccountAccess(accountId, nibbleHwid, routineCheck)) { + return AntiMulticlientResult.REMOTE_REACHED_LIMIT; + } else if (onlineRemoteHwids.contains(nibbleHwid)) { + return AntiMulticlientResult.REMOTE_LOGGEDIN; + } else if (!attemptAccountAccess(accountId, nibbleHwid, routineCheck)) { + return AntiMulticlientResult.REMOTE_REACHED_LIMIT; } - if (!routineCheck) { - if (onlineRemoteHwids.contains(nibbleHwid)) { - return AntiMulticlientResult.REMOTE_LOGGEDIN; - } - - if (!attemptAccountAccess(accountId, nibbleHwid, routineCheck)) { - return AntiMulticlientResult.REMOTE_REACHED_LIMIT; - } - - session.setAttribute(MapleClient.CLIENT_NIBBLEHWID, nibbleHwid); - onlineRemoteHwids.add(nibbleHwid); - } else { - if (!attemptAccountAccess(accountId, nibbleHwid, routineCheck)) { - return AntiMulticlientResult.REMOTE_REACHED_LIMIT; - } - } + session.setAttribute(MapleClient.CLIENT_NIBBLEHWID, nibbleHwid); + onlineRemoteHwids.add(nibbleHwid); return AntiMulticlientResult.SUCCESS; } finally { @@ -243,8 +234,8 @@ public class MapleSessionCoordinator { public AntiMulticlientResult attemptGameSession(IoSession session, int accountId, String remoteHwid) { final String remoteHost = getSessionRemoteHost(session); if (!YamlConfig.config.server.DETERRED_MULTICLIENT) { - associateRemoteHostHwid(remoteHost, remoteHwid); - associateRemoteHostHwid(getSessionRemoteAddress(session), remoteHwid); // no HWID information on the loggedin newcomer session... + hostHwidCache.addEntry(remoteHost, remoteHwid); + hostHwidCache.addEntry(getSessionRemoteAddress(session), remoteHwid); // no HWID information on the loggedin newcomer session... return AntiMulticlientResult.SUCCESS; } @@ -263,9 +254,7 @@ public class MapleSessionCoordinator { if (!remoteHwid.endsWith(nibbleHwid)) { return AntiMulticlientResult.REMOTE_NO_MATCH; - } - - if (onlineRemoteHwids.contains(remoteHwid)) { + } else if (onlineRemoteHwids.contains(remoteHwid)) { return AntiMulticlientResult.REMOTE_LOGGEDIN; } @@ -273,8 +262,8 @@ public class MapleSessionCoordinator { // updated session CLIENT_HWID attribute will be set when the player log in the game onlineRemoteHwids.add(remoteHwid); - associateRemoteHostHwid(remoteHost, remoteHwid); - associateRemoteHostHwid(getSessionRemoteAddress(session), remoteHwid); + hostHwidCache.addEntry(remoteHost, remoteHwid); + hostHwidCache.addEntry(getSessionRemoteAddress(session), remoteHwid); associateHwidAccountIfAbsent(remoteHwid, accountId); return AntiMulticlientResult.SUCCESS; @@ -317,10 +306,10 @@ public class MapleSessionCoordinator { } MapleClient client = new MapleClient(null, null, session); - Integer cid = Server.getInstance().freeCharacteridInTransition(client); - if (cid != null) { + Integer chrId = Server.getInstance().freeCharacteridInTransition(client); + if (chrId != null) { try { - client.setAccID(MapleCharacter.loadCharFromDB(cid, client, false).getAccountID()); + client.setAccID(MapleCharacter.loadCharFromDB(chrId, client, false).getAccountID()); } catch (SQLException sqle) { sqle.printStackTrace(); } @@ -341,9 +330,10 @@ public class MapleSessionCoordinator { hwid = (String) session.removeAttribute(MapleClient.CLIENT_HWID); onlineRemoteHwids.remove(hwid); - + if (client != null) { - if (hwid != null) { // is a game session + final boolean isGameSession = hwid != null; + if (isGameSession) { onlineClients.remove(client.getAccID()); } else { MapleClient loggedClient = onlineClients.get(client.getAccID()); @@ -358,44 +348,21 @@ public class MapleSessionCoordinator { if (immediately != null) { session.close(immediately); } - - // session.removeAttribute(MapleClient.CLIENT_REMOTE_ADDRESS); No real need for removing String property on closed sessions } public String pickLoginSessionHwid(IoSession session) { String remoteHost = getSessionRemoteAddress(session); - return cachedHostHwids.remove(remoteHost); // thanks BHB, resinate for noticing players from same network not being able to login + // thanks BHB, resinate for noticing players from same network not being able to login + return hostHwidCache.removeEntryAndGetItsHwid(remoteHost); } public String getGameSessionHwid(IoSession session) { String remoteHost = getSessionRemoteHost(session); - return cachedHostHwids.get(remoteHost); + return hostHwidCache.getEntryHwid(remoteHost); } - private void associateRemoteHostHwid(String remoteHost, String remoteHwid) { - cachedHostHwids.put(remoteHost, remoteHwid); - cachedHostTimeout.put(remoteHost, getHostTimeout()); - } - - private static long getHostTimeout() { - return Server.getInstance().getCurrentTime() + TimeUnit.DAYS.toMillis(7); // 1 week-time entry - } - - public void runUpdateHwidHistory() { - SessionDAO.deleteExpiredHwidAccounts(); - - long timeNow = Server.getInstance().getCurrentTime(); - List toRemove = new LinkedList<>(); - for (Entry cht : cachedHostTimeout.entrySet()) { - if (cht.getValue() < timeNow) { - toRemove.add(cht.getKey()); - } - } - - for (String s : toRemove) { - cachedHostHwids.remove(s); - cachedHostTimeout.remove(s); - } + public void clearExpiredHwidHistory() { + hostHwidCache.clearExpired(); } public void runUpdateLoginHistory() { diff --git a/src/main/java/net/server/task/LoginCoordinatorTask.java b/src/main/java/net/server/task/LoginCoordinatorTask.java index bf9109a40b..6efe23b2dd 100644 --- a/src/main/java/net/server/task/LoginCoordinatorTask.java +++ b/src/main/java/net/server/task/LoginCoordinatorTask.java @@ -29,6 +29,6 @@ public class LoginCoordinatorTask implements Runnable { @Override public void run() { - MapleSessionCoordinator.getInstance().runUpdateHwidHistory(); + MapleSessionCoordinator.getInstance().clearExpiredHwidHistory(); } }