Add dedicated host hwid cache, further refactor session coordinator

This commit is contained in:
P0nk
2021-06-29 08:35:21 +02:00
parent 671313ab57
commit d34798649b
5 changed files with 134 additions and 100 deletions

View File

@@ -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));
}
}

View File

@@ -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<String, HostHwid> hostHwidCache = new ConcurrentHashMap<>(); // Key: remoteHost
void clearExpired() {
SessionDAO.deleteExpiredHwidAccounts();
Instant now = Instant.ofEpochMilli(Server.getInstance().getCurrentTime());
List<String> remoteHostsToRemove = new ArrayList<>();
for (Map.Entry<String, HostHwid> 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();
}
}

View File

@@ -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;
}
}

View File

@@ -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<Integer, MapleClient> onlineClients = new HashMap<>();
private final Set<String> onlineRemoteHwids = new HashSet<>();
private final Map<String, Set<IoSession>> loginRemoteHosts = new HashMap<>();
private final Set<String> pooledRemoteHosts = new HashSet<>();
private final ConcurrentHashMap<String, String> cachedHostHwids = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Long> cachedHostTimeout = new ConcurrentHashMap<>();
private final Map<Integer, MapleClient> onlineClients = new HashMap<>(); // Key: account id
private final Set<String> onlineRemoteHwids = new HashSet<>(); // Hwid/nibblehwid
private final Map<String, Set<IoSession>> 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<IoSession> 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<IoSession> 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<IoSession> 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<IoSession> 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<String> toRemove = new LinkedList<>();
for (Entry<String, Long> 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() {

View File

@@ -29,6 +29,6 @@ public class LoginCoordinatorTask implements Runnable {
@Override
public void run() {
MapleSessionCoordinator.getInstance().runUpdateHwidHistory();
MapleSessionCoordinator.getInstance().clearExpiredHwidHistory();
}
}