Merge pull request #164 from UnfamiliarLegacy/fix/habbox

Improve Nitro reliability and bypass cloudflare challenge on french retro
This commit is contained in:
sirjonasxx 2023-06-16 07:06:46 +02:00 committed by GitHub
commit ac9e802d50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 154 additions and 10 deletions

View File

@ -0,0 +1,66 @@
package gearth.protocol.connection.proxy.nitro;
import gearth.protocol.HMessage;
import gearth.protocol.connection.HState;
import gearth.protocol.connection.HStateSetter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class NitroConnectionState {
private static final Logger logger = LoggerFactory.getLogger(NitroConnectionState.class);
private final HStateSetter stateSetter;
private boolean aborting;
private boolean toServer;
private boolean toClient;
public NitroConnectionState(HStateSetter stateSetter) {
this.stateSetter = stateSetter;
}
public void setConnected(HMessage.Direction direction) {
if (direction == HMessage.Direction.TOCLIENT) {
this.toClient = true;
} else if (direction == HMessage.Direction.TOSERVER) {
this.toServer = true;
}
this.checkConnected();
}
public boolean isConnected() {
if (this.aborting) {
return false;
}
if (!this.toClient) {
return false;
}
if (!this.toServer) {
return false;
}
return true;
}
private void checkConnected() {
if (!this.isConnected()) {
return;
}
this.stateSetter.setState(HState.CONNECTED);
logger.info("Connected");
}
public void setAborting() {
this.aborting = true;
this.stateSetter.setState(HState.ABORTING);
logger.info("Aborting");
}
}

View File

@ -0,0 +1,28 @@
package gearth.protocol.connection.proxy.nitro;
import gearth.protocol.packethandler.nitro.NitroPacketHandler;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Queue;
public class NitroPacketQueue {
private final NitroPacketHandler packetHandler;
private final Queue<byte[]> packets;
public NitroPacketQueue(NitroPacketHandler packetHandler) {
this.packetHandler = packetHandler;
this.packets = new LinkedList<>();
}
public void enqueue(byte[] b) {
this.packets.add(b);
}
public synchronized void flush() throws IOException {
while (!this.packets.isEmpty()) {
this.packetHandler.act(this.packets.remove());
}
}
}

View File

@ -2,8 +2,11 @@ package gearth.protocol.connection.proxy.nitro.websocket;
import gearth.protocol.HConnection; import gearth.protocol.HConnection;
import gearth.protocol.HMessage; import gearth.protocol.HMessage;
import gearth.protocol.StateChangeListener;
import gearth.protocol.connection.*; import gearth.protocol.connection.*;
import gearth.protocol.connection.proxy.nitro.NitroConnectionState;
import gearth.protocol.connection.proxy.nitro.NitroConstants; import gearth.protocol.connection.proxy.nitro.NitroConstants;
import gearth.protocol.connection.proxy.nitro.NitroPacketQueue;
import gearth.protocol.connection.proxy.nitro.NitroProxyProvider; import gearth.protocol.connection.proxy.nitro.NitroProxyProvider;
import gearth.protocol.packethandler.nitro.NitroPacketHandler; import gearth.protocol.packethandler.nitro.NitroPacketHandler;
import org.eclipse.jetty.websocket.jsr356.JsrSession; import org.eclipse.jetty.websocket.jsr356.JsrSession;
@ -25,22 +28,24 @@ public class NitroWebsocketClient implements NitroSession {
private static final Logger logger = LoggerFactory.getLogger(NitroWebsocketClient.class); private static final Logger logger = LoggerFactory.getLogger(NitroWebsocketClient.class);
private final HProxySetter proxySetter; private final HProxySetter proxySetter;
private final HStateSetter stateSetter;
private final HConnection connection; private final HConnection connection;
private final NitroConnectionState state;
private final NitroProxyProvider proxyProvider; private final NitroProxyProvider proxyProvider;
private final NitroWebsocketServer server; private final NitroWebsocketServer server;
private final NitroPacketHandler packetHandler; private final NitroPacketHandler packetHandler;
private final NitroPacketQueue packetQueue;
private final AtomicBoolean shutdownLock; private final AtomicBoolean shutdownLock;
private JsrSession activeSession = null; private JsrSession activeSession = null;
public NitroWebsocketClient(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection, NitroProxyProvider proxyProvider) { public NitroWebsocketClient(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection, NitroProxyProvider proxyProvider) {
this.proxySetter = proxySetter; this.proxySetter = proxySetter;
this.stateSetter = stateSetter;
this.connection = connection; this.connection = connection;
this.state = new NitroConnectionState(stateSetter);
this.proxyProvider = proxyProvider; this.proxyProvider = proxyProvider;
this.server = new NitroWebsocketServer(connection, this); this.server = new NitroWebsocketServer(connection, this, this.state);
this.packetHandler = new NitroPacketHandler(HMessage.Direction.TOSERVER, server, connection.getExtensionHandler(), connection.getTrafficObservables()); this.packetHandler = new NitroPacketHandler(HMessage.Direction.TOSERVER, server, connection.getExtensionHandler(), connection.getTrafficObservables());
this.packetQueue = new NitroPacketQueue(this.packetHandler);
this.shutdownLock = new AtomicBoolean(); this.shutdownLock = new AtomicBoolean();
} }
@ -48,6 +53,24 @@ public class NitroWebsocketClient implements NitroSession {
public void onOpen(Session session) throws Exception { public void onOpen(Session session) throws Exception {
logger.info("WebSocket connection accepted"); logger.info("WebSocket connection accepted");
// Setup state change listener
connection.getStateObservable().addListener(new StateChangeListener() {
@Override
public void stateChanged(HState oldState, HState newState) {
// Clean up when we don't need it anymore.
if ((oldState == HState.WAITING_FOR_CLIENT || newState == HState.NOT_CONNECTED) || newState == HState.ABORTING) {
connection.getStateObservable().removeListener(this);
}
// Process queue when connected.
try {
packetQueue.flush();
} catch (IOException e) {
logger.error("Failed to flush packet queue in state change listener", e);
}
}
});
activeSession = (JsrSession) session; activeSession = (JsrSession) session;
activeSession.setMaxBinaryMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE); activeSession.setMaxBinaryMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE);
activeSession.setMaxTextMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE); activeSession.setMaxTextMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE);
@ -72,12 +95,21 @@ public class NitroWebsocketClient implements NitroSession {
); );
proxySetter.setProxy(proxy); proxySetter.setProxy(proxy);
stateSetter.setState(HState.CONNECTED); state.setConnected(HMessage.Direction.TOSERVER);
} }
@OnMessage @OnMessage
public void onMessage(byte[] b, Session session) throws IOException { public void onMessage(byte[] b, Session session) throws IOException {
packetHandler.act(b); logger.debug("Received packet from browser");
// Enqueue all packets we receive to ensure we preserve correct packet order.
packetQueue.enqueue(b);
// Flush everything if we are connected.
// We also flush when connection state changes to connected.
if (state.isConnected()) {
packetQueue.flush();
}
} }
@OnClose @OnClose
@ -133,7 +165,7 @@ public class NitroWebsocketClient implements NitroSession {
// Reset program state. // Reset program state.
proxySetter.setProxy(null); proxySetter.setProxy(null);
stateSetter.setState(HState.ABORTING); state.setAborting();
} }
} }
} }

View File

@ -2,10 +2,13 @@ package gearth.protocol.connection.proxy.nitro.websocket;
import gearth.protocol.HConnection; import gearth.protocol.HConnection;
import gearth.protocol.HMessage; import gearth.protocol.HMessage;
import gearth.protocol.connection.proxy.nitro.NitroConnectionState;
import gearth.protocol.connection.proxy.nitro.NitroConstants; import gearth.protocol.connection.proxy.nitro.NitroConstants;
import gearth.protocol.packethandler.PacketHandler; import gearth.protocol.packethandler.PacketHandler;
import gearth.protocol.packethandler.nitro.NitroPacketHandler; import gearth.protocol.packethandler.nitro.NitroPacketHandler;
import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketListener; import org.eclipse.jetty.websocket.api.WebSocketListener;
@ -36,15 +39,21 @@ public class NitroWebsocketServer implements WebSocketListener, NitroSession {
"Sec-WebSocket-Version", "Sec-WebSocket-Version",
"Host", "Host",
"Connection", "Connection",
"Upgrade" "Upgrade",
"User-Agent", // Added by default
"Accept-Encoding", // Added by default
"Cache-Control", // Added by default
"Pragma" // Added by default
)); ));
private final PacketHandler packetHandler; private final PacketHandler packetHandler;
private final NitroWebsocketClient client; private final NitroWebsocketClient client;
private final NitroConnectionState state;
private Session activeSession = null; private Session activeSession = null;
public NitroWebsocketServer(HConnection connection, NitroWebsocketClient client) { public NitroWebsocketServer(HConnection connection, NitroWebsocketClient client, NitroConnectionState state) {
this.client = client; this.client = client;
this.state = state;
this.packetHandler = new NitroPacketHandler(HMessage.Direction.TOCLIENT, client, connection.getExtensionHandler(), connection.getTrafficObservables()); this.packetHandler = new NitroPacketHandler(HMessage.Direction.TOCLIENT, client, connection.getExtensionHandler(), connection.getTrafficObservables());
} }
@ -56,6 +65,8 @@ public class NitroWebsocketServer implements WebSocketListener, NitroSession {
final ClientUpgradeRequest request = new ClientUpgradeRequest(); final ClientUpgradeRequest request = new ClientUpgradeRequest();
request.addExtensions("permessage-deflate");
clientHeaders.forEach((key, value) -> { clientHeaders.forEach((key, value) -> {
if (SKIP_HEADERS.contains(key)) { if (SKIP_HEADERS.contains(key)) {
return; return;
@ -64,12 +75,17 @@ public class NitroWebsocketServer implements WebSocketListener, NitroSession {
request.setHeader(key, value); request.setHeader(key, value);
}); });
if (clientHeaders.containsKey("User-Agent")) {
final String realUserAgent = clientHeaders.get(HttpHeader.USER_AGENT.toString()).get(0);
final HttpField clientUserAgent = new HttpField(HttpHeader.USER_AGENT, realUserAgent);
client.getHttpClient().setUserAgentField(clientUserAgent);
}
logger.info("Connecting to origin websocket at {}", websocketUrl); logger.info("Connecting to origin websocket at {}", websocketUrl);
client.start(); client.start();
client.connect(this, URI.create(websocketUrl), request); client.connect(this, URI.create(websocketUrl), request);
logger.info("Connected to origin websocket");
} catch (Exception e) { } catch (Exception e) {
throw new IOException("Failed to start websocket client to origin " + websocketUrl, e); throw new IOException("Failed to start websocket client to origin " + websocketUrl, e);
} }
@ -124,6 +140,7 @@ public class NitroWebsocketServer implements WebSocketListener, NitroSession {
@Override @Override
public void onWebSocketConnect(org.eclipse.jetty.websocket.api.Session session) { public void onWebSocketConnect(org.eclipse.jetty.websocket.api.Session session) {
activeSession = session; activeSession = session;
state.setConnected(HMessage.Direction.TOCLIENT);
} }
@Override @Override

View File

@ -35,6 +35,7 @@ public class NitroPacketHandler extends PacketHandler {
final Session localSession = session.getSession(); final Session localSession = session.getSession();
if (localSession == null) { if (localSession == null) {
logger.warn("Discarding {} bytes because the session for direction {} was null", buffer.length, this.direction);
return false; return false;
} }