Add dedicated host hwid cache, further refactor session coordinator
This commit is contained in:
16
src/main/java/net/server/coordinator/session/HostHwid.java
Normal file
16
src/main/java/net/server/coordinator/session/HostHwid.java
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,20 +111,26 @@ 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();
|
||||||
MapleClient ingameClient = onlineClients.get(accountId);
|
disconnectClientIfOnline(accountId);
|
||||||
if (ingameClient != null) { // thanks MedicOP for finding out a loss of loggedin account uniqueness when using the CMS "Unstuck" feature
|
|
||||||
ingameClient.forceDisconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
onlineClients.put(accountId, client);
|
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) {
|
public boolean canStartLoginSession(IoSession session) {
|
||||||
if (!YamlConfig.config.server.DETERRED_MULTICLIENT) {
|
if (!YamlConfig.config.server.DETERRED_MULTICLIENT) {
|
||||||
return true;
|
return true;
|
||||||
@@ -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);
|
|
||||||
if (lrh != null) {
|
String nibbleHwid = (String) session.removeAttribute(MapleClient.CLIENT_NIBBLEHWID);
|
||||||
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;
|
||||||
|
} else if (onlineRemoteHwids.contains(nibbleHwid)) {
|
||||||
|
return AntiMulticlientResult.REMOTE_LOGGEDIN;
|
||||||
|
} else if (!attemptAccountAccess(accountId, nibbleHwid, routineCheck)) {
|
||||||
|
return AntiMulticlientResult.REMOTE_REACHED_LIMIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!routineCheck) {
|
session.setAttribute(MapleClient.CLIENT_NIBBLEHWID, nibbleHwid);
|
||||||
if (onlineRemoteHwids.contains(nibbleHwid)) {
|
onlineRemoteHwids.add(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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
@@ -341,9 +330,10 @@ public class MapleSessionCoordinator {
|
|||||||
|
|
||||||
hwid = (String) session.removeAttribute(MapleClient.CLIENT_HWID);
|
hwid = (String) session.removeAttribute(MapleClient.CLIENT_HWID);
|
||||||
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() {
|
||||||
|
|||||||
@@ -29,6 +29,6 @@ public class LoginCoordinatorTask implements Runnable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
MapleSessionCoordinator.getInstance().runUpdateHwidHistory();
|
MapleSessionCoordinator.getInstance().clearExpiredHwidHistory();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user