added rate limiting, added connections per ip limit

This commit is contained in:
LAX1DUDE 2022-08-13 20:00:26 -07:00
parent f26e1b8f6d
commit 7e3aee8699
5 changed files with 477 additions and 109 deletions

View File

@ -20,12 +20,14 @@ public class EaglerSPClient {
public final long createdOn;
public boolean serverNotifiedOfClose = false;
public LoginState state = LoginState.INIT;
public final String address;
EaglerSPClient(WebSocket sock, EaglerSPServer srv, String id) {
EaglerSPClient(WebSocket sock, EaglerSPServer srv, String id, String addr) {
this.socket = sock;
this.server = srv;
this.id = id;
this.createdOn = System.currentTimeMillis();
this.address = addr;
}
public void send(IPacket packet) {

View File

@ -18,6 +18,7 @@ import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;
import net.lax1dude.eaglercraft.sp.relay.RateLimiter.RateLimit;
import net.lax1dude.eaglercraft.sp.relay.pkt.*;
import net.lax1dude.eaglercraft.sp.relay.pkt.IPacket07LocalWorlds.LocalWorld;
@ -26,6 +27,9 @@ public class EaglerSPRelay extends WebSocketServer {
public static EaglerSPRelay instance;
public static final EaglerSPRelayConfig config = new EaglerSPRelayConfig();
private static RateLimiter pingRateLimiter = null;
private static RateLimiter worldRateLimiter = null;
public static final DebugLogger logger = DebugLogger.getLogger("EaglerSPRelay");
public static void main(String[] args) throws IOException, InterruptedException {
@ -35,9 +39,24 @@ public class EaglerSPRelay extends WebSocketServer {
logger.debug("Debug logging enabled");
}
}
logger.info("Starting EaglerSPRelay version {}...", Constants.versionName);
config.load(new File("relayConfig.ini"));
if(config.isPingRateLimitEnable()) {
pingRateLimiter = new RateLimiter(config.getPingRateLimitPeriod() * 1000,
config.getPingRateLimitLimit(), config.getPingRateLimitLockoutLimit(),
config.getPingRateLimitLockoutDuration() * 1000);
}
if(config.isWorldRateLimitEnable()) {
worldRateLimiter = new RateLimiter(config.getWorldRateLimitPeriod() * 1000,
config.getWorldRateLimitLimit(), config.getWorldRateLimitLockoutLimit(),
config.getWorldRateLimitLockoutDuration() * 1000);
}
EaglerSPRelayConfigRelayList.loadRelays(new File("relays.txt"));
logger.info("Starting WebSocket Server...");
instance = new EaglerSPRelay(new InetSocketAddress(config.getAddress(), config.getPort()));
instance.setConnectionLostTimeout(20);
@ -45,6 +64,7 @@ public class EaglerSPRelay extends WebSocketServer {
instance.start();
Thread tickThread = new Thread((() -> {
int rateLimitUpdateCounter = 0;
while(true) {
try {
long millis = System.currentTimeMillis();
@ -52,7 +72,7 @@ public class EaglerSPRelay extends WebSocketServer {
Iterator<Entry<WebSocket,PendingConnection>> itr = pendingConnections.entrySet().iterator();
while(itr.hasNext()) {
Entry<WebSocket,PendingConnection> etr = itr.next();
if(millis - etr.getValue().openTime > 1000l) {
if(millis - etr.getValue().openTime > 500l) {
etr.getKey().close();
itr.remove();
}
@ -62,16 +82,25 @@ public class EaglerSPRelay extends WebSocketServer {
Iterator<EaglerSPClient> itr = clientConnections.values().iterator();
while(itr.hasNext()) {
EaglerSPClient cl = itr.next();
if(millis - cl.createdOn > 500l) {
if(millis - cl.createdOn > 5000l) {
cl.disconnect(IPacketFEDisconnectClient.TYPE_TIMEOUT, "Took too long to connect!");
}
}
}
if(++rateLimitUpdateCounter > 300) {
if(pingRateLimiter != null) {
pingRateLimiter.update();
}
if(worldRateLimiter != null) {
worldRateLimiter.update();
}
rateLimitUpdateCounter = 0;
}
}catch(Throwable t) {
logger.error("Error in update loop!");
logger.error(t);
}
Util.sleep(50l);
Util.sleep(100l);
}
}), "Relay Tick");
tickThread.setDaemon(true);
@ -114,6 +143,7 @@ public class EaglerSPRelay extends WebSocketServer {
private static final Map<WebSocket,EaglerSPClient> clientConnections = new HashMap();
private static final Map<String,EaglerSPServer> serverCodes = new HashMap();
private static final Map<WebSocket,EaglerSPServer> serverConnections = new HashMap();
private static final Map<String,List<EaglerSPClient>> clientAddressSets = new HashMap();
private static final Map<String,List<EaglerSPServer>> serverAddressSets = new HashMap();
@Override
@ -131,7 +161,32 @@ public class EaglerSPRelay extends WebSocketServer {
}else {
addr = arg0.getRemoteSocketAddress().getAddress().getHostAddress().toLowerCase();
}
int totalCons = 0;
synchronized(pendingConnections) {
Iterator<PendingConnection> pendingItr = pendingConnections.values().iterator();
while(pendingItr.hasNext()) {
if(pendingItr.next().address.equals(addr)) {
++totalCons;
}
}
}
synchronized(clientAddressSets) {
List<EaglerSPClient> lst = clientAddressSets.get(addr);
if(lst != null) {
totalCons += lst.size();
}
}
if(totalCons >= config.getConnectionsPerIP()) {
logger.debug("[{}]: Too many connections are open on this address", arg0.getAttachment());
arg0.send(IPacketFEDisconnectClient.ratelimitPacketTooMany);
arg0.close();
return;
}
arg0.setAttachment(addr);
PendingConnection waiting = new PendingConnection(millis, addr);
logger.debug("[{}]: Connection opened", arg0.getRemoteSocketAddress());
synchronized(pendingConnections) {
@ -165,6 +220,21 @@ public class EaglerSPRelay extends WebSocketServer {
return;
}
if(ipkt.connectionType == 0x01) {
if(!rateLimit(worldRateLimiter, arg0, waiting.address)) {
logger.debug("[{}]: Got world ratelimited", arg0.getAttachment());
return;
}
synchronized(serverAddressSets) {
List<EaglerSPServer> lst = serverAddressSets.get(waiting.address);
if(lst != null) {
if(lst.size() >= config.getWorldsPerIP()) {
logger.debug("[{}]: Too many worlds are open on this address", arg0.getAttachment());
arg0.send(IPacketFEDisconnectClient.ratelimitPacketTooMany);
arg0.close();
return;
}
}
}
logger.debug("[{}]: Connected as a server", arg0.getAttachment());
EaglerSPServer srv;
synchronized(serverCodes) {
@ -200,57 +270,71 @@ public class EaglerSPRelay extends WebSocketServer {
}
srv.send(new IPacket01ICEServers(EaglerSPRelayConfigRelayList.relayServers));
logger.debug("[{}][Relay -> Server] PKT 0x01: Send ICE server list to server", arg0.getAttachment());
}else if(ipkt.connectionType == 0x02) {
String code = ipkt.connectionCode;
logger.debug("[{}]: Connected as a client, requested server code: {}", arg0.getAttachment(), code);
if(code.length() != config.getCodeLength()) {
logger.debug("The code '{}' is invalid because it's the wrong length, disconnecting");
arg0.send(IPacket.writePacket(new IPacketFFErrorCode(IPacketFFErrorCode.TYPE_CODE_LENGTH,
"The join code is the wrong length, it should be " + config.getCodeLength() + " chars long")));
}else {
if(!rateLimit(pingRateLimiter, arg0, waiting.address)) {
logger.debug("[{}]: Got ping ratelimited", arg0.getAttachment());
return;
}
if(ipkt.connectionType == 0x02) {
String code = ipkt.connectionCode;
logger.debug("[{}]: Connected as a client, requested server code: {}", arg0.getAttachment(), code);
if(code.length() != config.getCodeLength()) {
logger.debug("The code '{}' is invalid because it's the wrong length, disconnecting");
arg0.send(IPacket.writePacket(new IPacketFFErrorCode(IPacketFFErrorCode.TYPE_CODE_LENGTH,
"The join code is the wrong length, it should be " + config.getCodeLength() + " chars long")));
arg0.close();
}else {
EaglerSPServer srv;
synchronized(serverCodes) {
srv = serverCodes.get(code);
}
if(srv == null) {
arg0.send(IPacket.writePacket(new IPacketFFErrorCode(IPacketFFErrorCode.TYPE_INCORRECT_CODE,
"Invalid code, no LAN world found!")));
arg0.close();
return;
}
String id;
EaglerSPClient cl;
synchronized(clientIds) {
int j = 0;
do {
id = EaglerSPClient.generateClientId();
}while(clientIds.containsKey(id));
cl = new EaglerSPClient(arg0, srv, id, waiting.address);
clientIds.put(id, cl);
ipkt.connectionCode = id;
arg0.send(IPacket.writePacket(ipkt));
srv.handleNewClient(cl);
}
synchronized(clientConnections) {
clientConnections.put(arg0, cl);
}
synchronized(clientAddressSets) {
List<EaglerSPClient> lst = clientAddressSets.get(cl.address);
if(lst == null) {
lst = new ArrayList();
clientAddressSets.put(cl.address, lst);
}
lst.add(cl);
}
cl.send(new IPacket01ICEServers(EaglerSPRelayConfigRelayList.relayServers));
logger.debug("[{}][Relay -> Client] PKT 0x01: Send ICE server list to client", arg0.getAttachment());
}
}else if(ipkt.connectionType == 0x02) {
logger.debug("[{}]: Pinging the server", arg0.getAttachment());
arg0.send(IPacket.writePacket(new IPacket69Pong(Constants.protocolVersion, config.getComment(), Constants.versionBrand)));
arg0.close();
}else if(ipkt.connectionType == 0x03) {
logger.debug("[{}]: Polling the server for other worlds", arg0.getAttachment());
arg0.send(IPacket.writePacket(new IPacket07LocalWorlds(getLocalWorlds(waiting.address))));
arg0.close();
}else {
EaglerSPServer srv;
synchronized(serverCodes) {
srv = serverCodes.get(code);
}
if(srv == null) {
arg0.send(IPacket.writePacket(new IPacketFFErrorCode(IPacketFFErrorCode.TYPE_INCORRECT_CODE,
"Invalid code, no LAN world found!")));
arg0.close();
return;
}
String id;
EaglerSPClient cl;
synchronized(clientIds) {
int j = 0;
do {
id = EaglerSPClient.generateClientId();
}while(clientIds.containsKey(id));
cl = new EaglerSPClient(arg0, srv, id);
clientIds.put(id, cl);
ipkt.connectionCode = id;
arg0.send(IPacket.writePacket(ipkt));
srv.handleNewClient(cl);
}
synchronized(clientConnections) {
clientConnections.put(arg0, cl);
}
cl.send(new IPacket01ICEServers(EaglerSPRelayConfigRelayList.relayServers));
logger.debug("[{}][Relay -> Client] PKT 0x01: Send ICE server list to client", arg0.getAttachment());
logger.debug("[{}]: Unknown connection type: {}", arg0.getAttachment(), ipkt.connectionType);
arg0.send(IPacket.writePacket(new IPacketFFErrorCode(IPacketFFErrorCode.TYPE_ILLEGAL_OPERATION,
"Unexpected Init Packet")));
arg0.close();
}
}else if(ipkt.connectionType == 0x02) {
logger.debug("[{}]: Pinging the server", arg0.getAttachment());
arg0.send(IPacket.writePacket(new IPacket69Pong(Constants.protocolVersion, config.getComment(), Constants.versionBrand)));
arg0.close();
}else if(ipkt.connectionType == 0x03) {
logger.debug("[{}]: Polling the server for other worlds", arg0.getAttachment());
arg0.send(IPacket.writePacket(new IPacket07LocalWorlds(getLocalWorlds(waiting.address))));
arg0.close();
}else {
logger.debug("[{}]: Unknown connection type: {}", arg0.getAttachment(), ipkt.connectionType);
arg0.send(IPacket.writePacket(new IPacketFFErrorCode(IPacketFFErrorCode.TYPE_ILLEGAL_OPERATION,
"Unexpected Init Packet")));
arg0.close();
}
}else {
logger.debug("[{}]: Pending connection did not send a 0x00 packet to identify "
@ -314,6 +398,15 @@ public class EaglerSPRelay extends WebSocketServer {
synchronized(serverCodes) {
serverCodes.remove(srv.code);
}
synchronized(serverAddressSets) {
List<EaglerSPServer> lst = serverAddressSets.get(srv.serverAddress);
if(lst != null) {
lst.remove(srv);
if(lst.size() == 0) {
serverAddressSets.remove(srv.serverAddress);
}
}
}
ArrayList<EaglerSPClient> clientList;
synchronized(clientConnections) {
clientList = new ArrayList(clientConnections.values());
@ -326,18 +419,20 @@ public class EaglerSPRelay extends WebSocketServer {
cl.socket.close();
}
}
synchronized(serverAddressSets) {
List<EaglerSPServer> lst = serverAddressSets.get(srv.serverAddress);
lst.remove(srv);
if(lst.size() == 0) {
serverAddressSets.remove(srv.serverAddress);
}
}
}else {
EaglerSPClient cl;
synchronized(clientConnections) {
cl = clientConnections.remove(arg0);
}
synchronized(clientAddressSets) {
List<EaglerSPClient> lst = clientAddressSets.get(cl.address);
if(lst != null) {
lst.remove(cl);
if(lst.size() == 0) {
clientAddressSets.remove(cl.address);
}
}
}
if(cl != null) {
logger.debug("[{}]: Client closed, id: {}", arg0.getAttachment(), cl.id);
synchronized(clientIds) {
@ -375,5 +470,30 @@ public class EaglerSPRelay extends WebSocketServer {
}
return lst;
}
private boolean rateLimit(RateLimiter limiter, WebSocket sock, String addr) {
if(limiter != null) {
RateLimit l = limiter.limit(addr);
if(l == RateLimit.NONE) {
return true;
}else if(l == RateLimit.LIMIT) {
sock.send(IPacketFEDisconnectClient.ratelimitPacketBlock);
sock.close();
return false;
}else if(l == RateLimit.LIMIT_NOW_LOCKOUT) {
sock.send(IPacketFEDisconnectClient.ratelimitPacketBlockLock);
sock.close();
return false;
}else if(l == RateLimit.LOCKOUT) {
sock.send(IPacketFEDisconnectClient.ratelimitPacketLocked);
sock.close();
return false;
}else {
return true; // ?
}
}else {
return true;
}
}
}

View File

@ -17,12 +17,22 @@ public class EaglerSPRelayConfig {
private int codeLength = 5;
private String codeChars = "abcdefghijklmnopqrstuvwxyz0123456789$%&*+?!";
private boolean codeMixCase = false;
private int connectionsPerIP = 256;
private boolean rateLimitEnable = false;
private int rateLimitPeriod = 128;
private int rateLimitLimit = 48;
private int rateLimitLockoutLimit = 64;
private int rateLimitLockoutDuration = 600;
private int connectionsPerIP = 128;
private int worldsPerIP = 32;
private boolean openRateLimitEnable = true;
private int openRateLimitPeriod = 192;
private int openRateLimitLimit = 32;
private int openRateLimitLockoutLimit = 48;
private int openRateLimitLockoutDuration = 600;
private boolean pingRateLimitEnable = true;
private int pingRateLimitPeriod = 256;
private int pingRateLimitLimit = 128;
private int pingRateLimitLockoutLimit = 192;
private int pingRateLimitLockoutDuration = 300;
private String originWhitelist = "";
private String[] originWhitelistArray = new String[0];
private boolean enableRealIpHeader = false;
@ -35,11 +45,17 @@ public class EaglerSPRelayConfig {
}else {
EaglerSPRelay.logger.info("Loading config file: {}", conf.getAbsoluteFile());
boolean gotPort = false, gotCodeLength = false, gotCodeChars = false;
boolean gotCodeMixCase = false, gotConnectionsPerIP = false;
boolean gotRateLimitEnable = false, gotRateLimitPeriod = false;
boolean gotRateLimitLimit = false, gotRateLimitLockoutLimit = false;
boolean gotRateLimitLockoutDuration = false, gotOriginWhitelist = false;
boolean gotEnableRealIpHeader = false, gotAddress = false, gotComment = false;
boolean gotCodeMixCase = false;
boolean gotConnectionsPerIP = false, gotWorldsPerIP = false,
gotOpenRateLimitEnable = false, gotOpenRateLimitPeriod = false,
gotOpenRateLimitLimit = false, gotOpenRateLimitLockoutLimit = false,
gotOpenRateLimitLockoutDuration = false;
boolean gotPingRateLimitEnable = false, gotPingRateLimitPeriod = false,
gotPingRateLimitLimit = false, gotPingRateLimitLockoutLimit = false,
gotPingRateLimitLockoutDuration = false;
boolean gotOriginWhitelist = false, gotEnableRealIpHeader = false,
gotAddress = false, gotComment = false;
Throwable t2 = null;
try(BufferedReader reader = new BufferedReader(new FileReader(conf))) {
String s;
@ -86,6 +102,66 @@ public class EaglerSPRelayConfig {
t2 = t;
break;
}
}else if(ss[0].equalsIgnoreCase("worlds-per-ip")) {
try {
worldsPerIP = Integer.parseInt(ss[1]);
gotWorldsPerIP = true;
}catch(Throwable t) {
EaglerSPRelay.logger.warn("Invalid worlds-per-ip {} in conf {}", ss[1], conf.getAbsoluteFile());
EaglerSPRelay.logger.warn(t);
t2 = t;
break;
}
}else if(ss[0].equalsIgnoreCase("world-ratelimit-enable")) {
try {
openRateLimitEnable = getBooleanValue(ss[1]);
gotOpenRateLimitEnable = true;
}catch(Throwable t) {
EaglerSPRelay.logger.warn("Invalid world-ratelimit-enable {} in conf {}", ss[1], conf.getAbsoluteFile());
EaglerSPRelay.logger.warn(t);
t2 = t;
break;
}
}else if(ss[0].equalsIgnoreCase("world-ratelimit-period")) {
try {
openRateLimitPeriod = Integer.parseInt(ss[1]);
gotOpenRateLimitPeriod = true;
}catch(Throwable t) {
EaglerSPRelay.logger.warn("Invalid world-ratelimit-period {} in conf {}", ss[1], conf.getAbsoluteFile());
EaglerSPRelay.logger.warn(t);
t2 = t;
break;
}
}else if(ss[0].equalsIgnoreCase("world-ratelimit-limit")) {
try {
openRateLimitLimit = Integer.parseInt(ss[1]);
gotOpenRateLimitLimit = true;
}catch(Throwable t) {
EaglerSPRelay.logger.warn("Invalid world-ratelimit-limit {} in conf {}", ss[1], conf.getAbsoluteFile());
EaglerSPRelay.logger.warn(t);
t2 = t;
break;
}
}else if(ss[0].equalsIgnoreCase("world-ratelimit-lockout-limit")) {
try {
openRateLimitLockoutLimit = Integer.parseInt(ss[1]);
gotOpenRateLimitLockoutLimit = true;
}catch(Throwable t) {
EaglerSPRelay.logger.warn("Invalid world-ratelimit-lockout-limit {} in conf {}", ss[1], conf.getAbsoluteFile());
EaglerSPRelay.logger.warn(t);
t2 = t;
break;
}
}else if(ss[0].equalsIgnoreCase("world-ratelimit-lockout-duration")) {
try {
openRateLimitLockoutDuration = Integer.parseInt(ss[1]);
gotOpenRateLimitLockoutDuration = true;
}catch(Throwable t) {
EaglerSPRelay.logger.warn("Invalid world-ratelimit-lockout-duration {} in conf {}", ss[1], conf.getAbsoluteFile());
EaglerSPRelay.logger.warn(t);
t2 = t;
break;
}
}else if(ss[0].equalsIgnoreCase("connections-per-ip")) {
try {
connectionsPerIP = Integer.parseInt(ss[1]);
@ -96,52 +172,52 @@ public class EaglerSPRelayConfig {
t2 = t;
break;
}
}else if(ss[0].equalsIgnoreCase("ratelimit-enable")) {
}else if(ss[0].equalsIgnoreCase("ping-ratelimit-enable")) {
try {
rateLimitEnable = getBooleanValue(ss[1]);
gotRateLimitEnable = true;
pingRateLimitEnable = getBooleanValue(ss[1]);
gotPingRateLimitEnable = true;
}catch(Throwable t) {
EaglerSPRelay.logger.warn("Invalid rate-limit-enable {} in conf {}", ss[1], conf.getAbsoluteFile());
EaglerSPRelay.logger.warn("Invalid ping-ratelimit-enable {} in conf {}", ss[1], conf.getAbsoluteFile());
EaglerSPRelay.logger.warn(t);
t2 = t;
break;
}
}else if(ss[0].equalsIgnoreCase("ratelimit-period")) {
}else if(ss[0].equalsIgnoreCase("ping-ratelimit-period")) {
try {
rateLimitPeriod = Integer.parseInt(ss[1]);
gotRateLimitPeriod = true;
pingRateLimitPeriod = Integer.parseInt(ss[1]);
gotPingRateLimitPeriod = true;
}catch(Throwable t) {
EaglerSPRelay.logger.warn("Invalid ratelimit-period {} in conf {}", ss[1], conf.getAbsoluteFile());
EaglerSPRelay.logger.warn("Invalid ping-ratelimit-period {} in conf {}", ss[1], conf.getAbsoluteFile());
EaglerSPRelay.logger.warn(t);
t2 = t;
break;
}
}else if(ss[0].equalsIgnoreCase("ratelimit-limit")) {
}else if(ss[0].equalsIgnoreCase("ping-ratelimit-limit")) {
try {
rateLimitLimit = Integer.parseInt(ss[1]);
gotRateLimitLimit = true;
pingRateLimitLimit = Integer.parseInt(ss[1]);
gotPingRateLimitLimit = true;
}catch(Throwable t) {
EaglerSPRelay.logger.warn("Invalid ratelimit-limit {} in conf {}", ss[1], conf.getAbsoluteFile());
EaglerSPRelay.logger.warn("Invalid ping-ratelimit-limit {} in conf {}", ss[1], conf.getAbsoluteFile());
EaglerSPRelay.logger.warn(t);
t2 = t;
break;
}
}else if(ss[0].equalsIgnoreCase("ratelimit-lockout-limit")) {
}else if(ss[0].equalsIgnoreCase("ping-ratelimit-lockout-limit")) {
try {
rateLimitLockoutLimit = Integer.parseInt(ss[1]);
gotRateLimitLockoutLimit = true;
pingRateLimitLockoutLimit = Integer.parseInt(ss[1]);
gotPingRateLimitLockoutLimit = true;
}catch(Throwable t) {
EaglerSPRelay.logger.warn("Invalid ratelimit-lockout-limit {} in conf {}", ss[1], conf.getAbsoluteFile());
EaglerSPRelay.logger.warn("Invalid ping-ratelimit-lockout-limit {} in conf {}", ss[1], conf.getAbsoluteFile());
EaglerSPRelay.logger.warn(t);
t2 = t;
break;
}
}else if(ss[0].equalsIgnoreCase("ratelimit-lockout-duration")) {
}else if(ss[0].equalsIgnoreCase("ping-ratelimit-lockout-duration")) {
try {
rateLimitLockoutDuration = Integer.parseInt(ss[1]);
gotRateLimitLockoutDuration = true;
pingRateLimitLockoutDuration = Integer.parseInt(ss[1]);
gotPingRateLimitLockoutDuration = true;
}catch(Throwable t) {
EaglerSPRelay.logger.warn("Invalid ratelimit-lockout-duration {} in conf {}", ss[1], conf.getAbsoluteFile());
EaglerSPRelay.logger.warn("Invalid ping-ratelimit-lockout-duration {} in conf {}", ss[1], conf.getAbsoluteFile());
EaglerSPRelay.logger.warn(t);
t2 = t;
break;
@ -174,10 +250,14 @@ public class EaglerSPRelayConfig {
t2 = t;
}
if(t2 != null || !gotPort || !gotCodeLength || !gotCodeChars ||
!gotCodeMixCase || !gotConnectionsPerIP || !gotRateLimitEnable ||
!gotRateLimitPeriod || !gotRateLimitLimit || !gotRateLimitLockoutLimit ||
!gotRateLimitLockoutDuration || !gotOriginWhitelist ||
!gotEnableRealIpHeader || !gotAddress || !gotComment) {
!gotCodeMixCase || !gotWorldsPerIP || !gotOpenRateLimitEnable ||
!gotOpenRateLimitPeriod || !gotOpenRateLimitLimit ||
!gotOpenRateLimitLockoutLimit || !gotOpenRateLimitLockoutDuration ||
!gotConnectionsPerIP || !gotPingRateLimitEnable ||
!gotPingRateLimitPeriod || !gotPingRateLimitLimit ||
!gotPingRateLimitLockoutLimit || !gotPingRateLimitLockoutDuration ||
!gotOriginWhitelist || !gotEnableRealIpHeader || !gotAddress ||
!gotComment) {
EaglerSPRelay.logger.warn("Updating config file: {}", conf.getAbsoluteFile());
save(conf);
}
@ -204,11 +284,17 @@ public class EaglerSPRelayConfig {
w.println("code-chars=" + codeChars);
w.println("code-mix-case=" + codeMixCase);
w.println("connections-per-ip=" + connectionsPerIP);
w.println("ratelimit-enable=" + rateLimitEnable);
w.println("ratelimit-period=" + rateLimitPeriod);
w.println("ratelimit-limit=" + rateLimitLimit);
w.println("ratelimit-lockout-limit=" + rateLimitLockoutLimit);
w.println("ratelimit-lockout-duration=" + rateLimitLockoutDuration);
w.println("ping-ratelimit-enable=" + pingRateLimitEnable);
w.println("ping-ratelimit-period=" + pingRateLimitPeriod);
w.println("ping-ratelimit-limit=" + pingRateLimitLimit);
w.println("ping-ratelimit-lockout-limit=" + pingRateLimitLockoutLimit);
w.println("ping-ratelimit-lockout-duration=" + pingRateLimitLockoutDuration);
w.println("worlds-per-ip=" + worldsPerIP);
w.println("world-ratelimit-enable=" + openRateLimitEnable);
w.println("world-ratelimit-period=" + openRateLimitPeriod);
w.println("world-ratelimit-limit=" + openRateLimitLimit);
w.println("world-ratelimit-lockout-limit=" + openRateLimitLockoutLimit);
w.println("world-ratelimit-lockout-duration=" + openRateLimitLockoutDuration);
w.println("origin-whitelist=" + originWhitelist);
w.println("enable-real-ip-header=" + enableRealIpHeader);
w.print("server-comment=" + serverComment);
@ -252,24 +338,48 @@ public class EaglerSPRelayConfig {
return connectionsPerIP;
}
public boolean isRateLimitEnable() {
return rateLimitEnable;
public boolean isPingRateLimitEnable() {
return pingRateLimitEnable;
}
public int getRateLimitPeriod() {
return rateLimitPeriod;
public int getPingRateLimitPeriod() {
return pingRateLimitPeriod;
}
public int getRateLimitLimit() {
return rateLimitLimit;
public int getPingRateLimitLimit() {
return pingRateLimitLimit;
}
public int getRateLimitLockoutLimit() {
return rateLimitLockoutLimit;
public int getPingRateLimitLockoutLimit() {
return pingRateLimitLockoutLimit;
}
public int getRateLimitLockoutDuration() {
return rateLimitLockoutDuration;
public int getPingRateLimitLockoutDuration() {
return pingRateLimitLockoutDuration;
}
public int getWorldsPerIP() {
return worldsPerIP;
}
public boolean isWorldRateLimitEnable() {
return openRateLimitEnable;
}
public int getWorldRateLimitPeriod() {
return openRateLimitPeriod;
}
public int getWorldRateLimitLimit() {
return openRateLimitLimit;
}
public int getWorldRateLimitLockoutLimit() {
return openRateLimitLockoutLimit;
}
public int getWorldRateLimitLockoutDuration() {
return openRateLimitLockoutDuration;
}
public String getOriginWhitelist() {
@ -279,6 +389,23 @@ public class EaglerSPRelayConfig {
public String[] getOriginWhitelistArray() {
return originWhitelistArray;
}
public boolean getIsWhitelisted(String domain) {
domain = domain.toLowerCase();
for(int i = 0; i < originWhitelistArray.length; ++i) {
String etr = originWhitelistArray[i].toLowerCase();
if(etr.startsWith("*")) {
if(domain.endsWith(etr.substring(1))) {
return true;
}
}else {
if(domain.equals(etr)) {
return true;
}
}
}
return false;
}
public boolean isEnableRealIpHeader() {
return enableRealIpHeader;

View File

@ -0,0 +1,113 @@
package net.lax1dude.eaglercraft.sp.relay;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class RateLimiter {
private final int period;
private final int limit;
private final int lockoutLimit;
private final int lockoutDuration;
private class RateLimitEntry {
protected long timer;
protected int count;
protected long lockedTimer;
protected boolean locked;
protected RateLimitEntry() {
timer = System.currentTimeMillis();
count = 0;
lockedTimer = 0l;
locked = false;
}
protected void update() {
long millis = System.currentTimeMillis();
if(locked) {
if(millis - lockedTimer > RateLimiter.this.lockoutDuration) {
timer = millis;
count = 0;
lockedTimer = 0l;
locked = false;
}
}else {
long p = RateLimiter.this.period;
int breaker = 0;
while(millis - timer > p) {
timer += p;
--count;
if(count < 0 || ++breaker > 100) {
timer = millis;
count = 0;
break;
}
}
}
}
}
public static enum RateLimit {
NONE, LIMIT, LIMIT_NOW_LOCKOUT, LOCKOUT;
}
private final Map<String, RateLimitEntry> limiters = new HashMap();
public RateLimiter(int period, int limit, int lockoutLimit, int lockoutDuration) {
this.period = period;
this.limit = limit;
this.lockoutLimit = lockoutLimit;
this.lockoutDuration = lockoutDuration;
}
public RateLimit limit(String addr) {
synchronized(this) {
RateLimitEntry etr = limiters.get(addr);
if(etr == null) {
etr = new RateLimitEntry();
limiters.put(addr, etr);
}else {
etr.update();
}
if(etr.locked) {
return RateLimit.LOCKOUT;
}
++etr.count;
if(etr.count >= lockoutLimit) {
etr.count = 0;
etr.locked = true;
etr.lockedTimer = System.currentTimeMillis();
return RateLimit.LIMIT_NOW_LOCKOUT;
}else if(etr.count > limit) {
return RateLimit.LIMIT;
}else {
return RateLimit.NONE;
}
}
}
public void update() {
synchronized(this) {
Iterator<RateLimitEntry> itr = limiters.values().iterator();
while(itr.hasNext()) {
if(itr.next().count == 0) {
itr.remove();
}
}
}
}
public void reset() {
synchronized(this) {
limiters.clear();
}
}
}

View File

@ -3,6 +3,7 @@ package net.lax1dude.eaglercraft.sp.relay.pkt;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
public class IPacketFEDisconnectClient extends IPacket {
@ -41,5 +42,10 @@ public class IPacketFEDisconnectClient extends IPacket {
public int packetLength() {
return -1;
}
public static final ByteBuffer ratelimitPacketTooMany = ByteBuffer.wrap(new byte[] { (byte)0xFC, (byte)0x00 });
public static final ByteBuffer ratelimitPacketBlock = ByteBuffer.wrap(new byte[] { (byte)0xFC, (byte)0x01 });
public static final ByteBuffer ratelimitPacketBlockLock = ByteBuffer.wrap(new byte[] { (byte)0xFC, (byte)0x02 });
public static final ByteBuffer ratelimitPacketLocked = ByteBuffer.wrap(new byte[] { (byte)0xFC, (byte)0x03 });
}