From de39613091c5440982b52adba9f2d29c2be87015 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Wed, 7 Jun 2023 17:38:05 +0200 Subject: [PATCH 1/4] Bypass user-agent based cloudflare challenge --- .../nitro/websocket/NitroWebsocketServer.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java index 37ca63d..673badb 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java @@ -6,6 +6,8 @@ import gearth.protocol.connection.proxy.nitro.NitroConstants; import gearth.protocol.packethandler.PacketHandler; import gearth.protocol.packethandler.nitro.NitroPacketHandler; 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.websocket.api.Session; import org.eclipse.jetty.websocket.api.WebSocketListener; @@ -36,7 +38,11 @@ public class NitroWebsocketServer implements WebSocketListener, NitroSession { "Sec-WebSocket-Version", "Host", "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; @@ -56,6 +62,8 @@ public class NitroWebsocketServer implements WebSocketListener, NitroSession { final ClientUpgradeRequest request = new ClientUpgradeRequest(); + request.addExtensions("permessage-deflate"); + clientHeaders.forEach((key, value) -> { if (SKIP_HEADERS.contains(key)) { return; @@ -64,6 +72,13 @@ public class NitroWebsocketServer implements WebSocketListener, NitroSession { 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); client.start(); From df3aabf51897bdf64038afd14695351b805cc477 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Wed, 7 Jun 2023 19:19:50 +0200 Subject: [PATCH 2/4] Add log message for packet discard --- .../gearth/protocol/packethandler/nitro/NitroPacketHandler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/nitro/NitroPacketHandler.java b/G-Earth/src/main/java/gearth/protocol/packethandler/nitro/NitroPacketHandler.java index fc72f34..853127b 100644 --- a/G-Earth/src/main/java/gearth/protocol/packethandler/nitro/NitroPacketHandler.java +++ b/G-Earth/src/main/java/gearth/protocol/packethandler/nitro/NitroPacketHandler.java @@ -35,6 +35,7 @@ public class NitroPacketHandler extends PacketHandler { final Session localSession = session.getSession(); if (localSession == null) { + logger.warn("Discarding {} bytes because the session for direction {} was null", buffer.length, this.direction); return false; } From c7037c0441ca936ba65249a8eebcb6bfd19ccb4e Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Wed, 7 Jun 2023 19:35:21 +0200 Subject: [PATCH 3/4] Better nitro connection state management --- .../proxy/nitro/NitroConnectionState.java | 58 +++++++++++++++++++ .../nitro/websocket/NitroWebsocketClient.java | 15 ++--- .../nitro/websocket/NitroWebsocketServer.java | 8 ++- 3 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConnectionState.java diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConnectionState.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConnectionState.java new file mode 100644 index 0000000..42c7b20 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConnectionState.java @@ -0,0 +1,58 @@ +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 void checkConnected() { + if (this.aborting) { + return; + } + + if (!this.toClient) { + return; + } + + if (!this.toServer) { + return; + } + + this.stateSetter.setState(HState.CONNECTED); + + logger.info("Connected"); + } + + public void setAborting() { + this.aborting = true; + this.stateSetter.setState(HState.ABORTING); + + logger.info("Aborting"); + } + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java index 7ebfc7d..1c0cc78 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java @@ -3,6 +3,7 @@ package gearth.protocol.connection.proxy.nitro.websocket; import gearth.protocol.HConnection; import gearth.protocol.HMessage; import gearth.protocol.connection.*; +import gearth.protocol.connection.proxy.nitro.NitroConnectionState; import gearth.protocol.connection.proxy.nitro.NitroConstants; import gearth.protocol.connection.proxy.nitro.NitroProxyProvider; import gearth.protocol.packethandler.nitro.NitroPacketHandler; @@ -25,8 +26,7 @@ public class NitroWebsocketClient implements NitroSession { private static final Logger logger = LoggerFactory.getLogger(NitroWebsocketClient.class); private final HProxySetter proxySetter; - private final HStateSetter stateSetter; - private final HConnection connection; + private final NitroConnectionState state; private final NitroProxyProvider proxyProvider; private final NitroWebsocketServer server; private final NitroPacketHandler packetHandler; @@ -36,10 +36,9 @@ public class NitroWebsocketClient implements NitroSession { public NitroWebsocketClient(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection, NitroProxyProvider proxyProvider) { this.proxySetter = proxySetter; - this.stateSetter = stateSetter; - this.connection = connection; + this.state = new NitroConnectionState(stateSetter); 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.shutdownLock = new AtomicBoolean(); } @@ -72,11 +71,13 @@ public class NitroWebsocketClient implements NitroSession { ); proxySetter.setProxy(proxy); - stateSetter.setState(HState.CONNECTED); + state.setConnected(HMessage.Direction.TOSERVER); } @OnMessage public void onMessage(byte[] b, Session session) throws IOException { + logger.debug("Received packet from browser"); + packetHandler.act(b); } @@ -133,7 +134,7 @@ public class NitroWebsocketClient implements NitroSession { // Reset program state. proxySetter.setProxy(null); - stateSetter.setState(HState.ABORTING); + state.setAborting(); } } } diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java index 673badb..1177fa8 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java @@ -2,6 +2,7 @@ package gearth.protocol.connection.proxy.nitro.websocket; import gearth.protocol.HConnection; import gearth.protocol.HMessage; +import gearth.protocol.connection.proxy.nitro.NitroConnectionState; import gearth.protocol.connection.proxy.nitro.NitroConstants; import gearth.protocol.packethandler.PacketHandler; import gearth.protocol.packethandler.nitro.NitroPacketHandler; @@ -47,10 +48,12 @@ public class NitroWebsocketServer implements WebSocketListener, NitroSession { private final PacketHandler packetHandler; private final NitroWebsocketClient client; + private final NitroConnectionState state; private Session activeSession = null; - public NitroWebsocketServer(HConnection connection, NitroWebsocketClient client) { + public NitroWebsocketServer(HConnection connection, NitroWebsocketClient client, NitroConnectionState state) { this.client = client; + this.state = state; this.packetHandler = new NitroPacketHandler(HMessage.Direction.TOCLIENT, client, connection.getExtensionHandler(), connection.getTrafficObservables()); } @@ -83,8 +86,6 @@ public class NitroWebsocketServer implements WebSocketListener, NitroSession { client.start(); client.connect(this, URI.create(websocketUrl), request); - - logger.info("Connected to origin websocket"); } catch (Exception e) { throw new IOException("Failed to start websocket client to origin " + websocketUrl, e); } @@ -139,6 +140,7 @@ public class NitroWebsocketServer implements WebSocketListener, NitroSession { @Override public void onWebSocketConnect(org.eclipse.jetty.websocket.api.Session session) { activeSession = session; + state.setConnected(HMessage.Direction.TOCLIENT); } @Override From 1579bed1ef9dfab3d692644ca6efa96285d521f6 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Wed, 7 Jun 2023 19:59:55 +0200 Subject: [PATCH 4/4] Fix discarded packets --- .../proxy/nitro/NitroConnectionState.java | 14 ++++++-- .../proxy/nitro/NitroPacketQueue.java | 28 ++++++++++++++++ .../nitro/websocket/NitroWebsocketClient.java | 33 ++++++++++++++++++- 3 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroPacketQueue.java diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConnectionState.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConnectionState.java index 42c7b20..c5381f7 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConnectionState.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConnectionState.java @@ -30,16 +30,24 @@ public class NitroConnectionState { this.checkConnected(); } - public void checkConnected() { + public boolean isConnected() { if (this.aborting) { - return; + return false; } if (!this.toClient) { - return; + return false; } if (!this.toServer) { + return false; + } + + return true; + } + + private void checkConnected() { + if (!this.isConnected()) { return; } diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroPacketQueue.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroPacketQueue.java new file mode 100644 index 0000000..9fc2869 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroPacketQueue.java @@ -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 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()); + } + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java index 1c0cc78..8740fd9 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java @@ -2,9 +2,11 @@ package gearth.protocol.connection.proxy.nitro.websocket; import gearth.protocol.HConnection; import gearth.protocol.HMessage; +import gearth.protocol.StateChangeListener; import gearth.protocol.connection.*; import gearth.protocol.connection.proxy.nitro.NitroConnectionState; import gearth.protocol.connection.proxy.nitro.NitroConstants; +import gearth.protocol.connection.proxy.nitro.NitroPacketQueue; import gearth.protocol.connection.proxy.nitro.NitroProxyProvider; import gearth.protocol.packethandler.nitro.NitroPacketHandler; import org.eclipse.jetty.websocket.jsr356.JsrSession; @@ -26,20 +28,24 @@ public class NitroWebsocketClient implements NitroSession { private static final Logger logger = LoggerFactory.getLogger(NitroWebsocketClient.class); private final HProxySetter proxySetter; + private final HConnection connection; private final NitroConnectionState state; private final NitroProxyProvider proxyProvider; private final NitroWebsocketServer server; private final NitroPacketHandler packetHandler; + private final NitroPacketQueue packetQueue; private final AtomicBoolean shutdownLock; private JsrSession activeSession = null; public NitroWebsocketClient(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection, NitroProxyProvider proxyProvider) { this.proxySetter = proxySetter; + this.connection = connection; this.state = new NitroConnectionState(stateSetter); this.proxyProvider = proxyProvider; this.server = new NitroWebsocketServer(connection, this, this.state); this.packetHandler = new NitroPacketHandler(HMessage.Direction.TOSERVER, server, connection.getExtensionHandler(), connection.getTrafficObservables()); + this.packetQueue = new NitroPacketQueue(this.packetHandler); this.shutdownLock = new AtomicBoolean(); } @@ -47,6 +53,24 @@ public class NitroWebsocketClient implements NitroSession { public void onOpen(Session session) throws Exception { 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.setMaxBinaryMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE); activeSession.setMaxTextMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE); @@ -78,7 +102,14 @@ public class NitroWebsocketClient implements NitroSession { public void onMessage(byte[] b, Session session) throws IOException { logger.debug("Received packet from browser"); - packetHandler.act(b); + // 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