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; package net.server.coordinator.session;
public record HwidRelevance(String hwid, int relevance) { 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.time.Instant;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@@ -62,13 +60,10 @@ public class MapleSessionCoordinator {
private final SessionInitialization sessionInit = new SessionInitialization(); private final SessionInitialization sessionInit = new SessionInitialization();
private final LoginStorage loginStorage = new LoginStorage(); private final LoginStorage loginStorage = new LoginStorage();
private final Map<Integer, MapleClient> onlineClients = new HashMap<>(); private final Map<Integer, MapleClient> onlineClients = new HashMap<>(); // Key: account id
private final Set<String> onlineRemoteHwids = new HashSet<>(); private final Set<String> onlineRemoteHwids = new HashSet<>(); // Hwid/nibblehwid
private final Map<String, Set<IoSession>> loginRemoteHosts = new HashMap<>(); private final Map<String, Set<IoSession>> loginRemoteHosts = new HashMap<>(); // Key: Ip (+ nibblehwid)
private final Set<String> pooledRemoteHosts = new HashSet<>(); private final HostHwidCache hostHwidCache = new HostHwidCache();
private final ConcurrentHashMap<String, String> cachedHostHwids = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Long> cachedHostTimeout = new ConcurrentHashMap<>();
private MapleSessionCoordinator() { private MapleSessionCoordinator() {
} }
@@ -81,12 +76,7 @@ public class MapleSessionCoordinator {
if (!routineCheck) { if (!routineCheck) {
// better update HWID relevance as soon as the login is authenticated // better update HWID relevance as soon as the login is authenticated
Instant expiry = HwidAssociationExpiry.getHwidAccountExpiry(hwidRelevance.relevance()); Instant expiry = HwidAssociationExpiry.getHwidAccountExpiry(hwidRelevance.relevance());
int relevance = hwidRelevance.relevance(); SessionDAO.updateAccountAccess(con, nibbleHwid, accountId, expiry, hwidRelevance.getIncrementedRelevance());
if (relevance < Byte.MAX_VALUE) {
relevance++;
}
SessionDAO.updateAccountAccess(con, nibbleHwid, accountId, expiry, relevance);
} }
return true; return true;
@@ -121,18 +111,24 @@ public class MapleSessionCoordinator {
return (MapleClient) session.getAttribute(MapleClient.CLIENT_KEY); 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); MapleClient client = getSessionClient(session);
if (client != null) { if (client != null) {
int accountId = client.getAccID(); int accountId = client.getAccID();
disconnectClientIfOnline(accountId);
onlineClients.put(accountId, client);
}
}
private void disconnectClientIfOnline(int accountId) {
MapleClient ingameClient = onlineClients.get(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 if (ingameClient != null) { // thanks MedicOP for finding out a loss of loggedin account uniqueness when using the CMS "Unstuck" feature
ingameClient.forceDisconnect(); ingameClient.forceDisconnect();
} }
onlineClients.put(accountId, client);
}
} }
public boolean canStartLoginSession(IoSession session) { public boolean canStartLoginSession(IoSession session) {
@@ -152,39 +148,31 @@ public class MapleSessionCoordinator {
} }
try { try {
String knownHwid = cachedHostHwids.get(remoteHost); final HostHwid knownHwid = hostHwidCache.getEntry(remoteHost);
if (knownHwid != null) { if (knownHwid != null && onlineRemoteHwids.contains(knownHwid.hwid())) {
if (onlineRemoteHwids.contains(knownHwid)) {
return false; return false;
} } else if (loginRemoteHosts.containsKey(remoteHost)) {
}
if (loginRemoteHosts.containsKey(remoteHost)) {
return false; return false;
} }
Set<IoSession> lrh = new HashSet<>(2); addRemoteHostSession(remoteHost, session);
lrh.add(session);
loginRemoteHosts.put(remoteHost, lrh);
return true; return true;
} finally { } finally {
sessionInit.finalize(remoteHost); 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) { public void closeLoginSession(IoSession session) {
String nibbleHwid = (String) session.removeAttribute(MapleClient.CLIENT_NIBBLEHWID);
String remoteHost = getSessionRemoteHost(session); String remoteHost = getSessionRemoteHost(session);
removeRemoteHostSession(remoteHost, session);
Set<IoSession> lrh = loginRemoteHosts.get(remoteHost); String nibbleHwid = (String) session.removeAttribute(MapleClient.CLIENT_NIBBLEHWID);
if (lrh != null) {
lrh.remove(session);
if (lrh.isEmpty()) {
loginRemoteHosts.remove(remoteHost);
}
}
if (nibbleHwid != null) { if (nibbleHwid != null) {
onlineRemoteHwids.remove(nibbleHwid); 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) { public AntiMulticlientResult attemptLoginSession(IoSession session, String nibbleHwid, int accountId, boolean routineCheck) {
if (!YamlConfig.config.server.DETERRED_MULTICLIENT) { if (!YamlConfig.config.server.DETERRED_MULTICLIENT) {
session.setAttribute(MapleClient.CLIENT_NIBBLEHWID, nibbleHwid); session.setAttribute(MapleClient.CLIENT_NIBBLEHWID, nibbleHwid);
@@ -215,24 +214,16 @@ public class MapleSessionCoordinator {
try { try {
if (!loginStorage.registerLogin(accountId)) { if (!loginStorage.registerLogin(accountId)) {
return AntiMulticlientResult.MANY_ACCOUNT_ATTEMPTS; return AntiMulticlientResult.MANY_ACCOUNT_ATTEMPTS;
} } else if (routineCheck && !attemptAccountAccess(accountId, nibbleHwid, routineCheck)) {
return AntiMulticlientResult.REMOTE_REACHED_LIMIT;
if (!routineCheck) { } else if (onlineRemoteHwids.contains(nibbleHwid)) {
if (onlineRemoteHwids.contains(nibbleHwid)) {
return AntiMulticlientResult.REMOTE_LOGGEDIN; return AntiMulticlientResult.REMOTE_LOGGEDIN;
} } else if (!attemptAccountAccess(accountId, nibbleHwid, routineCheck)) {
if (!attemptAccountAccess(accountId, nibbleHwid, routineCheck)) {
return AntiMulticlientResult.REMOTE_REACHED_LIMIT; return AntiMulticlientResult.REMOTE_REACHED_LIMIT;
} }
session.setAttribute(MapleClient.CLIENT_NIBBLEHWID, nibbleHwid); session.setAttribute(MapleClient.CLIENT_NIBBLEHWID, nibbleHwid);
onlineRemoteHwids.add(nibbleHwid); onlineRemoteHwids.add(nibbleHwid);
} else {
if (!attemptAccountAccess(accountId, nibbleHwid, routineCheck)) {
return AntiMulticlientResult.REMOTE_REACHED_LIMIT;
}
}
return AntiMulticlientResult.SUCCESS; return AntiMulticlientResult.SUCCESS;
} finally { } finally {
@@ -243,8 +234,8 @@ public class MapleSessionCoordinator {
public AntiMulticlientResult attemptGameSession(IoSession session, int accountId, String remoteHwid) { public AntiMulticlientResult attemptGameSession(IoSession session, int accountId, String remoteHwid) {
final String remoteHost = getSessionRemoteHost(session); final String remoteHost = getSessionRemoteHost(session);
if (!YamlConfig.config.server.DETERRED_MULTICLIENT) { if (!YamlConfig.config.server.DETERRED_MULTICLIENT) {
associateRemoteHostHwid(remoteHost, remoteHwid); hostHwidCache.addEntry(remoteHost, remoteHwid);
associateRemoteHostHwid(getSessionRemoteAddress(session), remoteHwid); // no HWID information on the loggedin newcomer session... hostHwidCache.addEntry(getSessionRemoteAddress(session), remoteHwid); // no HWID information on the loggedin newcomer session...
return AntiMulticlientResult.SUCCESS; return AntiMulticlientResult.SUCCESS;
} }
@@ -263,9 +254,7 @@ public class MapleSessionCoordinator {
if (!remoteHwid.endsWith(nibbleHwid)) { if (!remoteHwid.endsWith(nibbleHwid)) {
return AntiMulticlientResult.REMOTE_NO_MATCH; return AntiMulticlientResult.REMOTE_NO_MATCH;
} } else if (onlineRemoteHwids.contains(remoteHwid)) {
if (onlineRemoteHwids.contains(remoteHwid)) {
return AntiMulticlientResult.REMOTE_LOGGEDIN; 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 // updated session CLIENT_HWID attribute will be set when the player log in the game
onlineRemoteHwids.add(remoteHwid); onlineRemoteHwids.add(remoteHwid);
associateRemoteHostHwid(remoteHost, remoteHwid); hostHwidCache.addEntry(remoteHost, remoteHwid);
associateRemoteHostHwid(getSessionRemoteAddress(session), remoteHwid); hostHwidCache.addEntry(getSessionRemoteAddress(session), remoteHwid);
associateHwidAccountIfAbsent(remoteHwid, accountId); associateHwidAccountIfAbsent(remoteHwid, accountId);
return AntiMulticlientResult.SUCCESS; return AntiMulticlientResult.SUCCESS;
@@ -317,10 +306,10 @@ public class MapleSessionCoordinator {
} }
MapleClient client = new MapleClient(null, null, session); MapleClient client = new MapleClient(null, null, session);
Integer cid = Server.getInstance().freeCharacteridInTransition(client); Integer chrId = Server.getInstance().freeCharacteridInTransition(client);
if (cid != null) { if (chrId != null) {
try { try {
client.setAccID(MapleCharacter.loadCharFromDB(cid, client, false).getAccountID()); client.setAccID(MapleCharacter.loadCharFromDB(chrId, client, false).getAccountID());
} catch (SQLException sqle) { } catch (SQLException sqle) {
sqle.printStackTrace(); sqle.printStackTrace();
} }
@@ -343,7 +332,8 @@ public class MapleSessionCoordinator {
onlineRemoteHwids.remove(hwid); onlineRemoteHwids.remove(hwid);
if (client != null) { if (client != null) {
if (hwid != null) { // is a game session final boolean isGameSession = hwid != null;
if (isGameSession) {
onlineClients.remove(client.getAccID()); onlineClients.remove(client.getAccID());
} else { } else {
MapleClient loggedClient = onlineClients.get(client.getAccID()); MapleClient loggedClient = onlineClients.get(client.getAccID());
@@ -358,44 +348,21 @@ public class MapleSessionCoordinator {
if (immediately != null) { if (immediately != null) {
session.close(immediately); session.close(immediately);
} }
// session.removeAttribute(MapleClient.CLIENT_REMOTE_ADDRESS); No real need for removing String property on closed sessions
} }
public String pickLoginSessionHwid(IoSession session) { public String pickLoginSessionHwid(IoSession session) {
String remoteHost = getSessionRemoteAddress(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) { public String getGameSessionHwid(IoSession session) {
String remoteHost = getSessionRemoteHost(session); String remoteHost = getSessionRemoteHost(session);
return cachedHostHwids.get(remoteHost); return hostHwidCache.getEntryHwid(remoteHost);
} }
private void associateRemoteHostHwid(String remoteHost, String remoteHwid) { public void clearExpiredHwidHistory() {
cachedHostHwids.put(remoteHost, remoteHwid); hostHwidCache.clearExpired();
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 runUpdateLoginHistory() { public void runUpdateLoginHistory() {

View File

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